Le mount(2)
l'appel système résoudra complètement ses chemins via les montages et les liens symboliques, mais contrairement à open(2)
, n'acceptera pas de chemin vers un fichier supprimé, c'est-à-dire un chemin qui se résout en une entrée de répertoire non liée.
(similaire au <filename> (deleted)
chemins de /proc/PID/fd/FD
, procfs affichera les dentries non liées sous la forme <filename>//deleted
en /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
Tout cela fonctionnait dans les noyaux plus anciens, mais plus depuis la v4.19, introduite pour la première fois par ce changement :
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);
+
Il semble que cet effet n'était pas voulu par le changement. Depuis lors, d'autres changements sans rapport se sont accumulés, le rendant encore plus confus.
Une conséquence de cela est qu'il empêche également d'épingler un fichier supprimé ailleurs dans l'espace de noms via un fd ouvert :
# 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
La dernière commande échoue à cause de la même condition que celle de l'OP.
Vous pouvez même recréer
a
, pointant vers le même inode exact, mais vous obtenez la même chose
C'est la même chose qu'avec /proc/PID/fd/FD
"liens symboliques". Le noyau est assez intelligent pour suivre un fichier par des renommages directs, mais pas par 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 ...
En parcourant le code source, j'ai trouvé exactement un ENOENT
qui était pertinent, c'est-à-dire pour une entrée d'annuaire non liée :
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()
est généralement appliqué à la cible, pas à la source. Dans cette fonction, elle est appelée en raison de la propagation de montage. Il est nécessaire d'appliquer la règle selon laquelle vous ne pouvez pas ajouter de montages au-dessus d'un fichier supprimé, lors de la propagation du montage. Mais l'application se fait avec impatience, même si aucune propagation de montage ne se produit, ce qui nécessiterait cela. Je pense que c'est bien que la vérification soit cohérente comme celle-ci, elle est juste codée un peu plus obscurément que je ne le préférerais idéalement.
Quoi qu'il en soit, je pense qu'il est raisonnable d'appliquer cela. Tant que cela aide à réduire le nombre de cas étranges à analyser, et que personne n'a de contre-argument particulièrement convaincant.