From a05fc7edd988c176491487ef0ae4dbf5f7a64cd7 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Wed, 15 Oct 2025 16:02:47 +0200 Subject: [PATCH] fsnotify: Use connector list for destroying inode marks 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 Reviewed-by: Christian Brauner Signed-off-by: Jan Kara --- fs/notify/fsnotify.c | 61 +------------------------------------------- fs/notify/fsnotify.h | 3 +++ fs/notify/mark.c | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 60 deletions(-) diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c index 706484fb3bf3f..9995de1710e59 100644 --- a/fs/notify/fsnotify.c +++ b/fs/notify/fsnotify.c @@ -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), diff --git a/fs/notify/fsnotify.h b/fs/notify/fsnotify.h index 6b58d733ceb6e..58c7bb25e5718 100644 --- a/fs/notify/fsnotify.h +++ b/fs/notify/fsnotify.h @@ -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 */ diff --git a/fs/notify/mark.c b/fs/notify/mark.c index 4a525791a2f36..8e6997e9aebbb 100644 --- a/fs/notify/mark.c +++ b/fs/notify/mark.c @@ -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) { -- 2.47.3