是否有一种算法可以确定符号链接是否循环?


16

如果Unix系统遇到包含符号链接循环或符号链接太多的路径,通常会出错,因为它们对一次路径查找中要遍历的符号链接的数量有限制。但是,有没有一种方法可以真正确定给定的路径是否解析为某种路径或是否包含循环,即使该路径包含的链接数量超出了unix愿意遵循的范围?还是这是一个无法确定的正式问题?如果可以决定,是否可以在合理的时间/内存范围内决定(例如,无需访问文件系统上的所有文件)?

一些例子:

a/b/c/d
where a/b is a symlink to ../e
and e is a symlink to f
and f is a symlink to a/b

a/b/c/d
where a/b/c is a symlink to ../c

a/b/c/d
where a/b/c is a symlink to ../c/d

a/b/c/d
where a/b/c is a symlink to /a/b/e
where a/b/e is a symlink to /a/b/f
where a/b/f is a symlink to /a/b/g

编辑

澄清一下,我不是在寻找在文件系统中循环的问题,而是在问一个决策算法,该算法确定给定路径是解析为确定的文件/目录还是根本不解析。例如,在以下系统中,存在一个循环,但是给定的路径仍然可以正常解析:

/ -- a -- b
where b is a symlink to /a

这个目录树显然有一个循环,但是路径a/b/b/b/b/b仍然可以解析为/a


命令行工具readlink ...对上述情况有何看法?
slm

1
您是否在问我们是否只能从路径名中得知是否存在循环?还是我们可以在真实的操作系统中使用标准工具并检查路径名的各个组成部分解析为什么?
Mike Diehn

@MikeDiehn显然,如果不执行文件系统操作,就无法仅通过路径判断其是否解析。但是在OS环境中,区分仅需要遍历许多符号链接来解析的路径与根本不解析的路径也不是一件容易的事。
JanKanis 2013年

Answers:


10

我不完全了解您的要求。如果我不了解,我想您是在问是否有一种方法可以在处理文件时检测到这一点。我不认为这是可能的。

我能想到的唯一方法是进行查找,从特定位置开始查找目录树中的特定分支。

$ tree 
.
`-- a
    `-- b
        |-- c
        |   `-- d
        |       `-- e -> ../../../../a/b
        `-- e -> e

5 directories, 1 file

find命令将检测到此循环,但不会真正告诉您很多有关该循环的信息。

$ find -L . -mindepth 15
find: File system loop detected; `./a/b/c/d/e' is part of the same file system loop as `./a/b'.
find: `./a/b/e': Too many levels of symbolic links

我任意选择了15个电平,以阻止显示的任何输出find。但是,-mindepth如果您不关心显示的目录树,则可以删除该开关()。该find命令仍然检测到循环并停止:

$ find -L . 
.
./a
./a/b
./a/b/c
./a/b/c/d
find: File system loop detected; `./a/b/c/d/e' is part of the same file system loop as `./a/b'.
find: `./a/b/e': Too many levels of symbolic links

顺便说一句,如果您想覆盖默认值MAXSYMLINKS(在Linux(内核的3.x新版本)上显然是40),则可以看到标题为:如何增加MAXSYMLINKS的此U&L问答。

使用symlinks命令

FTP站点维护人员可以使用一种称为的工具,该工具symlinks将帮助解决由符号链接引起的工具太长或悬挂树的问题。

在某些情况下,该symlinks工具也可用于删除违规链接。

$ symlinks -srv a
lengthy:  /home/saml/tst/99159/a/b/c/d/e -> ../../../../a/b
dangling: /home/saml/tst/99159/a/b/e -> e

glibc库

glibc库似乎提供了一些与此相关的C函数,但我并不完全知道它们的作用或如何实际使用它们。因此,我只能将它们指出给您。

手册页man symlink显示了名为的函数的函数定义symlink()。描述如下:

symlink()创建一个名为newpath的符号链接,其中包含字符串oldpath。

错误之一表明此函数返回:

ELOOP在解析newpath时遇到太多符号链接。

我还将带您到手册页,man path_resolution该手册页讨论Unix如何确定磁盘上项目的路径。特别是本段。

If  the component is found and is a symbolic link (symlink), we first 
resolve this symbolic link (with the current lookup directory as starting 
lookup directory).  Upon error, that error is returned.  If the result is 
not a directory, an ENOTDIR error is returned.  If the resolution of the 
symlink is successful and returns a directory, we set the current lookup
directory to that directory, and go to the next component.  Note that the 
resolution process here involves recursion.  In order  to  protect  the 
kernel against stack overflow, and also to protect against denial of 
service, there are limits on the maximum recursion depth, and on the maximum 
number of symbolic links followed.  An ELOOP error is returned  when  the
maximum is exceeded ("Too many levels of symbolic links").

如果可能,我希望有一种方法可以在给定单个路径时检测到符号链接循环,并在程序中手动解决符号链接,而不是让OS来解决。但是我想知道这是否可能。查找解决方案看起来很有趣,但是您是否知道/ how / find检测到符号链接循环,以及它使用的方法是否完整(即,检测到所有可能的循环并且不会误识别任何非循环路径)?
JanKanis 2013年

@Somejan-查看我对A的更新。让我知道是否有意义。
slm

5

好的,经过一番思考之后,我认为我有一个明确的解决方案。

关键的见解是,如果路径中的每个链接都解析为某种内容,那么整个路径都将解析。或相反,如果路径无法解析,则必须存在一个特定的符号链接,该符号链接要求无法解析的遍历。

以前在考虑此问题时,我使用的算法是从根开始遍历路径的元素,当遇到符号链接时,它将该路径元素替换为符号链接的内容,然后继续遍历。由于此方法无法记住当前正在解析的符号链接,因此无法检测到何时处于非解析循环中。

如果该算法跟踪当前正在解析的符号链接(或在递归链接的情况下跟踪哪个符号链接),则它可以检测是否正在尝试递归地解析仍在忙于解析的链接。

算法:

initialize `location` to the current working directory
initialize `link_contents` to the path we want to resolve
initialize `active_symlinks` to the empty set

def resolve_symlink(location, link_contents, active_symlinks) :
    loop forever:
        next_location = location / [first element of link_contents]
        see if next_location is a symlink.
        if so:
            if next_location in active_symlinks: abort, we have a loop
            location = resolve_symlink(location, readlink(next_location), active_symlinks ∪ {next_location})
        else:
            location = next_location
        strip first element of link_contents
        if link_contents is empty: 
            return location

编辑

我在https://bitbucket.org/JanKanis/python-inotify/src/853ed903e870cbfa283e6ce7a5e41aeffe16d4e7/inotify/pathresolver.py?at=pathwatcher中使用python实现了这一目标。


3

Python有一个称为networkx.simple_cycles()的函数可用于此目的。但是,是的,它需要读取系统上的每个文件。

>>> import networkx as nx
>>> G = nx.DiGraph()
>>> G.add_edge('A', 'B')
>>> G.add_edge('B', 'C')
>>> G.add_edge('C', 'D')
>>> G.add_edge('C', 'A')
>>> nx.simple_cycles(G)
[['A', 'B', 'C', 'A']]

我还考虑过使用某种图形算法,但是我不确定带有符号链接的目录树是否可以在一个简单图形中充分表示。在目录树abc中,其中c是到..的符号链接,存在一个循环,但是像a / b / c / b / c / b之类的路径仍然可以解析,因为它们仅遵循循环有限的次数,并且不继续循环播放。
JanKanis 2013年

@Somejan:文件系统名称空间一个图形,文件名是在该图形上选择的路径。
ninjalj 2013年

@ninjalj:是的,文件系统是一个图形,但是我不认为文件名仅仅是该图形上的路径。文件名可以看作是一组关于如何遍历图形的指令。即使该图包含的循环并不意味着该循环后面的文件名也不一定会解析,请参阅我先前示例中的示例。
JanKanis 2013年

3

是的,在静态系统上(即不进行任何更改时),存在一种算法。符号链接的数量有限,因此它们构成了一个有限的图,检测循环是一个最终的过程。

在实时系统上,无法检测周期,因为在周期检测器运行时,符号链接可能会更改。读取每个符号链接都是原子的,但是跟随符号链接不是。如果在内核进行遍历时某些符号链接不断变化,则它可能最终到达包含不同链接的无限路径上。


有一些方法可以减轻这些更改,以使其准确性达到98-99%。您可以使它注意文件上的时间戳,我不建议您实际关注链接。由于它是从根递归的,因此稍后将找到实际目录。
Back2Basics

1
@ Back2Basics这些数字完全没有意义。这是一个内核接口。如果它一直不起作用,那它就不起作用了。
吉尔斯(Gilles)'所以

2

从查看当前的Linux内核资源可以看出,内核所做的只是保持对链接数量的了解,如果它大于某个数量,则会出错。有关注释和功能,请参见namei.c中的1330行nested_symlink()。ELOOP宏(在read(2)这种情况下从系统调用返回的错误号)显示在该文件的许多位置,因此它可能不像对链接进行计数那样简单,但是可以肯定它的外观。

在链表(Floyd的循环检测算法)或有向图中,可以找到许多算法来查找“循环” 。对我来说,不清楚要在特定路径中检测到实际的“循环”或“循环”,您必须执行哪个操作。无论如何,这些算法都可能需要花费很长时间才能运行,所以我猜测仅计算跟随的符号链接的数量就可以达到目标的90%。


对于实际用途,只需计算遍历的链接数就可以了,特别是因为这是内核的工作,因此,即使遇到符号链接过多的正确解析路径,您仍然无法将该路径用于任何实际的操作(即不涉及手动解决符号链接)
JanKanis
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.