我不明白为什么在取消链接后绑定安装时会出现 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
Run Code Online (Sandbox Code Playgroud)
这对我来说似乎是一个错误。您甚至可以重新创建“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
Run Code Online (Sandbox Code Playgroud)
世界正在发生什么?
该mount(2)系统调用将完全通过坐骑和符号连接解决其路径,但不像open(2),不会接受已删除的文件的路径,即解析为一个未链接的目录项的路径。
(类似<filename> (deleted)的路径/proc/PID/fd/FD,PROCFS将显示未链接的目录项为<filename>//deleted在/proc/PID/mountinfo)
# unshare -m
# echo foo > foo; touch bar baz quux
# mount -B foo bar
# mount -B bar baz
# grep foo /proc/self/mountinfo
56 38 8:7 /tmp/foo /tmp/bar ...
57 38 8:7 /tmp/foo /tmp/baz ...
# rm foo
# grep foo /proc/self/mountinfo
56 38 8:7 /tmp/foo//deleted /tmp/bar ...
57 38 8:7 /tmp/foo//deleted /tmp/baz ...
# mount -B baz quux
mount: mount(2) failed: /tmp/quux: No such file or directory
Run Code Online (Sandbox Code Playgroud)
所有这些过去都可以在较旧的内核中使用,但自 v4.19 以来就不再适用了,此更改首次引入:
commit 1064f874abc0d05eeed8993815f584d847b72486
Author: Eric W. Biederman <ebiederm@xmission.com>
Date: Fri Jan 20 18:28:35 2017 +1300
mnt: Tuck mounts under others instead of creating shadow/side mounts.
...
+ /* Preallocate a mountpoint in case the new mounts need
+ * to be tucked under other mounts.
+ */
+ smp = get_mountpoint(source_mnt->mnt.mnt_root);
+ if (IS_ERR(smp))
+ return PTR_ERR(smp);
+
Run Code Online (Sandbox Code Playgroud)
看起来这种效果是变化所无意的。从那以后,其他不相关的变化层出不穷,更加令人困惑。
这样做的结果是,它还可以防止通过打开的 fd 将已删除的文件固定在命名空间中的其他位置:
# exec 7>foo; touch bar
# rm foo
# mount -B /proc/self/fd/7 bar
mount: mount(2) failed: /tmp/bar: No such file or directory
Run Code Online (Sandbox Code Playgroud)
由于与 OP 的条件相同,最后一个命令失败。
你甚至可以重新创建
a,指向相同的 inode,但你得到同样的东西
这与/proc/PID/fd/FD“符号链接”相同。内核足够聪明,可以通过直接重命名来跟踪文件,但不能通过ln+ rm( link(2)+ unlink(2)):
# unshare -m
# echo foo > foo; touch bar baz
# mount -B foo bar
# mount -B bar baz
# grep foo /proc/self/mountinfo
56 38 8:7 /tmp/foo /tmp/bar ...
57 38 8:7 /tmp/foo /tmp/baz ...
# mv foo quux
# grep bar /proc/self/mountinfo
56 38 8:7 /tmp/quux /tmp/bar ...
# ln quux foo; rm quux
# grep bar /proc/self/mountinfo
56 38 8:7 /tmp/quux//deleted /tmp/bar ...
Run Code Online (Sandbox Code Playgroud)
浏览源代码,我找到了一个ENOENT相关的代码,即未链接的目录条目:
static int attach_recursive_mnt(struct mount *source_mnt,
struct mount *dest_mnt,
struct mountpoint *dest_mp,
struct path *parent_path)
{
[...]
/* Preallocate a mountpoint in case the new mounts need
* to be tucked under other mounts.
*/
smp = get_mountpoint(source_mnt->mnt.mnt_root);
Run Code Online (Sandbox Code Playgroud)
static struct mountpoint *get_mountpoint(struct dentry *dentry)
{
struct mountpoint *mp, *new = NULL;
int ret;
if (d_mountpoint(dentry)) {
/* might be worth a WARN_ON() */
if (d_unlinked(dentry))
return ERR_PTR(-ENOENT);
Run Code Online (Sandbox Code Playgroud)
https://elixir.bootlin.com/linux/v5.2/source/fs/namespace.c#L3100
get_mountpoint()通常应用于目标,而不是源。在此函数中,由于挂载传播而调用它。有必要强制执行这样的规则:在挂载传播期间,您不能在已删除的文件之上添加挂载。但强制执行正在急切地进行,即使没有发生需要这样做的挂载传播。我认为检查像这样保持一致是件好事,只是编码比我理想中的更晦涩一些。
不管怎样,我认为强制执行这一点是合理的。只要它有助于减少需要分析的奇怪案例的数量,并且没有人有特别令人信服的反驳论点。