]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
vfs: add notifications for mount attach and detach
authorMiklos Szeredi <mszeredi@redhat.com>
Wed, 29 Jan 2025 16:58:01 +0000 (17:58 +0100)
committerChristian Brauner <brauner@kernel.org>
Wed, 5 Feb 2025 16:21:11 +0000 (17:21 +0100)
Add notifications for attaching and detaching mounts to fs/namespace.c

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Link: https://lore.kernel.org/r/20250129165803.72138-4-mszeredi@redhat.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/mount.h
fs/namespace.c
fs/pnode.c

index 5324a931b4034eb438b34a2d7882920b5e5ae6a3..946dc8b792d7d727b89a741373a08ed291cd2813 100644 (file)
@@ -5,6 +5,8 @@
 #include <linux/ns_common.h>
 #include <linux/fs_pin.h>
 
+extern struct list_head notify_list;
+
 struct mnt_namespace {
        struct ns_common        ns;
        struct mount *  root;
@@ -80,6 +82,8 @@ struct mount {
 #ifdef CONFIG_FSNOTIFY
        struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
        __u32 mnt_fsnotify_mask;
+       struct list_head to_notify;     /* need to queue notification */
+       struct mnt_namespace *prev_ns;  /* previous namespace (NULL if none) */
 #endif
        int mnt_id;                     /* mount identifier, reused */
        u64 mnt_id_unique;              /* mount ID unique until reboot */
@@ -182,4 +186,20 @@ static inline struct mnt_namespace *to_mnt_ns(struct ns_common *ns)
        return container_of(ns, struct mnt_namespace, ns);
 }
 
+#ifdef CONFIG_FSNOTIFY
+static inline void mnt_notify_add(struct mount *m)
+{
+       /* Optimize the case where there are no watches */
+       if ((m->mnt_ns && m->mnt_ns->n_fsnotify_marks) ||
+           (m->prev_ns && m->prev_ns->n_fsnotify_marks))
+               list_add_tail(&m->to_notify, &notify_list);
+       else
+               m->prev_ns = m->mnt_ns;
+}
+#else
+static inline void mnt_notify_add(struct mount *m)
+{
+}
+#endif
+
 struct mnt_namespace *mnt_ns_from_dentry(struct dentry *dentry);
index bc77b2e0130dce1c9326185bc0ec9b438735120b..05f6680e2251f57984b876b0ad8d2963ab89324d 100644 (file)
@@ -81,6 +81,9 @@ static HLIST_HEAD(unmounted); /* protected by namespace_sem */
 static LIST_HEAD(ex_mountpoints); /* protected by namespace_sem */
 static DEFINE_SEQLOCK(mnt_ns_tree_lock);
 
+#ifdef CONFIG_FSNOTIFY
+LIST_HEAD(notify_list); /* protected by namespace_sem */
+#endif
 static struct rb_root mnt_ns_tree = RB_ROOT; /* protected by mnt_ns_tree_lock */
 static LIST_HEAD(mnt_ns_list); /* protected by mnt_ns_tree_lock */
 
@@ -163,6 +166,7 @@ static void mnt_ns_release(struct mnt_namespace *ns)
 {
        /* keep alive for {list,stat}mount() */
        if (refcount_dec_and_test(&ns->passive)) {
+               fsnotify_mntns_delete(ns);
                put_user_ns(ns->user_ns);
                kfree(ns);
        }
@@ -1176,6 +1180,8 @@ static void mnt_add_to_ns(struct mnt_namespace *ns, struct mount *mnt)
                ns->mnt_first_node = &mnt->mnt_node;
        rb_link_node(&mnt->mnt_node, parent, link);
        rb_insert_color(&mnt->mnt_node, &ns->mounts);
+
+       mnt_notify_add(mnt);
 }
 
 /*
@@ -1723,6 +1729,50 @@ int may_umount(struct vfsmount *mnt)
 
 EXPORT_SYMBOL(may_umount);
 
+#ifdef CONFIG_FSNOTIFY
+static void mnt_notify(struct mount *p)
+{
+       if (!p->prev_ns && p->mnt_ns) {
+               fsnotify_mnt_attach(p->mnt_ns, &p->mnt);
+       } else if (p->prev_ns && !p->mnt_ns) {
+               fsnotify_mnt_detach(p->prev_ns, &p->mnt);
+       } else if (p->prev_ns == p->mnt_ns) {
+               fsnotify_mnt_move(p->mnt_ns, &p->mnt);
+       } else {
+               fsnotify_mnt_detach(p->prev_ns, &p->mnt);
+               fsnotify_mnt_attach(p->mnt_ns, &p->mnt);
+       }
+       p->prev_ns = p->mnt_ns;
+}
+
+static void notify_mnt_list(void)
+{
+       struct mount *m, *tmp;
+       /*
+        * Notify about mounts that were added/reparented/detached/remain
+        * connected after unmount.
+        */
+       list_for_each_entry_safe(m, tmp, &notify_list, to_notify) {
+               mnt_notify(m);
+               list_del_init(&m->to_notify);
+       }
+}
+
+static bool need_notify_mnt_list(void)
+{
+       return !list_empty(&notify_list);
+}
+#else
+static void notify_mnt_list(void)
+{
+}
+
+static bool need_notify_mnt_list(void)
+{
+       return false;
+}
+#endif
+
 static void namespace_unlock(void)
 {
        struct hlist_head head;
@@ -1733,7 +1783,18 @@ static void namespace_unlock(void)
        hlist_move_list(&unmounted, &head);
        list_splice_init(&ex_mountpoints, &list);
 
-       up_write(&namespace_sem);
+       if (need_notify_mnt_list()) {
+               /*
+                * No point blocking out concurrent readers while notifications
+                * are sent. This will also allow statmount()/listmount() to run
+                * concurrently.
+                */
+               downgrade_write(&namespace_sem);
+               notify_mnt_list();
+               up_read(&namespace_sem);
+       } else {
+               up_write(&namespace_sem);
+       }
 
        shrink_dentry_list(&list);
 
@@ -1846,6 +1907,19 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
                change_mnt_propagation(p, MS_PRIVATE);
                if (disconnect)
                        hlist_add_head(&p->mnt_umount, &unmounted);
+
+               /*
+                * At this point p->mnt_ns is NULL, notification will be queued
+                * only if
+                *
+                *  - p->prev_ns is non-NULL *and*
+                *  - p->prev_ns->n_fsnotify_marks is non-NULL
+                *
+                * This will preclude queuing the mount if this is a cleanup
+                * after a failed copy_tree() or destruction of an anonymous
+                * namespace, etc.
+                */
+               mnt_notify_add(p);
        }
 }
 
@@ -2555,6 +2629,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
                        dest_mp = smp;
                unhash_mnt(source_mnt);
                attach_mnt(source_mnt, top_mnt, dest_mp, beneath);
+               mnt_notify_add(source_mnt);
                touch_mnt_namespace(source_mnt->mnt_ns);
        } else {
                if (source_mnt->mnt_ns) {
@@ -4476,6 +4551,8 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
        list_del_init(&new_mnt->mnt_expire);
        put_mountpoint(root_mp);
        unlock_mount_hash();
+       mnt_notify_add(root_mnt);
+       mnt_notify_add(new_mnt);
        chroot_fs_refs(&root, &new);
        error = 0;
 out4:
index ef048f008bdd5a4b6f2c3d5906034579072a257b..82d809c785ec77d3fa4a149d3f381fe2b513f90e 100644 (file)
@@ -549,8 +549,10 @@ static void restore_mounts(struct list_head *to_restore)
                        mp = parent->mnt_mp;
                        parent = parent->mnt_parent;
                }
-               if (parent != mnt->mnt_parent)
+               if (parent != mnt->mnt_parent) {
                        mnt_change_mountpoint(parent, mp, mnt);
+                       mnt_notify_add(mnt);
+               }
        }
 }