Рекурсия символической ссылки - что делает ее «перезагрузкой»?

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.

В оболочке Bourne, если вы делаете:

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/ pushdon оболочек, у которых они есть)) оболочка поддерживает собственное представление о текущем рабочем каталоге. Он хранится в $PWDспециальной переменной.

Когда вы делаете:

cd c/d

даже если cили c/dявляются символическими ссылками, в то время как $PWDсодержит /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может быть переименован. Текущий каталог никогда не меняется (это всегда один и тот же индекс, хотя его можно удалить), но его путь в дереве каталогов может полностью измениться. 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, после a cd a, bashпроверяет, что $PWDвсе еще указывает на текущий каталог. Для этого он вызывает stat()значение, $PWDчтобы проверить его номер инода и сравнить его с номером ..

Но когда поиск $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
Бен