Symboliczna rekurencja łącza - co powoduje, że „resetuje się”?


64

Napisałem mały skrypt bash, aby zobaczyć, co się stanie, gdy będę podążał za dowiązaniem symbolicznym prowadzącym do tego samego katalogu. Spodziewałem się, że albo utworzy bardzo długi katalog roboczy, albo się zawiesi. Ale wynik mnie zaskoczył ...

mkdir a
cd a

ln -s ./. a

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

Niektóre dane wyjściowe to

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

co tu się dzieje?

Odpowiedzi:


88

Patrice zidentyfikował źródło problemu w swojej odpowiedzi , ale jeśli chcesz dowiedzieć się, jak się stąd dowiedzieć, dlaczego to robisz, oto długa historia.

Bieżący katalog roboczy procesu nie jest niczym zbyt skomplikowanym. Jest to atrybut procesu będący dojściem do pliku katalogu typu, od którego zaczynają się ścieżki względne (w wywołaniach systemowych wykonywanych przez proces). Podczas rozwiązywania ścieżki względnej jądro nie musi znać (a) pełnej ścieżki do bieżącego katalogu, po prostu odczytuje pozycje katalogu w tym pliku katalogu, aby znaleźć pierwszy składnik ścieżki względnej (i ..jest jak każdy inny w tym zakresie) i kontynuuje od tego momentu.

Teraz, jako użytkownik, czasami lubisz wiedzieć, gdzie ten katalog znajduje się w drzewie katalogów. W większości Unices drzewo katalogów jest drzewem bez pętli. Oznacza to, że istnieje tylko jedna ścieżka od katalogu głównego tree ( /) do dowolnego podanego pliku. Ścieżka ta jest ogólnie nazywana ścieżką kanoniczną.

Aby uzyskać ścieżkę do bieżącego katalogu roboczego, proces musi po prostu pójść w górę (dobrze w dół, jeśli chcesz zobaczyć drzewo z jego korzeniem u dołu), drzewo z powrotem do katalogu głównego, znajdując nazwy węzłów w drodze.

Na przykład proces próbujący dowiedzieć się, że jego bieżącym katalogiem jest /a/b/c, otworzyłby ..katalog (ścieżka względna, podobnie ..jak wpis w bieżącym katalogu) i szukałby katalogu typu o tym samym numerze i-węzła, jak ., aby dowiedzieć się, że cdopasowuje, a następnie otwiera ../..i tak dalej, aż znajdzie /. Nie ma w tym dwuznaczności.

To właśnie robią lub przynajmniej robią funkcje getwd()lub getcwd()C.

W niektórych systemach, takich jak współczesny Linux, istnieje wywołanie systemowe, które zwraca kanoniczną ścieżkę do bieżącego katalogu, który wykonuje to wyszukiwanie w przestrzeni jądra (i pozwala znaleźć twój bieżący katalog, nawet jeśli nie masz dostępu do odczytu wszystkich jego składników) i to właśnie getcwd()tam wzywa. W nowoczesnym systemie Linux można również znaleźć ścieżkę do bieżącego katalogu za pomocą readlink () na /proc/self/cwd.

Tak właśnie robi większość języków i wczesnych powłok, zwracając ścieżkę do bieżącego katalogu.

W twoim przypadku można nazwać cd aponieważ może razy, ile chcesz, bo to dowiązanie do ., bieżący katalog nie zmienia się więc wszyscy getcwd(), pwd -P, python -c 'import os; print os.getcwd()', perl -MPOSIX -le 'print getcwd'by przywrócić ${HOME}.

Teraz dowiązania symboliczne skomplikowały to wszystko.

symlinkszezwól na skoki w drzewie katalogów. W /a/b/c, jeśli /alub /a/blub /a/b/cjest dowiązaniem symbolicznym, wówczas kanoniczna ścieżka /a/b/cbyłaby czymś zupełnie innym. W szczególności ..wpis /a/b/cnie jest konieczny /a/b.

W powłoce Bourne'a, jeśli:

cd /a/b/c
cd ..

Lub nawet:

cd /a/b/c/..

Nie ma gwarancji, że skończysz /a/b.

Tak jak:

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

niekoniecznie jest taki sam jak:

vi /a/b/d

kshwprowadził koncepcję logicznego bieżącego katalogu roboczego, aby jakoś obejść ten problem. Ludzie się do tego przyzwyczaili, a POSIX ostatecznie określił takie zachowanie, co oznacza, że ​​większość powłok również to robi:

Dla cdi pwdwbudowanych poleceń ( i tylko dla nich (choć także dla popd/ pushdna muszli, które mają je)), powłoka utrzymuje swój własny pomysł bieżącego katalogu roboczego. Jest przechowywany w $PWDspecjalnej zmiennej.

Kiedy to zrobisz:

cd c/d

nawet jeśli club c/dsą dowiązaniami symbolicznymi, podczas gdy $PWDzawiera /a/b, dołącza c/dsię do końca, tak $PWDstaje się /a/b/c/d. A kiedy to zrobisz:

cd ../e

Zamiast robić chdir("../e"), to robi chdir("/a/b/c/e").

A pwdpolecenie zwraca tylko zawartość $PWDzmiennej.

Jest to przydatne w interaktywnych powłokach, ponieważ pwdwyświetla ścieżkę do bieżącego katalogu, która zawiera informacje o tym, jak się tam dostałeś, i dopóki używasz tylko ..argumentów, cda nie innych poleceń, rzadziej cię zaskoczy, ponieważ cd a; cd ..lub cd a/..ogólnie by cię odzyskał gdzie byłeś

Teraz $PWDnie jest modyfikowany, chyba że wykonasz cd. Do czasu następnego połączenia cdlub pwdwielu rzeczy może się zdarzyć, $PWDmożna zmienić nazwę dowolnego elementu . Bieżący katalog nigdy się nie zmienia (zawsze jest to ten sam i-węzeł, chociaż można go usunąć), ale jego ścieżka w drzewie katalogów może ulec całkowitej zmianie. getcwd()oblicza bieżący katalog za każdym razem, gdy jest wywoływany, idąc po drzewie katalogów, dzięki czemu jego informacje są zawsze dokładne, ale w przypadku katalogu logicznego implementowanego przez powłoki POSIX, informacje w nim $PWDmogą stać się nieaktualne. Więc po uruchomieniu cdlub pwdniektóre pociski mogą chcieć się przed tym uchronić.

W tym konkretnym przypadku widać różne zachowania z różnymi powłokami.

Niektórzy lubią ksh93całkowicie zignorować problem, więc zwrócą nieprawidłowe informacje nawet po zadzwonieniu cd(i nie zobaczylibyśmy takiego zachowania bash).

Niektórzy lubią bashlub zshzrobić sprawdzić, czy $PWDnadal jest ścieżka do katalogu bieżącym momencie cd, ale nie po pwd.

pdksh sprawdza zarówno pwdi cd(ale pwdnie aktualizuje $PWD)

ash(przynajmniej ten znaleziony w Debianie) nie sprawdza, a kiedy to robisz cd a, faktycznie tak się dzieje cd "$PWD/a", więc jeśli bieżący katalog się zmienił i $PWDnie wskazuje już na bieżący katalog, faktycznie nie zmieni się na akatalog w bieżącym katalogu , ale ten w $PWD(i zwraca błąd, jeśli nie istnieje).

Jeśli chcesz się nim bawić, możesz:

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)

w różnych skorupkach.

W twoim przypadku, ponieważ używasz bashpo cd a, bashsprawdza, które $PWDnadal wskazują na bieżący katalog. W tym celu wywołuje stat()wartość $PWDsprawdzania numeru i-węzła i porównywania go z wartością ..

Ale gdy wyszukiwanie $PWDścieżki wiąże się z rozwiązaniem zbyt wielu dowiązań symbolicznych, to stat()zwraca błąd, więc powłoka nie może sprawdzić, czy $PWDnadal odpowiada bieżącemu katalogowi, więc oblicza go ponownie getcwd()i $PWDodpowiednio aktualizuje .

Teraz, aby wyjaśnić odpowiedź Patrice, sprawdzenie liczby napotkanych dowiązań symbolicznych podczas wyszukiwania ścieżki ma na celu ochronę przed pętlami dowiązań symbolicznych. Najprostszą pętlę można wykonać

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

Bez tej bezpiecznej ochrony cd a/xsystem musiałby znaleźć miejsce, do którego prowadzi alink, znajduje go bi jest dowiązaniem symbolicznym, do którego prowadzi a, i który trwałby bez końca. Najprostszym sposobem, aby się temu zapobiec, jest poddanie się po rozwiązaniu więcej niż dowolnej liczby dowiązań symbolicznych.

Wróćmy do logicznego bieżącego katalogu roboczego i dlaczego nie jest to tak dobra funkcja. Ważne jest, aby zdawać sobie sprawę, że to tylko dla cdpowłoki, a nie innych poleceń.

Na przykład:

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

nie zawsze jest taki sam jak:

vi -- "$dir/$file"

Dlatego czasami zdarza się, że ludzie zalecają zawsze używać cd -Pskryptów, aby uniknąć zamieszania (nie chcesz, aby twoje oprogramowanie obsługiwało argumenty ../xodmienne od innych poleceń tylko dlatego, że jest napisane w powłoce zamiast w innym języku).

-PRozwiązaniem jest wyłączenie katalog logiczny obsługi tak cd -P -- "$var"faktycznie nie zadzwonić chdir()na treść $var(z wyjątkiem, gdy $varjest -ale to inna historia). A po cd -P, $PWDbędzie zawierać kanoniczną ścieżkę.


7
Słodki Jezu! Dzięki za tak wyczerpującą odpowiedź, to naprawdę bardzo interesujące :)
Lucas

Świetna odpowiedź, wielkie dzięki! Czuję się trochę znał wszystkie te rzeczy, ale nigdy nie zrozumiał ani myślał o tym, jak oni wszyscy przyszli razem. Świetne wyjaśnienie.
dimo414

42

Jest to wynik zakodowanego na stałe limitu w źródle jądra Linux; Aby zapobiec odmowie usługi, limit liczby zagnieżdżonych dowiązań symbolicznych wynosi 40 (znajduje się w follow_link()funkcji wewnątrz fs/namei.c, wywoływanej przez nested_symlink()źródło jądra).

Prawdopodobnie uzyskałbyś podobne zachowanie (i prawdopodobnie inny limit niż 40) z innymi jądrami obsługującymi dowiązania symboliczne.


1
Czy istnieje powód, aby „zresetować”, a nie tylko zatrzymać. tzn. x%40raczej niż max(x,40). Chyba nadal widać, że zmieniłeś katalog.
Lucas

4
Link do źródła, dla każdego innego ciekawego: lxr.linux.no/linux+v3.9.6/fs/namei.c#L818
Ben
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.