Der mount(2)
Der Systemaufruf wird seine Pfade vollständig durch Mounts und Symlinks auflösen, aber im Gegensatz zu open(2)
, akzeptiert keinen Pfad zu einer gelöschten Datei, dh einen Pfad, der sich in einen nicht verknüpften Verzeichniseintrag auflöst.
(ähnlich dem <filename> (deleted)
Pfade von /proc/PID/fd/FD
, procfs zeigt nicht verknüpfte Dentries als <filename>//deleted
an 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
All dies funktionierte früher in älteren Kerneln, funktioniert aber seit v4.19 nicht mehr, was erstmals durch diese Änderung eingeführt wurde:
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);
+
Es sieht so aus, als ob dieser Effekt durch die Änderung unbeabsichtigt war. Seitdem haben sich andere, nicht zusammenhängende Änderungen angehäuft, die es noch mehr verwirren.
Eine Folge davon ist, dass es auch verhindert, eine gelöschte Datei irgendwo anders im Namensraum über ein offenes fd darauf zu pinnen:
# 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
Der letzte Befehl schlägt aufgrund der gleichen Bedingung wie die des OP fehl.
Sie können sogar a
neu erstellen , die auf genau denselben Inode zeigen, aber Sie erhalten dasselbe
Es ist dasselbe wie bei /proc/PID/fd/FD
"Symlinks". Der Kernel ist intelligent genug, um einer Datei durch direkte Umbenennungen zu folgen, aber nicht durch 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 ...
Als ich den Quellcode durchging, fand ich genau eine ENOENT
das war relevant, also für einen unverlinkten Verzeichniseintrag:
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()
wird im Allgemeinen auf das Ziel angewendet, nicht auf die Quelle. In dieser Funktion wird sie wegen Mount-Propagation aufgerufen. Es ist notwendig, die Regel durchzusetzen, dass Sie während der Mount-Propagierung keine Mounts über einer gelöschten Datei hinzufügen können. Aber die Durchsetzung geschieht eifrig, auch wenn keine Mount-Propagation stattfindet, die dies erfordern würde. Ich finde es gut, dass die Überprüfung so konsistent ist, sie ist nur etwas undurchsichtiger codiert, als ich es idealerweise vorziehen würde.
Wie auch immer ich es sehe, ich denke, es ist vernünftig, dies durchzusetzen. Solange es hilft, die Anzahl der zu analysierenden seltsamen Fälle zu reduzieren, und niemand ein besonders überzeugendes Gegenargument hat.