Символічна рекурсія зв'язку - що робить її “скиданням”?


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 дерево каталогів - це дерево, без циклу. Тобто, є лише один шлях від кореня дерева ( /) до будь-якого файлу. Цей шлях взагалі називають канонічним шляхом.

Щоб отримати шлях до поточного робочого каталогу, потрібно зробити лише процес вгору (добре вниз, якщо ви хочете бачити дерево з його коренем внизу) дерево назад до кореня, знаходячи назви вузлів на шляху.

Наприклад, процес, який намагається з'ясувати, що це його поточний каталог /a/b/c, відкриє ..каталог (відносний шлях, такий ..є запис у поточному каталозі) та шукатиме файл типу каталогів з тим самим номером вводу ., що і з'ясувати, що cзбігається, потім відкривається ../..і так далі, поки не знайде /. Там немає двозначності.

Ось що роблять або, принаймні, раніше виконують функції getwd()або getcwd()C.

У деяких системах, таких як сучасний Linux, є системний виклик, щоб повернути канонічний шлях до поточного каталогу, який здійснює пошук у просторі ядра (і дозволяє знайти поточний каталог, навіть якщо ви не маєте доступу для читання до всіх його компонентів) і ось що getcwd()дзвонить туди. У сучасному Linux ви також можете знайти шлях до поточного каталогу через readlink () на /proc/self/cwd.

Саме це робить більшість мов та ранніх оболонок, повертаючи шлях до поточного каталогу.

У вашому випадку, ви можете зателефонувати , cd aяк може раз , як ви хочете, тому що це символьне посилання ., поточний каталог не змінюється , так все getcwd(), pwd -P, python -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.

У оболонці Борна, якщо ви робите:

cd /a/b/c
cd ..

Або навіть:

cd /a/b/c/..

Немає гарантій, що ти опинишся /a/b.

Так як:

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

не обов'язково те саме, що:

vi /a/b/d

kshввели концепцію логічного поточного робочого каталогу, щоб якось обійти це. Люди звикли до цього, і POSIX в кінці кінців уточнив таку поведінку, яка означає, що більшість снарядів і сьогодні це роблять:

Для команд cdі pwdвбудованих команд ( і лише для них (хоча також для 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може бути перейменований. Поточний каталог ніколи не змінюється (це завжди той самий inode, хоча його можна видалити), але його шлях у дереві каталогів може повністю змінитися. 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каталог у поточному каталозі , але один в $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перевірити його номер inode та порівняти його зі значенням ..

Але коли пошук $PWDшляху передбачає вирішення занадто багато символьних посилань, це stat()повертається з помилкою, тому оболонка не може перевірити, чи $PWDвсе ще відповідає поточному каталогу, тому він обчислює його ще раз getcwd()і оновлює $PWDвідповідно.

Тепер, щоб уточнити відповідь Патріса, перевірка кількості посилань, що виникають під час пошуку шляху, полягає у захисті від циклів символьних посилань. Найпростішу петлю можна зробити за допомогою

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

Без цього безпечного охоронного cd a/xпристрою система повинна буде знаходити, де aпосилається, знаходить це bі є символьним посиланням, на яке посилається a, і це буде тривати нескінченно. Найпростіший спосіб уберегтися від цього - відмовитися після вирішення більше ніж довільну кількість символьних посилань.

Тепер повернемося до логічного поточного робочого каталогу і чому це не така гарна функція. Важливо усвідомити, що це лише для 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 (знайдене у follow_link()функції всередині fs/namei.c, викликається nested_symlink()в джерелі ядра).

Ви, ймовірно, отримаєте подібну поведінку (і, можливо, інший ліміт, ніж 40) з іншими ядрами, що підтримують символьні посилання.


1
Чи є причина, щоб він "скинувся", а не просто зупинився. тобто, x%40а не max(x,40). Я думаю, ви все ще можете побачити, що ви змінили каталог.
Лукас

4
Посилання на джерело для всіх, хто цікавиться: lxr.linux.no/linux+v3.9.6/fs/namei.c#L818
Бен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.