From 8c955dacf45f8964b98f26910ffecb5e26c02e31 Mon Sep 17 00:00:00 2001 From: Roberto Bergantinos Corpas Date: Tue, 21 Apr 2026 12:00:46 +0200 Subject: [PATCH] nfs: set gencount on protocol-specific remove commit bd4928ec799b ("NFS: Avoid changing nlink when file removes and attribute updates race") avoids races on attribute cache with any inflight RPC that may modify inode attributes (and gencount) during i.e. REMOVE. However it does not take into account that REMOVE may trigger a delegation return which also advances gencount (via the attribute refresh during delegation return), and in that case the gencount mismatch is not due to an inflight RPC that already reflected the removal. Push the setting of gencount under each version-specific layer, reordering correctly for nfsv4 case after the delegation returns. This fixes LTP/inotify04 failure after bd4928ec799b. Fixes: bd4928ec799b ("NFS: Avoid changing nlink when file removes and attribute updates race") Signed-off-by: Roberto Bergantinos Corpas Signed-off-by: Anna Schumaker --- fs/nfs/dir.c | 13 +++---------- fs/nfs/internal.h | 1 + fs/nfs/nfs3proc.c | 8 ++++++++ fs/nfs/nfs4proc.c | 5 +++++ fs/nfs/proc.c | 8 ++++++++ 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 49394123bd09..741144f04932 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -1935,7 +1935,7 @@ static int nfs_dentry_delete(const struct dentry *dentry) } /* Ensure that we revalidate inode->i_nlink */ -static void nfs_drop_nlink(struct inode *inode, unsigned long gencount) +void nfs_drop_nlink(struct inode *inode, unsigned long gencount) { struct nfs_inode *nfsi = NFS_I(inode); @@ -1949,6 +1949,7 @@ static void nfs_drop_nlink(struct inode *inode, unsigned long gencount) NFS_INO_INVALID_NLINK); spin_unlock(&inode->i_lock); } +EXPORT_SYMBOL_GPL(nfs_drop_nlink); /* * Called when the dentry loses inode. @@ -2554,7 +2555,6 @@ EXPORT_SYMBOL_GPL(nfs_rmdir); static int nfs_safe_remove(struct dentry *dentry) { struct inode *dir = d_inode(dentry->d_parent); - struct inode *inode = d_inode(dentry); int error = -EBUSY; dfprintk(VFS, "NFS: safe_remove(%pd2)\n", dentry); @@ -2566,14 +2566,7 @@ static int nfs_safe_remove(struct dentry *dentry) } trace_nfs_remove_enter(dir, dentry); - if (inode != NULL) { - unsigned long gencount = READ_ONCE(NFS_I(inode)->attr_gencount); - - error = NFS_PROTO(dir)->remove(dir, dentry); - if (error == 0) - nfs_drop_nlink(inode, gencount); - } else - error = NFS_PROTO(dir)->remove(dir, dentry); + error = NFS_PROTO(dir)->remove(dir, dentry); if (error == -ENOENT) nfs_dentry_handle_enoent(dentry); trace_nfs_remove_exit(dir, dentry, error); diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index 959edbc9add2..42f170973000 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -395,6 +395,7 @@ extern unsigned long nfs_access_cache_count(struct shrinker *shrink, struct shrink_control *sc); extern unsigned long nfs_access_cache_scan(struct shrinker *shrink, struct shrink_control *sc); +extern void nfs_drop_nlink(struct inode *inode, unsigned long gencount); struct dentry *nfs_lookup(struct inode *, struct dentry *, unsigned int); void nfs_d_prune_case_insensitive_aliases(struct inode *inode); int nfs_create(struct mnt_idmap *, struct inode *, struct dentry *, diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c index b80d0c5efc27..b5aa3100b627 100644 --- a/fs/nfs/nfs3proc.c +++ b/fs/nfs/nfs3proc.c @@ -448,13 +448,21 @@ nfs3_proc_remove(struct inode *dir, struct dentry *dentry) .rpc_resp = &res, }; int status = -ENOMEM; + struct inode *inode = d_inode(dentry); + unsigned long gencount; dprintk("NFS call remove %pd2\n", dentry); res.dir_attr = nfs_alloc_fattr(); if (res.dir_attr == NULL) goto out; + if (inode) + gencount = READ_ONCE(NFS_I(inode)->attr_gencount); + status = rpc_call_sync(NFS_CLIENT(dir), &msg, 0); + if (status == 0 && inode) + nfs_drop_nlink(inode, gencount); + nfs_post_op_update_inode(dir, res.dir_attr); nfs_free_fattr(res.dir_attr); out: diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 518348e87dd8..0adc209dc5df 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -4969,12 +4969,14 @@ static int nfs4_proc_remove(struct inode *dir, struct dentry *dentry) }; struct inode *inode = d_inode(dentry); int err; + unsigned long gencount; if (inode) { if (inode->i_nlink == 1) nfs4_inode_return_delegation(inode); else nfs4_inode_make_writeable(inode); + gencount = READ_ONCE(NFS_I(inode)->attr_gencount); } do { err = _nfs4_proc_remove(dir, &dentry->d_name, NF4REG); @@ -4982,6 +4984,9 @@ static int nfs4_proc_remove(struct inode *dir, struct dentry *dentry) err = nfs4_handle_exception(NFS_SERVER(dir), err, &exception); } while (exception.retry); + + if (err == 0 && inode) + nfs_drop_nlink(inode, gencount); return err; } diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c index 03c2c1f31be9..406d04b52a7d 100644 --- a/fs/nfs/proc.c +++ b/fs/nfs/proc.c @@ -323,9 +323,17 @@ nfs_proc_remove(struct inode *dir, struct dentry *dentry) .rpc_argp = &arg, }; int status; + struct inode *inode = d_inode(dentry); + unsigned long gencount; dprintk("NFS call remove %pd2\n",dentry); + if (inode) + gencount = READ_ONCE(NFS_I(inode)->attr_gencount); + status = rpc_call_sync(NFS_CLIENT(dir), &msg, 0); + if (status == 0 && inode) + nfs_drop_nlink(inode, gencount); + nfs_mark_for_revalidate(dir); dprintk("NFS reply remove: %d\n", status);