]> www.infradead.org Git - users/hch/misc.git/commitdiff
umount_tree(): take all victims out of propagation graph at once
authorAl Viro <viro@zeniv.linux.org.uk>
Tue, 19 Aug 2025 16:22:03 +0000 (12:22 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Tue, 16 Sep 2025 01:26:44 +0000 (21:26 -0400)
For each removed mount we need to calculate where the slaves will end up.
To avoid duplicating that work, do it for all mounts to be removed
at once, taking the mounts themselves out of propagation graph as
we go, then do all transfers; the duplicate work on finding destinations
is avoided since if we run into a mount that already had destination found,
we don't need to trace the rest of the way.  That's guaranteed
O(removed mounts) for finding destinations and removing from propagation
graph and O(surviving mounts that have master removed) for transfers.

Reviewed-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/namespace.c
fs/pnode.c
fs/pnode.h

index 75856c7ce746bb10072ba721ae6e0e3867a4c67d..ae29cbf13e3951a9fa33bcc29e97787e5283adb7 100644 (file)
@@ -1846,6 +1846,8 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
        if (how & UMOUNT_PROPAGATE)
                propagate_umount(&tmp_list);
 
+       bulk_make_private(&tmp_list);
+
        while (!list_empty(&tmp_list)) {
                struct mnt_namespace *ns;
                bool disconnect;
@@ -1870,7 +1872,6 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
                                umount_mnt(p);
                        }
                }
-               change_mnt_propagation(p, MS_PRIVATE);
                if (disconnect)
                        hlist_add_head(&p->mnt_umount, &unmounted);
 
index edaf9d9d0eaf03c8fe74111f024d2085976faac1..5d91c3e58d2a35649627bb8210d06c06c9198e38 100644 (file)
@@ -71,19 +71,6 @@ static inline bool will_be_unmounted(struct mount *m)
        return m->mnt.mnt_flags & MNT_UMOUNT;
 }
 
-static struct mount *propagation_source(struct mount *mnt)
-{
-       do {
-               struct mount *m;
-               for (m = next_peer(mnt); m != mnt; m = next_peer(m)) {
-                       if (!will_be_unmounted(m))
-                               return m;
-               }
-               mnt = mnt->mnt_master;
-       } while (mnt && will_be_unmounted(mnt));
-       return mnt;
-}
-
 static void transfer_propagation(struct mount *mnt, struct mount *to)
 {
        struct hlist_node *p = NULL, *n;
@@ -112,11 +99,10 @@ void change_mnt_propagation(struct mount *mnt, int type)
                return;
        }
        if (IS_MNT_SHARED(mnt)) {
-               if (type == MS_SLAVE || !hlist_empty(&mnt->mnt_slave_list))
-                       m = propagation_source(mnt);
                if (list_empty(&mnt->mnt_share)) {
                        mnt_release_group_id(mnt);
                } else {
+                       m = next_peer(mnt);
                        list_del_init(&mnt->mnt_share);
                        mnt->mnt_group_id = 0;
                }
@@ -137,6 +123,57 @@ void change_mnt_propagation(struct mount *mnt, int type)
        }
 }
 
+static struct mount *trace_transfers(struct mount *m)
+{
+       while (1) {
+               struct mount *next = next_peer(m);
+
+               if (next != m) {
+                       list_del_init(&m->mnt_share);
+                       m->mnt_group_id = 0;
+                       m->mnt_master = next;
+               } else {
+                       if (IS_MNT_SHARED(m))
+                               mnt_release_group_id(m);
+                       next = m->mnt_master;
+               }
+               hlist_del_init(&m->mnt_slave);
+               CLEAR_MNT_SHARED(m);
+               SET_MNT_MARK(m);
+
+               if (!next || !will_be_unmounted(next))
+                       return next;
+               if (IS_MNT_MARKED(next))
+                       return next->mnt_master;
+               m = next;
+       }
+}
+
+static void set_destinations(struct mount *m, struct mount *master)
+{
+       struct mount *next;
+
+       while ((next = m->mnt_master) != master) {
+               m->mnt_master = master;
+               m = next;
+       }
+}
+
+void bulk_make_private(struct list_head *set)
+{
+       struct mount *m;
+
+       list_for_each_entry(m, set, mnt_list)
+               if (!IS_MNT_MARKED(m))
+                       set_destinations(m, trace_transfers(m));
+
+       list_for_each_entry(m, set, mnt_list) {
+               transfer_propagation(m, m->mnt_master);
+               m->mnt_master = NULL;
+               CLEAR_MNT_MARK(m);
+       }
+}
+
 static struct mount *__propagation_next(struct mount *m,
                                         struct mount *origin)
 {
index 00ab153e3e9d32408765614156a30be571a32c6f..b029db225f33c0f7754bc9179d88991ccf02ee91 100644 (file)
@@ -42,6 +42,7 @@ static inline bool peers(const struct mount *m1, const struct mount *m2)
 }
 
 void change_mnt_propagation(struct mount *, int);
+void bulk_make_private(struct list_head *);
 int propagate_mnt(struct mount *, struct mountpoint *, struct mount *,
                struct hlist_head *);
 void propagate_umount(struct list_head *);