シンボリックリンクの再帰-何がそれを「リセット」しますか?


64

同じディレクトリを指すシンボリックリンクをたどっていくとどうなるかを確認するために、小さなbashスクリプトを書きました。非常に長い作業ディレクトリを作成するか、クラッシュすることを期待していました。しかし、結果は私を驚かせました...

mkdir a
cd a

ln -s ./. a

for i in `seq 1 1000`
do
  cd a
  pwd
done

出力の一部は

${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a
${HOME}/a/a
${HOME}/a/a/a
${HOME}/a/a/a/a
${HOME}/a/a/a/a/a
${HOME}/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a

ここで何が起きてるの?

回答:


88

パトリスは彼の答えで問題の原因を特定しましたが、そこからどうやってそれを得るのかを知りたい場合は、ここに長い話があります。

プロセスの現在の作業ディレクトリは、複雑すぎるとは思わないでしょう。これは、プロセスの属性であり、相対パス(プロセスによって作成されたシステムコール内)が始まるディレクトリタイプのファイルへのハンドルです。相対パスを解決する場合、カーネルは(a)現在のディレクトリへのフルパスを知る必要はありません。そのディレクトリファイルのディレクトリエントリを読み取り、相対パスの最初のコンポーネントを見つけるだけです(..他のコンポーネントと同様です)その点でファイル)とそこから続行します。

現在、ユーザーとして、ディレクトリツリー内のそのディレクトリの場所を知りたい場合があります。ほとんどのUnicesでは、ディレクトリツリーはループのないツリーです。つまり、ツリーのルート(/)から特定のファイルへのパスは1つだけです。そのパスは、一般に標準パスと呼ばれます。

現在の作業ディレクトリのパスを取得するために、プロセスがなければならないことは、ノードの名前を見つけるために、ルートに戻るツリーをたどるだけです(ルートが一番下にあるツリーを表示したい場合は十分に下降します)途中。

たとえば、現在のディレクトリが/a/b/cであることを見つけようとするプロセスは、..ディレクトリ(相対パス、..つまり現在のディレクトリのエントリ)を開き、と同じinode番号を持つディレクトリタイプのファイルを.探します。cが一致してから開く../..など、が見つかるまで続きます/。あいまいさはありません。

それは、getwd()またはgetcwd()C関数が行うこと、または少なくとも以前は行っていたことです。

現代のLinuxのような一部のシステムでは、カーネルスペースでそのルックアップを行う現在のディレクトリへの正規のパスを返すシステムコールがあります(すべてのコンポーネントへの読み取りアクセス権がない場合でも、現在のディレクトリを見つけることができます) 、そしてそれgetcwd()はそこを呼び出すものです。最新のLinuxでは、上のreadlink()を介して現在のディレクトリへのパスを見つけることもできます/proc/self/cwd

これが、現在のディレクトリへのパスを返すときに、ほとんどの言語と初期のシェルが行うことです。

あなたのケースでは、あなたが呼び出すことができますcd aあなたが望むようにはへのシンボリックリンクだから、得るような時間を.、現在のディレクトリはので、すべての変更されません、getcwd()pwd -Ppython -c 'import os; print os.getcwd()'perl -MPOSIX -le 'print getcwd'あなた返します${HOME}

さて、シンボリックリンクはそれをすべて複雑にしました。

symlinksディレクトリツリー内のジャンプを許可します。で/a/b/cあれば、/aまたは/a/bまたは/a/b/cシンボリックリンクで、その後の正規のパスは/a/b/c完全に異なるものになるだろう。特に、の..エントリ/a/b/cは必ずしも/a/bです。

Bourneシェルでは、次のようにします。

cd /a/b/c
cd ..

あるいは:

cd /a/b/c/..

で終わる保証はありません/a/b

と同じように:

vi /a/b/c/../d

以下と必ずしも同じではありません。

vi /a/b/d

ksh何らかの方法でそれを回避するために、論理的な現在の作業ディレクトリの概念を導入しました。人々はそれに慣れ、POSIXはその動作を指定することになりました。これは、今日のほとんどのシェルも同様に行うことを意味します。

以下の場合cdpwd組み込みコマンド(とだけ彼らのために(もかかわら用popd/ pushdそれらを持っているシェル上))、シェルは、現在の作業ディレクトリの独自のアイデアを維持しています。$PWD特別な変数に保存されます。

行うとき:

cd c/d

cまたはc/dがシンボリックリンクであっても、$PWDcontainesの/a/b場合、末尾に追加c/dされるため、に$PWDなり/a/b/c/dます。そしてあなたがするとき:

cd ../e

する代わりにchdir("../e")、それはしchdir("/a/b/c/e")ます。

また、pwdコマンドは$PWD変数の内容のみを返します。

それはので、インタラクティブシェルで便利ですpwd、あなたがそこにいる限り、あなたが唯一の使用として得た方法に関する情報を提供し、現在のディレクトリへのパス出力..への引数にcd他のコマンドとされていないので、それは、あなたを驚かせる可能性が低いのですcd a; cd ..かがcd a/..、一般的に戻ってあなたを得るでしょうあなたがいた場所に。

$PWDを実行しない限り、は変更されませんcd。次回、cdまたはを呼び出すまで、pwd多くのことが起こる可能性があり、のコンポーネントの$PWD名前は変更できます。現在のディレクトリは決して変更されません(削除される可能性はありますが、常に同じiノードです)が、ディレクトリツリー内のパスは完全に変更される可能性があります。getcwd()ディレクトリツリーをたどって呼び出されるたびに現在のディレクトリを計算し、その情報が常に正確になるようにしますが、POSIXシェルによって実装された論理ディレクトリでは、情報$PWDが古くなる可能性があります。したがって、cdまたはを実行するとpwd、一部のシェルはそれを防ぐ必要があります。

その特定のインスタンスでは、異なるシェルで異なる動作が見られます。

一部の人ksh93は問題を完全に無視するので、電話をかけた後でも間違った情報を返しますcd(そして、あなたがbashそこで見ている振る舞いを見ることはありません)。

以下のようないくつかbashまたはzshそれがチェックしますか$PWD、まだ時に、現在のディレクトリへのパスでcdない時に、しかしpwd

pdkshは両方pwdをチェックしますcdが(ただしpwd、更新はしません$PWD

ash(Debianの上で見つかった少なくとも一つ)はチェックしません、とあなたが行うときcd a、それは実際にはないcd "$PWD/a"、現在のディレクトリが変更されていないとしているので、もし、$PWD現在のディレクトリに、もはやポイント、それは実際には変更されませんa、現在のディレクトリ内のディレクトリ、ただし、1つ$PWD(存在しない場合はエラーを返します)。

あなたがそれで遊びたいなら、あなたはすることができます:

cd
mkdir -p a/b
cd a
pwd
mv ~/a ~/b 
pwd
echo "$PWD"
cd b
pwd; echo "$PWD"; pwd -P # (and notice the bug in ksh93)

さまざまなシェルで。

あなたの場合、を使用しているためbash、の後にcd a、まだ現在のディレクトリを指すbashチェックがあり$PWDます。それを行うstat()には、値を呼び出して$PWDそのiノード番号を確認し、そのiノード番号と比較し.ます。

しかし、$PWDパスの検索に含まれるシンボリックリンクの数が多すぎるstat()とエラーが返されるため、シェル$PWDは現在のディレクトリにまだ対応しているかどうかを確認できないため、再計算しgetcwd()て更新します$PWD

Patriceの答えを明確にするために、パスの検索中に発生したシンボリックリンクの数のチェックは、シンボリックリンクのループを防ぐことです。最も単純なループは

rm -f a b
ln -s a b
ln -s b a

セーフガードがなければcd a/x、システムはにaリンクする場所を見つけなければならず、それを見つけ、それbがにリンクするシンボリックリンクaであり、それは無期限に続きます。それを防ぐ最も簡単な方法は、任意の数を超えるシンボリックリンクを解決した後にgiveめることです。

論理的な現在の作業ディレクトリに戻り、なぜそれがあまり良い機能ではないのか。cdシェル内でのみ使用され、他のコマンドではないことを理解することが重要です。

例えば:

cd -- "$dir" &&  vi -- "$file"

常に同じとは限りません:

vi -- "$dir/$file"

そのcd -Pため、混乱を避けるために常にスクリプトで使用することを人々が推奨することがあります(../x別の言語ではなくシェルで書かれているという理由だけで、ソフトウェアが他のコマンドとは異なる引数を処理するのは望ましくありません)。

-Pオプションは無効にすることで論理ディレクトリので、取り扱いcd -P -- "$var"、実際に呼び出してんchdir()の内容に$var(場合を除き、$varされる-が、それはまた別の話だが)。そして、後にcd -P$PWD正規のパスが含まれます。


7
甘いイエス!このような包括的な答えをありがとう、それは本当に非常に興味深いです:)
ルーカス

素晴らしい答え、どうもありがとう!私はこれらすべてのことを少しでも知っていたように感じますが、それらがどのように一緒になったかについては理解も考えもしていませんでした。素晴らしい説明。
dimo414

42

これは、Linuxカーネルソースにハードコーディングされた制限の結果です。サービス拒否を防ぐため、ネストされたシンボリックリンクの数の制限は40です(カーネルソースで呼び出されるinside のfollow_link()関数にあります)。fs/namei.cnested_symlink()

おそらくシンボリックリンクをサポートする他のカーネルでも同様の動作(おそらく40を超える制限)が発生します。


1
停止するだけでなく、「リセット」する理由がありますか。すなわち、とx%40いうよりmax(x,40)。ディレクトリを変更したのをまだ見ることができると思います。
ルーカス

4
好奇心anyone盛
ベン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.