]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
fsnotify: Use connector list for destroying inode marks
authorJan Kara <jack@suse.cz>
Wed, 15 Oct 2025 14:02:47 +0000 (16:02 +0200)
committerJan Kara <jack@suse.cz>
Fri, 23 Jan 2026 12:26:37 +0000 (13:26 +0100)
Instead of iterating all inodes belonging to a superblock to find inode
marks and remove them on umount, iterate all inode connectors for the
superblock. This may be substantially faster since there are generally
much less inodes with fsnotify marks than all inodes. It also removes
one use of sb->s_inodes list which we strive to ultimately remove.

Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Reviewed-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Jan Kara <jack@suse.cz>
fs/notify/fsnotify.c
fs/notify/fsnotify.h
fs/notify/mark.c

index 706484fb3bf3f43a462f2d17f25823ac61d77c53..9995de1710e59696ef708763488e739a2428ef4f 100644 (file)
@@ -33,65 +33,6 @@ void __fsnotify_mntns_delete(struct mnt_namespace *mntns)
        fsnotify_clear_marks_by_mntns(mntns);
 }
 
-/**
- * fsnotify_unmount_inodes - an sb is unmounting.  handle any watched inodes.
- * @sb: superblock being unmounted.
- *
- * Called during unmount with no locks held, so needs to be safe against
- * concurrent modifiers. We temporarily drop sb->s_inode_list_lock and CAN block.
- */
-static void fsnotify_unmount_inodes(struct super_block *sb)
-{
-       struct inode *inode, *iput_inode = NULL;
-
-       spin_lock(&sb->s_inode_list_lock);
-       list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
-               /*
-                * We cannot __iget() an inode in state I_FREEING,
-                * I_WILL_FREE, or I_NEW which is fine because by that point
-                * the inode cannot have any associated watches.
-                */
-               spin_lock(&inode->i_lock);
-               if (inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW)) {
-                       spin_unlock(&inode->i_lock);
-                       continue;
-               }
-
-               /*
-                * If i_count is zero, the inode cannot have any watches and
-                * doing an __iget/iput with SB_ACTIVE clear would actually
-                * evict all inodes with zero i_count from icache which is
-                * unnecessarily violent and may in fact be illegal to do.
-                * However, we should have been called /after/ evict_inodes
-                * removed all zero refcount inodes, in any case.  Test to
-                * be sure.
-                */
-               if (!icount_read(inode)) {
-                       spin_unlock(&inode->i_lock);
-                       continue;
-               }
-
-               __iget(inode);
-               spin_unlock(&inode->i_lock);
-               spin_unlock(&sb->s_inode_list_lock);
-
-               iput(iput_inode);
-
-               /* for each watch, send FS_UNMOUNT and then remove it */
-               fsnotify_inode(inode, FS_UNMOUNT);
-
-               fsnotify_inode_delete(inode);
-
-               iput_inode = inode;
-
-               cond_resched();
-               spin_lock(&sb->s_inode_list_lock);
-       }
-       spin_unlock(&sb->s_inode_list_lock);
-
-       iput(iput_inode);
-}
-
 void fsnotify_sb_delete(struct super_block *sb)
 {
        struct fsnotify_sb_info *sbinfo = fsnotify_sb_info(sb);
@@ -100,7 +41,7 @@ void fsnotify_sb_delete(struct super_block *sb)
        if (!sbinfo)
                return;
 
-       fsnotify_unmount_inodes(sb);
+       fsnotify_unmount_inodes(sbinfo);
        fsnotify_clear_marks_by_sb(sb);
        /* Wait for outstanding object references from connectors */
        wait_var_event(fsnotify_sb_watched_objects(sb),
index 6b58d733ceb6e9c00a6a2e63f16e0b104125f1a4..58c7bb25e5718e268b5f17086653f4e81df57afd 100644 (file)
@@ -77,6 +77,9 @@ extern struct srcu_struct fsnotify_mark_srcu;
 extern int fsnotify_compare_groups(struct fsnotify_group *a,
                                   struct fsnotify_group *b);
 
+/* Destroy all inode marks for given superblock */
+void fsnotify_unmount_inodes(struct fsnotify_sb_info *sbinfo);
+
 /* Destroy all marks attached to an object via connector */
 extern void fsnotify_destroy_marks(fsnotify_connp_t *connp);
 /* run the list of all marks associated with inode and destroy them */
index 4a525791a2f36870f6d133292196dbf7751a0659..8e6997e9aebbb88a3db0a6a9175ddf4781ab9f10 100644 (file)
@@ -666,6 +666,56 @@ struct fsnotify_inode_mark_connector {
        struct list_head conns_list;
 };
 
+static struct inode *fsnotify_get_living_inode(struct fsnotify_sb_info *sbinfo)
+{
+       struct fsnotify_inode_mark_connector *iconn;
+       struct inode *inode;
+
+       spin_lock(&sbinfo->list_lock);
+       /* Find the first non-evicting inode */
+       list_for_each_entry(iconn, &sbinfo->inode_conn_list, conns_list) {
+               /* All connectors on the list are still attached to an inode */
+               inode = iconn->common.obj;
+               /*
+                * For connectors without FSNOTIFY_CONN_FLAG_HAS_IREF
+                * (evictable marks) corresponding inode may well have 0
+                * refcount and can be undergoing eviction. OTOH list_lock
+                * protects us from the connector getting detached and inode
+                * freed. So we can poke around the inode safely.
+                */
+               spin_lock(&inode->i_lock);
+               if (likely(
+                   !(inode_state_read(inode) & (I_FREEING | I_WILL_FREE)))) {
+                       __iget(inode);
+                       spin_unlock(&inode->i_lock);
+                       spin_unlock(&sbinfo->list_lock);
+                       return inode;
+               }
+               spin_unlock(&inode->i_lock);
+       }
+       spin_unlock(&sbinfo->list_lock);
+
+       return NULL;
+}
+
+/**
+ * fsnotify_unmount_inodes - an sb is unmounting. Handle any watched inodes.
+ * @sbinfo: fsnotify info for superblock being unmounted.
+ *
+ * Walk all inode connectors for the superblock and free all associated marks.
+ */
+void fsnotify_unmount_inodes(struct fsnotify_sb_info *sbinfo)
+{
+       struct inode *inode;
+
+       while ((inode = fsnotify_get_living_inode(sbinfo))) {
+               fsnotify_inode(inode, FS_UNMOUNT);
+               fsnotify_clear_marks_by_inode(inode);
+               iput(inode);
+               cond_resched();
+       }
+}
+
 static void fsnotify_init_connector(struct fsnotify_mark_connector *conn,
                                    void *obj, unsigned int obj_type)
 {