web-dev-qa-db-ja.com

シンボリックリンクがループするかどうかを決定するアルゴリズムはありますか?

Unixシステムは通常、1つのパスルックアップで通過するシンボリックリンクの数に制限があるため、シンボリックリンクループまたはシンボリックリンクが多すぎるパスに直面するとエラーになります。しかし、たとえ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に解決されます。

17
JanKanis

OK、もう少し考えた後、私には明確な解決策があると思います。

重要な洞察は、パスの一部であるすべてのリンクが何かに解決される場合、パス全体が解決されるということです。または逆に、パスが解決しない場合は、解決しないトラバースが必要な特定のシンボリックリンクが存在する必要があります。

以前この問題について考えていたときに、ルートから始まるパスの要素をトラバースするアルゴリズムを使用していました。シンボリックリンクが検出されると、そのパス要素をシンボリックリンクのコンテンツに置き換えてトラバースを続行しました。このアプローチは、現在解決しているシンボリックリンクを覚えていないため、非解決ループにあることを検出できません。

アルゴリズムが現在解決中のシンボリックリンク(または再帰リンクの場合はどのシンボリックリンク)を追跡している場合、解決にビジーなリンクを再帰的に解決しようとしているかどうかを検出できます。

アルゴリズム:

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

edit

python at https://bitbucket.org/JanKanis/python-inotify/src/853ed903e870cbfa283e6ce7a5e41aeffe16d4e7/inotify/pathresolver.py?at=pathwatcher

5
JanKanis

私はあなたが何を求めているのか完全には理解していません。もっとよくわからなかったら、ファイルを処理している最中にこれを検出する方法があるかどうか尋ねていたと思います。これが可能だとは思いません。

私が思いつく唯一の方法は、ディレクトリツリーの特定のブランチを調べ始める検索を行うことです。

_$ 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
_

findによって表示される出力をブロックするために、15レベルを任意に選択しました。ただし、表示されているディレクトリツリーを気にしない場合は、そのスイッチ(_-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ですが、このU&L Q&Aのタイトルは次のとおりです。 MAXSYMLINKSを増やす方法 =。

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()は、文字列oldpathを含むnewpathという名前のシンボリックリンクを作成します。

エラーの1つは、この関数が返すことを示しています。

ELOOP newpathの解決で遭遇したシンボリックリンクが多すぎます。

また、Unixがディスク上のアイテムへのパスを決定する方法について説明している_man path_resolution_のマニュアルページも紹介します。具体的にはこの段落。

_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").
_
10
slm

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']]
3
Back2Basics

静止システム(つまり、変更が行われていない場合)では、アルゴリズムがあります。シンボリックリンクの数は有限であるため、有限グラフを構成し、サイクルの検出は最終的なプロセスです。

ライブシステムでは、サイクル検出器の実行中にシンボリックリンクが変更される可能性があるため、サイクルを検出する方法はありません。各シンボリックリンクの読み取りはアトミックですが、シンボリックリンクをたどることは不可です。カーネルがトラバーサルを実行している間に一部のシンボリックリンクが変化し続ける場合、それは異なるリンクを含む無限のパスになる可能性があります。

現在のLinuxカーネルソースを見るとわかるように、カーネルが実行するのは、たどったリンクの数を数えることだけであり、ある数よりも大きい場合はエラーになります。コメントについては namei.cの1330行目nested_symlink()関数を参照してください。 ELOOPマクロ(この状況でread(2)システムコールから返されるエラー番号)は、そのファイル内のいくつかの場所に表示されるため、リンクを数えるほど単純ではないかもしれませんが、それのように見えます。

リンクされたリスト( フロイドのサイクル検出アルゴリズム )または 有向グラフ に「サイクル」を見つけるためのアルゴリズムがいくつかあります。特定のパス内の実際の「ループ」または「サイクル」を検出するためにどれを実行する必要があるかは、私にはわかりません。いずれにせよ、アルゴリズムの実行には長い時間がかかる可能性があるため、たどったシンボリックリンクの数を数えるだけで目標に到達するまでの道のりは90%になると思います。

2
Bruce Ediger