El mount(2)
la llamada del sistema resolverá completamente sus rutas a través de montajes y enlaces simbólicos, pero a diferencia de open(2)
, no aceptará una ruta a un archivo eliminado, es decir, una ruta que se resuelva en una entrada de directorio no vinculada.
(similar al <filename> (deleted)
caminos de /proc/PID/fd/FD
, procfs mostrará dentries no vinculados como <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
Todo esto solía funcionar en kernels más antiguos, pero no desde la versión 4.19, introducida por primera vez con este cambio:
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);
+
Parece que este efecto no fue intencionado por el cambio. Desde entonces, se han acumulado otros cambios no relacionados, lo que lo confunde aún más.
Una consecuencia de esto es que también evita fijar un archivo eliminado en otro lugar del espacio de nombres a través de un fd abierto:
# 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
El último comando falla debido a la misma condición que los OP.
Incluso puedes volver a crear a
, apuntando al mismo inodo exacto, pero obtienes lo mismo
Es lo mismo que con /proc/PID/fd/FD
"enlaces simbólicos". El kernel es lo suficientemente inteligente como para seguir un archivo a través de cambios de nombre directos, pero no a través de 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 ...
Recorriendo el código fuente, encontré exactamente un ENOENT
que era relevante, es decir, para una entrada de directorio no vinculada:
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()
generalmente se aplica al objetivo, no a la fuente. En esta función, se llama debido a la propagación del montaje. Es necesario hacer cumplir la regla de que no puede agregar montajes encima de un archivo eliminado, durante la propagación del montaje. Pero la aplicación está ocurriendo con entusiasmo, incluso si no ocurre una propagación de montaje que requiera esto. Creo que es bueno que la verificación sea consistente como esta, solo que está codificada un poco más oscura de lo que preferiría idealmente.
De cualquier manera que lo mire, creo que es razonable hacer cumplir esto. Siempre y cuando ayude a reducir la cantidad de casos extraños para analizar y nadie tenga un contraargumento especialmente convincente.