Il mount(2)
la chiamata di sistema risolverà completamente i suoi percorsi tramite montaggi e collegamenti simbolici, ma a differenza di open(2)
, non accetterà un percorso a un file eliminato, ovvero un percorso che si risolve in una voce di directory non collegata.
(simile al <filename> (deleted)
percorsi di /proc/PID/fd/FD
, procfs visualizzerà le voci non collegate come <filename>//deleted
in /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
Tutto questo funzionava nei kernel più vecchi, ma non dalla v4.19, introdotta per la prima volta da questa modifica:
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);
+
Sembra che questo effetto non fosse previsto dal cambiamento. Da allora si sono accumulate altre modifiche non correlate, confondendolo ancora di più.
Una conseguenza di ciò è che impedisce anche di bloccare un file eliminato da qualche altra parte nello spazio dei nomi tramite un fd aperto ad esso:
# 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
L'ultimo comando fallisce a causa della stessa condizione degli OP.
Puoi persino ricreare
a
, puntando allo stesso esatto inode, ma ottieni la stessa cosa
È la stessa cosa di /proc/PID/fd/FD
"link simbolici". Il kernel è abbastanza intelligente da seguire un file attraverso rinominazioni dirette, ma non attraverso 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 ...
Scorrendo il codice sorgente, ho trovato esattamente un ENOENT
che era rilevante, ad es. per una voce di directory non collegata:
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()
viene generalmente applicato alla destinazione, non alla fonte. In questa funzione, viene chiamato a causa della propagazione del montaggio. È necessario applicare la regola secondo cui non è possibile aggiungere montaggi su un file eliminato, durante la propagazione del montaggio. Ma l'applicazione sta avvenendo con entusiasmo, anche se non si verifica alcuna propagazione del montaggio che lo richieda. Penso che sia positivo che il controllo sia coerente in questo modo, è solo codificato in modo un po' più oscuro di quanto preferirei idealmente.
Ad ogni modo, penso che sia ragionevole imporre questo. Finché aiuta a ridurre il numero di casi strani da analizzare e nessuno ha una controargomentazione particolarmente convincente.