Rekursion symbolischer Links - was macht es zum „Zurücksetzen“?


64

Ich habe ein kleines Bash-Skript geschrieben, um zu sehen, was passiert, wenn ich einem symbolischen Link folge, der auf dasselbe Verzeichnis verweist. Ich hatte erwartet, dass es entweder ein sehr langes Arbeitsverzeichnis erstellt oder abstürzt. Aber das Ergebnis hat mich überrascht ...

mkdir a
cd a

ln -s ./. a

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

Ein Teil der Ausgabe ist

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

was passiert hier?

Antworten:


88

Patrice identifizierte die Ursache des Problems in seiner Antwort , aber wenn Sie wissen möchten, wie Sie von dort zu dem Grund kommen, warum Sie das bekommen, ist hier die lange Geschichte.

Das aktuelle Arbeitsverzeichnis eines Prozesses ist nichts, was Sie für zu kompliziert halten würden. Es ist ein Attribut des Prozesses, das ein Handle für eine Datei vom Typ Verzeichnis ist, von dem aus relative Pfade (in vom Prozess ausgeführten Systemaufrufen) beginnen. Wenn ein relativer Pfad aufgelöst wird, muss der Kernel nicht den (a) vollständigen Pfad zu diesem aktuellen Verzeichnis kennen, sondern liest nur die Verzeichniseinträge in dieser Verzeichnisdatei, um die erste Komponente des relativen Pfads zu finden (und ..verhält sich wie jede andere Datei in dieser Hinsicht) und fährt von dort fort.

Als Benutzer möchten Sie jetzt manchmal wissen, wo sich dieses Verzeichnis in der Verzeichnisstruktur befindet. Bei den meisten Unices ist der Verzeichnisbaum ein Baum ohne Schleife. Das heißt, es gibt nur einen Pfad von der Wurzel des Baums ( /) zu einer bestimmten Datei. Dieser Weg wird allgemein als kanonischer Weg bezeichnet.

Um den Pfad des aktuellen Arbeitsverzeichnisses zu ermitteln, muss ein Prozess nur den Baum nach oben (und nach unten, wenn Sie einen Baum mit seiner Wurzel unten sehen möchten) zurück zur Wurzel gehen und die Namen der Knoten finden unterwegs.

Zum Beispiel würde ein Prozess, der versucht herauszufinden, dass es sich um sein aktuelles Verzeichnis handelt /a/b/c, das ..Verzeichnis öffnen (relativer Pfad, ebenso ..wie der Eintrag im aktuellen Verzeichnis) und nach einer Datei vom Typ Verzeichnis mit der gleichen Inode-Nummer wie .suchen cpasst, öffnet sich dann ../..und so weiter, bis es findet /. Da gibt es keine Mehrdeutigkeit.

Das ist, was die getwd()oder getcwd()C-Funktionen tun oder zumindest verwendet haben.

Auf einigen Systemen wie dem modernen Linux gibt es einen Systemaufruf, der den kanonischen Pfad zum aktuellen Verzeichnis zurückgibt, der die Suche im Kernelraum durchführt (und es Ihnen ermöglicht, Ihr aktuelles Verzeichnis zu finden, auch wenn Sie nicht auf alle seine Komponenten lesend zugreifen können). , und das getcwd()nennt man dort. Unter modernen Linux finden Sie den Pfad zum aktuellen Verzeichnis auch über einen readlink () auf /proc/self/cwd.

Das ist es, was die meisten Sprachen und frühen Shells tun, wenn sie den Pfad zum aktuellen Verzeichnis zurückgeben.

In Ihrem Fall können Sie anrufen , cd awie kann mal , wie Sie wollen, denn es ist ein symbolischer Link ist zu ., wird das aktuelle Verzeichnis nicht so von allen ändern getcwd(), pwd -P, python -c 'import os; print os.getcwd()', perl -MPOSIX -le 'print getcwd'zurückkehren würde Ihre ${HOME}.

Nun erschwerten Symlinks all das.

symlinksErlaube Sprünge im Verzeichnisbaum. In /a/b/c, wenn /aoder /a/boder /a/b/cein symbolischer Link, dann die kanonische Pfad /a/b/cetwas ganz anderes sein würde. Insbesondere ist der ..Eintrag in /a/b/cnicht zwingend /a/b.

Wenn Sie in der Bourne-Shell Folgendes tun:

cd /a/b/c
cd ..

Oder auch:

cd /a/b/c/..

Es gibt keine Garantie dafür, dass Sie ankommen /a/b.

So wie:

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

ist nicht unbedingt dasselbe wie:

vi /a/b/d

kshführte das Konzept eines logischen aktuellen Arbeitsverzeichnisses ein , um das irgendwie zu umgehen. Die Leute haben sich daran gewöhnt und POSIX hat letztendlich dieses Verhalten spezifiziert, was bedeutet, dass die meisten Shells es heutzutage auch tun:

Für die cdund pwdeingebauten Befehle ( und nur für sie (obwohl auch für popd/ pushdauf Shells, die sie haben)) behält die Shell ihre eigene Vorstellung des aktuellen Arbeitsverzeichnisses bei. Es ist in der $PWDspeziellen Variablen gespeichert .

Wenn Sie das tun:

cd c/d

auch wenn symlinks sind coder enthalten , so wird es an das ende angehängt . Und wenn du das tust:c/d$PWD/a/bc/d$PWD/a/b/c/d

cd ../e

Anstatt es zu tun chdir("../e"), tut es es chdir("/a/b/c/e").

Und der pwdBefehl gibt nur den Inhalt der $PWDVariablen zurück.

Dies ist in interaktiven Shells nützlich, da pwdein Pfad zum aktuellen Verzeichnis ausgegeben wird, der Informationen darüber enthält, wie Sie dorthin gelangt sind. Solange Sie nur ..Argumente für cdund keine anderen Befehle verwenden, ist es weniger wahrscheinlich, dass Sie überrascht werden, da Sie im Allgemeinen zurückkehren cd a; cd ..oder cd a/..dies tun würden zu wo du warst.

Wird jetzt $PWDerst geändert, wenn Sie eine cd. Bis Sie das nächste Mal anrufen cdoder pwdeine Menge Dinge passieren könnten, könnten alle Komponenten von $PWDumbenannt werden. Das aktuelle Verzeichnis ändert sich nie (es ist immer derselbe Inode, obwohl es gelöscht werden könnte), aber sein Pfad in der Verzeichnisstruktur kann sich vollständig ändern. getcwd()Berechnet das aktuelle Verzeichnis bei jedem Aufruf durch Durchlaufen der Verzeichnisstruktur, damit die Informationen immer korrekt sind. Bei dem von POSIX-Shells implementierten logischen Verzeichnis können die Informationen jedoch $PWDveraltet sein. Wenn Sie also laufen cdoder pwd, möchten einige Muscheln möglicherweise dagegen schützen.

In diesem speziellen Fall sehen Sie unterschiedliche Verhaltensweisen mit unterschiedlichen Muscheln.

Manche ksh93ignorieren das Problem vollständig und geben auch nach Ihrem Anruf falsche Informationen zurück cd(und Sie würden das Verhalten, das Sie bashdort beobachten, nicht sehen ).

Manche mögen bashoder zshüberprüfen, ob dies $PWDnoch ein Pfad zum aktuellen Verzeichnis ist cd, aber nicht pwd.

pdksh prüft beide pwdund cd(aber pwdaktualisiert nicht $PWD)

ash(zumindest die, die auf Debian gefunden) überprüft nicht, und wenn Sie das tun cd a, tut es tatsächlich cd "$PWD/a", so dass , wenn das aktuelle Verzeichnis geändert hat und $PWDnicht mehr auf dem aktuellen Verzeichnis, es wird sich ändern , eigentlich nicht auf das aVerzeichnis im aktuellen Verzeichnis , aber die in $PWD(und einen Fehler zurück, wenn es nicht existiert).

Wenn Sie damit spielen möchten, können Sie Folgendes tun:

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)

in verschiedenen Schalen.

In Ihrem Fall, da Sie bashnach a verwenden cd a, wird bashüberprüft, dass $PWDimmer noch auf das aktuelle Verzeichnis zeigt. Dazu ruft es stat()den Wert von $PWDauf, um seine Inode-Nummer zu überprüfen und mit der von zu vergleichen ..

Wenn jedoch beim Aufrufen des $PWDPfads zu viele Symlinks aufgelöst werden, wird stat()ein Fehler zurückgegeben, sodass die Shell nicht prüfen kann, ob sie $PWDnoch dem aktuellen Verzeichnis entspricht, und sie daher erneut mit berechnet getcwd()und entsprechend aktualisiert $PWD.

Um die Antwort von Patrice zu verdeutlichen, dient diese Überprüfung der Anzahl der beim Nachschlagen eines Pfads aufgetretenen Symlinks zum Schutz vor Symlink-Schleifen. Die einfachste Schleife kann mit gemacht werden

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

Ohne diesen sicheren Schutz cd a/xmüsste das System auf einen Fall herausfinden, wohin aLinks führen, wo es sich befindet, bund es ist ein Symlink, auf das Links führen a, und das würde auf unbestimmte Zeit weitergehen. Die einfachste Möglichkeit, sich dagegen zu schützen, besteht darin, nach dem Auflösen von mehr als einer willkürlichen Anzahl von Symlinks aufzugeben.

Nun zurück zum logischen aktuellen Arbeitsverzeichnis und warum es nicht so gut ist. Es ist wichtig zu wissen, dass dies nur für cddie Shell und nicht für andere Befehle gilt.

Zum Beispiel:

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

ist nicht immer dasselbe wie:

vi -- "$dir/$file"

Aus diesem Grund wird manchmal empfohlen, immer cd -PSkripte zu verwenden, um Verwirrung zu vermeiden (Sie möchten nicht, dass Ihre Software ein Argument behandelt, das sich ../xvon anderen Befehlen unterscheidet, nur weil es in Shell statt in einer anderen Sprache geschrieben ist).

Die -POption besteht darin, die Verarbeitung logischer Verzeichnisse zu deaktivieren , damit cd -P -- "$var"tatsächlich chdir()der Inhalt von aufgerufen wird $var(außer wenn dies der Fall $varist, -aber das ist eine andere Geschichte). Und nach ein cd -P, $PWDwird einen kanonischen Pfad enthält.


7
Gütiger Gott! Vielen Dank für eine so umfassende Antwort, es ist wirklich sehr interessant :)
Lucas

Super Antwort, vielen Dank! Ich habe das Gefühl, dass ich all diese Dinge irgendwie wusste, aber ich hatte nie verstanden oder darüber nachgedacht, wie sie alle zusammen kamen. Tolle Erklärung.
dimo414

42

Dies ist das Ergebnis eines fest programmierten Limits in der Linux-Kernelquelle. Denial-of-Service, die Begrenzung der Anzahl von verschachtelten symbolischen Links zu verhindern , ist 40 (in der gefundene follow_link()Funktion innen fs/namei.cdurch genannte nested_symlink()in der Kernel - Quelle).

Sie würden wahrscheinlich ein ähnliches Verhalten (und möglicherweise ein anderes Limit als 40) bei anderen Kerneln erhalten, die Symlinks unterstützen.


1
Gibt es einen Grund für das "Zurücksetzen", anstatt einfach anzuhalten. dh x%40eher als max(x,40). Ich denke, Sie können immer noch sehen, dass Sie das Verzeichnis geändert haben.
Lucas

4
Ein Link zur Quelle, für alle anderen Neugierigen: lxr.linux.no/linux+v3.9.6/fs/namei.c#L818
Ben
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.