符号链接递归-是什么使其“重置”?


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

这是怎么回事

Answers:


88

帕特里斯(Patrice)在他的回答中指出了问题的根源,但是如果您想知道如何从那里得到解决的原因,那么这就是长话了。

您认为进程的当前工作目录并不复杂。它是进程的一个属性,是对目录类型文件的句柄,该目录的相对路径(在该进程进行的系统调用中)从此处开始。解析相对路径时,内核不需要知道当前目录的完整路径,它只需读取该目录文件中的目录条目即可找到相对路径的第一部分(与..其他任何路径一样)文件),然后从那里继续。

现在,作为用户,您有时想知道该目录在目录树中的位置。对于大多数Unices,目录树是一棵没有循环的树。也就是说,从树的根(/)到任何给定文件只有一条路径。该路径通常称为规范路径。

为了得到当前工作目录,什么程序需要做的仅仅是步行的路径(也下来,如果你想看到它的根树的底部)树回到根,找到节点的名称在途中。

例如,尝试查找其当前目录为的进程/a/b/c将打开该..目录(相对路径,..当前目录中的条目也是如此),并查找与inode编号相同的目录类型的文件.,找出c匹配,然后打开../..,依此类推,直到找到/。那里没有歧义。

这就是getwd()getcwd()C函数的作用,或者至少是过去所要做的。

在诸如现代Linux之类的某些系统上,有一个系统调用将规范路径返回到当前目录,该路径在内核空间中进行查找(并且即使您没有对所有组件的读取权限,也可以找到当前目录) ,那就是getcwd()在那里。在现代Linux上,您还可以通过中的readlink()找到当前目录的路径/proc/self/cwd

这是大多数语言和早期Shell在将路径返回到当前目录时所做的事情。

你的情况,你可以调用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将完全不同。特别是,..in /a/b/c不一定是/a/b

在Bourne Shell中,如果您这样做:

cd /a/b/c
cd ..

甚至:

cd /a/b/c/..

无法保证您最终会进入/a/b

就像:

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

不一定与:

vi /a/b/d

ksh引入了逻辑当前工作目录的概念以某种方式解决该问题。人们习惯了它,POSIX最终指定了这种行为,这意味着当今大多数shell也会这样做:

对于cdpwd内置命令(并且仅适用于它们(尽管也适用于具有它们的shell上的popd/ pushd)),shell会维护自己对当前工作目录的想法。它存储在$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。在下次调用cdor之前pwd,可能会发生很多事情,的任何组件$PWD都可以重命名。当前目录永不更改(尽管可以删除,但始终是相同的inode),但是其在目录树中的路径可以完全更改。getcwd()每次通过走目录树来计算当前目录时,都会计算当前目录,因此其信息始终准确,但是对于POSIX Shell实现的逻辑目录,其中的信息$PWD可能会过时。因此,在运行cd或时pwd,某些外壳可能需要对此加以防范。

在该特定实例中,您会看到具有不同外壳的不同行为。

有些人喜欢ksh93完全忽略该问题,因此即使您致电也将返回错误的信息cd(并且您不会看到bash那里的行为)。

有些人喜欢bashzsh确实检查它$PWD仍然是当前目录的路径cd,但不是pwd

pdksh会同时检查pwdcd(但是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 abash将检查$PWD仍指向当前目录。为此,它调用stat()的值$PWD来检查其inode编号并将其与进行比较.

但是,当查找$PWD路径涉及解析太多符号链接时,stat()返回的错误将导致错误,因此Shell无法检查是否$PWD仍与当前目录相对应,因此它再次使用进行计算getcwd()并相应地进行更新$PWD

现在,为了阐明Patrice的答案,在查找路径时检查遇到的符号链接数是为了防止符号链接循环。最简单的循环可以用

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

没有安全防护,在上cd a/x,系统将不得不找到a链接的位置,找到它的位置,b并且是一个链接到的符号链接a,并且该链接将无限期地继续下去。防止这种情况发生的最简单方法是在解决了多个符号链接后就放弃了。

现在回到逻辑当前工作目录,以及为什么它不是一个很好的功能。重要的是要意识到它仅适用cd于shell,而不适用于其他命令。

例如:

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

并不总是与以下内容相同:

vi -- "$dir/$file"

这就是为什么您有时会发现人们建议始终cd -P在脚本中使用以避免混淆(您不希望您的软件../x仅使用Shell而不是另一种语言来编写与其他命令不同的参数)。

-P选项是禁用的逻辑目录处理这样cd -P -- "$var"实际上并调用chdir()上的内容$var(除非$var-但那是另一回事)。并且在之后cd -P$PWD将包含规范路径。


7
亲爱的耶稣!感谢您提供如此全面的答案,这真的很有趣:)
卢卡斯(Lucas)

很棒的答案,非常感谢!我觉得我有点了解所有这些东西,但是我从未理解或思考它们是如何结合在一起的。很好的解释。
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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.