我不明白为什么在取消链接后绑定挂载时会得到 ENOENT:
kduda@penguin:/tmp$ echo hello > a
kduda@penguin:/tmp$ touch b c
kduda@penguin:/tmp$ sudo unshare -m
root@penguin:/tmp# mount -B a b
root@penguin:/tmp# rm a
root@penguin:/tmp# cat b
hello
root@penguin:/tmp# mount -B b c
mount: mount(2) failed: No such file or directory
这对我来说似乎是一个错误。你甚至可以重新创建“a”,指向同一个确切的 inode,但你得到的是同样的东西:
kduda@penguin:/tmp$ echo hello > a
kduda@penguin:/tmp$ ln a a-save
kduda@penguin:/tmp$ sudo unshare -m
root@penguin:/tmp# mount -B a b
root@penguin:/tmp# rm a
root@penguin:/tmp# ln a-save a
root@penguin:/tmp# mount -B b c
mount: mount(2) failed: No such file or directory
世界上到底发生了什么?
系统调用将
mount(2)
通过挂载和符号链接完全解析其路径,但与 不同open(2)
的是,它不会接受已删除文件的路径,即解析为未链接目录条目的路径。(类似于 的
<filename> (deleted)
路径/proc/PID/fd/FD
,procfs 将显示未链接的目录,<filename>//deleted
如/proc/PID/mountinfo
)所有这些过去都可以在较旧的内核中使用,但自 v4.19 以来就没有了,首先由此更改引入:
看起来这种影响是无意的变化。从那以后,其他不相关的变化不断涌现,更加混乱。
其结果是它还可以防止通过打开的 fd 将已删除的文件固定在命名空间中的其他位置:
由于与 OP 相同的条件,最后一个命令失败。
这与
/proc/PID/fd/FD
“符号链接”相同。内核足够聪明,可以通过直接重命名来跟踪文件,但不能通过ln
+rm
(link(2)
+unlink(2)
):浏览源代码,我找到了一个
ENOENT
相关的,即未链接的目录条目:https://elixir.bootlin.com/linux/v5.2/source/fs/namespace.c#L3100
get_mountpoint()
通常应用于目标,而不是源。在这个函数中,它是因为挂载传播而被调用的。在挂载传播期间,必须强制执行不能在已删除文件之上添加挂载的规则。但是,即使没有发生需要这样做的挂载传播,强制执行也很迫切。我认为像这样检查是一致的很好,它只是编码比我理想的情况要模糊一些。无论哪种方式,我认为执行此操作是合理的。只要它有助于减少要分析的奇怪案例的数量,没有人有特别令人信服的反驳论点。
一个综合的答案是:要理解三件事,然后这一切就说得通了。
首先,绑定挂载的来源是dentry,而不是 inode。也就是说,您不会在名称上绑定安装 inode;您将一个牙签绑定到另一个牙签上。要查看差异,请查看如果将不同的链接挂载到同一个 inode 会发生什么;挂载是不同的,因为源目录不同,即使 inode 相同:
要理解的第二件事是,当您挂载本身就是早期绑定挂载的目标的东西时,它与绑定挂载的源是同一个 dentry 对象(这就是绑定挂载;一个 dentry 在另一个之上。)因此,如果
a1
是 mount onb1
,则 mountb1
on与 mount onc1
完全相同,因为名称和指的是同一个目录。a1
c1
a1
b1
要理解的第三件事是内核禁止绑定挂载已删除的目录,因为......我看不出有什么好的理由。似乎针对挂载目标的错误检查(防止挂载到已删除的目录上,这是没有意义的,因为您永远无法引用新挂载)对挂载源应用没有充分的理由也是。这就是这里的代码:
这三个事实的结果是(继续上面的 shell 会话)如果
ENOENT
被删除:b2
c2
a2
这让我觉得这是一个错误,因为如果您在安装后删除 a2,您的 b2-on-c2 安装是有效的,并且顺序无关紧要:在某物上安装已删除的 dentry 是合法的还是不合法的,它不应该它何时被删除无关紧要。然而,理性的人不同意。
感谢大家。