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


16

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ます。


readlink ...上記の状況について、コマンドラインツールは何と言いますか?
slm

1
ループがあるかどうかをパス名だけで判断できるかどうか尋ねていますか?または、標準ツールを使用して、パス名のさまざまなコンポーネントの解決先を確認しながら、実際のオペレーティングシステムでこれを実行できますか?
マイクディーン

@MikeDiehn明らかに、ファイルシステム操作を行わずに解決するかどうかをパスだけで判断することはできません。しかし、OS環境でも、解決するために多くのシンボリックリンクをたどるだけのパスを、まったく解決しないパスと区別するのは簡単ではありません。
-JanKanis

回答:


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

ちなみに、MAXSYMLINKSLinux(カーネルの新しい3.xバージョン)で明らかに40であるデフォルトをオーバーライドしたい場合は、「MAXSYMLINKSを増やすにはどうすればよいですか?」というタイトルのこのU&L Q&Aを参照できます。

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の解決中に検出されたシンボリックリンクが多すぎます。

また、manページに移動して、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に実行させるのではなく、プログラムでシンボリックリンクを手動で解決する方法が必要です。しかし、これが可能かどうか疑問に思っています。findソリューションは興味深いように見えますが、/ how / findがシンボリックリンクループを検出し、それが使用する方法が完了している(つまり、考えられるすべてのループを検出し、非ループパスを誤認しない)アイデアはありますか?
JanKanis

@Somejan-Aの更新を参照してください。それが理にかなっている場合はお知らせください。
slm

5

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

編集

私は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']]

また、ある種のグラフアルゴリズムを使用することも考えましたが、シンボリックリンクを持つディレクトリツリーを単純なグラフで適切に表現できるかどうかはわかりません。cが..へのシンボリックリンクであるディレクトリツリーabcにはループがありますが、a / b / c / b / c / bのようなパスはループを有限回数だけたどっていないため、まだ解決しますループを続けます。
JanKanis

@Somejan:ファイルシステムの名前空間グラフであり、ファイル名はそのグラフ上で選択されたパスです。
ninjalj

@ninjalj:はい、ファイルシステムはグラフですが、ファイル名はそのグラフ上の単なるパスではないと思います。ファイル名は、グラフをトラバースする方法に関する一連の指示として見ることができます。グラフにサイクルが含まれていても、そのサイクルに続くファイル名が必ずしも解決しないわけではない場合は、前のコメントの例を参照してください。
JanKanis

3

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

稼働中のシステムでは、サイクル検出器の実行中にシンボリックリンクが変更される可能性があるため、サイクルを検出する方法はありません。各シンボリックリンクの読み取りはアトミックですが、シンボリックリンクのフォローはそうではありません。カーネルがトラバーサルを行っている間にいくつかのシンボリックリンクが変化し続けると、別個のリンクを含む無限のパスになる可能性があります。


これらの変更を緩和して、98〜99%の精度を実現する方法があります。ファイルのタイムスタンプに注意を払うことができますが、実際にリンクをたどることはお勧めしません。ルートから再帰的であるため、後で実際のディレクトリを見つけます。
Back2Basics

1
@ Back2Basicsこれらの数値は完全に無意味です。これはカーネルインターフェイスです。常に機能しない場合、期間は機能しません。
ジル 'SO-悪であるのをやめる'

2

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

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


実際の使用では、トラバースされたリンクの数を数えるだけで結構です。特にカーネルがそれを行うので、シンボリックリンクが多すぎて正しく解決できるパスに遭遇しても、そのパスを実用的なものに使用することはできません(つまり、手動でシンボリックリンクを解決する必要はありません)
JanKanis
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.