When there's a readdir in progress, doing a FUSE_NOTIFY_INVAL_{INODE,ENTRY} on an inode or dentry may result in stale directory info being cached. This is because the invalidation does not reset the readdir cache. This patch fixes this issue by adding a call to fuse_rdc_reset() (modified to include the required locking) to these two operations, allowing the readdir cache to be invalidated while it's being filled-in. Assisted-by: Claude:claude-opus-4-5 Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 5 +++-- fs/fuse/fuse_i.h | 13 +++++++++++++ fs/fuse/inode.c | 1 + fs/fuse/readdir.c | 6 +++--- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 7ac6b232ef12..6e5851de3613 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1615,6 +1615,7 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid, if (!(flags & FUSE_EXPIRE_ONLY)) d_invalidate(entry); fuse_invalidate_entry_cache(entry); + fuse_rdc_reset(entry->d_inode); if (child_nodeid != 0) { inode_lock(d_inode(entry)); @@ -1637,7 +1638,7 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid, dont_mount(entry); clear_nlink(d_inode(entry)); err = 0; - badentry: +badentry: inode_unlock(d_inode(entry)); if (!err) d_delete(entry); @@ -1646,7 +1647,7 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid, } end_removing(entry); - put_parent: +put_parent: dput(dir); iput(parent); return err; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 7f16049387d1..0f31065f8046 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1494,6 +1494,19 @@ int fuse_set_acl(struct mnt_idmap *, struct dentry *dentry, /* readdir.c */ int fuse_readdir(struct file *file, struct dir_context *ctx); +void __fuse_rdc_reset(struct inode *inode); + +static inline void fuse_rdc_reset(struct inode *inode) +{ + struct fuse_inode *fi; + + if (S_ISDIR(inode->i_mode)) { + fi = get_fuse_inode(inode); + spin_lock(&fi->rdc.lock); + __fuse_rdc_reset(inode); + spin_unlock(&fi->rdc.lock); + } +} /** * Return the number of bytes in an arguments list diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index c795abe47a4f..4d8220f573f2 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -570,6 +570,7 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid, fi->attr_version = atomic64_inc_return(&fc->attr_version); spin_unlock(&fi->lock); + fuse_rdc_reset(inode); fuse_invalidate_attr(inode); forget_all_cached_acls(inode); if (offset >= 0) { diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index c2aae2eef086..e7e1f051e45c 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -430,7 +430,7 @@ static enum fuse_parse_result fuse_parse_cache(struct fuse_file *ff, return res; } -static void fuse_rdc_reset(struct inode *inode) +void __fuse_rdc_reset(struct inode *inode) { struct fuse_inode *fi = get_fuse_inode(inode); @@ -493,7 +493,7 @@ static int fuse_readdir_cached(struct file *file, struct dir_context *ctx) if (inode_peek_iversion(inode) != fi->rdc.iversion || !timespec64_equal(&fi->rdc.mtime, &mtime)) { - fuse_rdc_reset(inode); + __fuse_rdc_reset(inode); goto retry_locked; } } @@ -541,7 +541,7 @@ static int fuse_readdir_cached(struct file *file, struct dir_context *ctx) * Uh-oh: page gone missing, cache is useless */ if (fi->rdc.version == ff->readdir.version) - fuse_rdc_reset(inode); + __fuse_rdc_reset(inode); goto retry_locked; }