リンクを解除した後にマウントをバインドするときに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
これは私にとってバグのようです。同じinodeを指す「a」を再生成することもできますが、同じ結果が得られます。
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
世界に何が起こっていますか?
答え1
システム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
これらの機能はすべて以前のカーネルで動作しましたが、v4.19以降は動作しなくなりました。この変化:
commit 1064f874abc0d05eeed8993815f584d847b72486
Author: Eric W. Biederman <[email protected]>
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);
+
この効果が意図したすべてを変えないようです。その他関連のない事項多様性山が積み重なり、さらに混乱します。
これの結果は、削除されたファイルが開かれた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
OPと同じ状況のため、最後のコマンドが失敗しました。
まったく同じ inode を指して再生成することもできますが、
a
同じ結果が得られます。
これは「シンボリックリンク」と同じです/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 ...
答え2
ソースコードを調べて、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);
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);
https://elixir.bootlin.com/linux/v5.2/source/fs/namespace.c#L3100
get_mountpoint()
通常、ソースではなくターゲットに適用されます。この関数では、マウント伝播のために呼び出されます。マウントの伝播中は、削除されたファイルの上にマウントを追加できないという規則を適用する必要があります。しかし、これを要求するマウント電波が発生しないにもかかわらず施行は懸命に進んでいます。一貫性のため、このようなことを確認するのが良いと思います。コーディングは私が理想的に望むものよりも少し曖昧であるということです。
それにもかかわらず、私はこれを実施するのが合理的だと思います。分析すべき奇妙なケースの数を減らすのに役立ち、誰も特に説得力のある反論をすることができなければです。
答え3
包括的な答えは次のとおりです。すべてが理解される前に、3つを理解する必要があります。
まず、バインドインストールのソースは次のとおりです。ディレクトリエントリ、インデックスノードではありません。つまり、名前にinodeをバインドせずに名前にバインドします。あるカタログ項目を別のカタログ項目にバインドマウントします。違いを確認するには、同じinodeに別のリンクをマウントすると何が起こるのかを見てください。その他、 inode が同じであってもソース dentry が異なるためです。
root@penguin:/tmp# echo hello > a1
root@penguin:/tmp# ln a1 a2
root@penguin:/tmp# touch b1 b2
root@penguin:/tmp# mount -B a1 b1
root@penguin:/tmp# mount -B a2 b2
root@penguin:/tmp# ls -li a1 a2 b1 b2
9552271 -rw-r--r-- 2 root root 6 Aug 25 05:16 a1
9552271 -rw-r--r-- 2 root root 6 Aug 25 05:16 a2
9552271 -rw-r--r-- 2 root root 6 Aug 25 05:16 b1
9552271 -rw-r--r-- 2 root root 6 Aug 25 05:16 b2
root@penguin:/tmp# grep /tmp/ /proc/self/mountinfo
421 364 0:38 /lxd/.../rootfs/tmp/a1 /tmp/b1 rw,...
422 364 0:38 /lxd/.../rootfs/tmp/a2 /tmp/b2 rw,...
2番目に理解する必要があるのは、それ自体が初期バインドマウントのターゲットであることをインストールするときにバインドマウントのソースと同じディレクトリエントリオブジェクトであることです(これはバインドマウントです。あるディレクトリエントリが別のディレクトリエントリを上書きします。ディレクトリエントリ)。 ) したがって、a1
にマウントされている場合、名前と参照は同じディレクトリエントリであるため、b1
Mounting b1
onはMounting onとまったく同じです。c1
a1
c1
a1
b1
3番目に理解する必要があるのは、カーネルが削除されたディレクトリエントリをマウントするバインドを無効にすることです。なぜなら…妥当な理由は見えません。エラーチェックの目的は次のとおりです。ターゲットマウント(削除されたディレクトリエントリへのマウントを防ぐために新しいマウントを参照できないため、そうする必要はありません)が正当な理由なく要求されました。源泉マウントも同様です。ここにあるコードは次のとおりです。
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);
これら3つの事実の結果は、ifが削除されたときにマウントされることです(上記のシェルセッションを続行します)ENOENT
。b2
c2
a2
root@penguin:/tmp# touch c1 c2
root@penguin:/tmp# rm a2
root@penguin:/tmp# mount -B b1 c1
root@penguin:/tmp# mount -B b2 c2
mount: mount(2) failed: /tmp/c2: No such file or directory
root@penguin:/tmp#
これは、インストール後にa2を削除すると、b2-on-c2のインストールが有効で順序が重要ではないため、これがバグだと思うようにします。削除されたディレクトリエントリを何かにインストールするのは正当であるか、違法であり、インストールする必要があります。いつ削除しても構いません。しかし、合理的な人々はこれに同意しません。
みんなありがとうございます。