public inbox for linux-nfs@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2] nfsd: fix file change detection in CB_GETATTR
@ 2026-04-04  0:54 Scott Mayhew
  2026-04-04 15:07 ` Chuck Lever
  0 siblings, 1 reply; 10+ messages in thread
From: Scott Mayhew @ 2026-04-04  0:54 UTC (permalink / raw)
  To: Chuck Lever, Jeff Layton
  Cc: NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, linux-nfs

RFC 8881, section 10.4.3 doesn't say anything about caching the file
size in the delegation record, nor does it say anything about comparing
a cached file size with the size reported by the client in the
CB_GETATTR reply for the purpose of determining if the client holds
modified data for the file.

What section 10.4.3 of RFC 8881 does say is that the server should
compare the *current* file size with size reported by the client holding
the delegation in the CB_GETATTR reply, and if they differ to treat it
as a modification regardless of the change attribute retrieved via the
CB_GETATTR.

Doing otherwise would cause the server to believe the client holding the
delegation has a modified version of the file, even if the client
flushed the modifications to the server prior to the CB_GETATTR.  This
would have the added side effect of subsequent CB_GETATTRs causing
updates to the mtime, ctime, and change attribute even if the client
holding the delegation makes no further updates to the file.

Modify nfsd4_deleg_getattr_conflict() to obtain the current file size
via vfs_getattr().  Retain the ncf_cur_fsize field, since it's a
convenient way to return the file size back to nfsd4_encode_fattr4(),
but don't use it for the purpose of detecting file changes.

Also, if we recall the delegation (because the client didn't respond to
the CB_GETATTR), then skip the logic that checks the nfs4_cb_fattr
fields.

Fixes: c5967721e106 ("NFSD: handle GETATTR conflict with write delegation")
Signed-off-by: Scott Mayhew <smayhew@redhat.com>
---
This patch is against Chuck's nfsd-testing branch.

A pynfs test that illustrates the issue is available here (delegated
timestamps must be disabled to make the test fail):
https://lore.kernel.org/linux-nfs/20260404003050.1560149-6-smayhew@redhat.com/T/#u

v2 changes:
- update kerneldoc comment for nfsd4_deleg_getattr_conflict()
- relocated declarations in nfsd4_deleg_getattr_conflict() to maintain
  reverse-xmas tree ordering
- pass the struct kstat from the nfsd4_fattr_args to
  nfsd4_deleg_getattr_conflict() instead of creating another one on the
  stack
- only call vfs_getattr() in nfsd4_deleg_getattr_conflict() for the
  !ncf_file_modified case (once the file has been flagged as modified,
  it remains so until the delegation is returned or revoked, so further
  calls to vfs_getattr() are unnecessary)
- if we recall the delegation (because the client didn't respond to the
  CB_GETATTR), then skip the CB_GETATTR comparison logic

Chuck - to test the last part I hacked a pynfs test to force a CB_RECALL
by not responding to the CB_GETATTR in a timely manner.  But nfsd has a
pretty aggressive timeout for the DELEGRETURN (30ms) before it responds
to the client that issued the GETATTR with NFS4ERR_DELAY.  On the system
I'm testing on, it looks like it's taking just over 30ms on average:

            nfsd-2971    [059] ...1.   451.007305: nfsd_cb_recall: addr=127.0.0.1:0 client 69d047c9:03b2de67 stateid 00000002:00000001
            nfsd-2971    [059] .....   451.037306: nfsd_delegret_wakeup: xid=0x16c768b1 inode=000000000a5c53d9 (timed out)
            nfsd-2971    [059] .....   451.037461: nfsd_deleg_return: client 69d047c9:03b2de67 stateid 00000002:00000001

So I bumped the timeout to 50ms (just in my test kernel) and it appears
to do the right thing... I'm just not sure how often it'll actually come
in play in normal usage.

 fs/nfsd/nfs4state.c | 35 ++++++++++++++++++++++++-----------
 fs/nfsd/nfs4xdr.c   |  3 ++-
 fs/nfsd/state.h     |  3 ++-
 3 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index fa657badf5f8..53d8e7e7d60b 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -9444,7 +9444,9 @@ static int cb_getattr_update_times(struct dentry *dentry, struct nfs4_delegation
 /**
  * nfsd4_deleg_getattr_conflict - Recall if GETATTR causes conflict
  * @rqstp: RPC transaction context
- * @dentry: dentry of inode to be checked for a conflict
+ * @path: used to get the inode and size of the file to be checked for a
+ * 	  conflict
+ * @stat: used to get the size of the file to be checked for a conflict
  * @pdp: returned WRITE delegation, if one was found
  *
  * This function is called when there is a conflict between a write
@@ -9459,17 +9461,18 @@ static int cb_getattr_update_times(struct dentry *dentry, struct nfs4_delegation
  * caller must put the reference.
  */
 __be32
-nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct dentry *dentry,
-			     struct nfs4_delegation **pdp)
+nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct path *path,
+			     struct kstat *stat, struct nfs4_delegation **pdp)
 {
 	struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
 	struct nfsd_thread_local_info *ntli = rqstp->rq_private;
+	struct inode *inode = d_inode(path->dentry);
 	struct file_lock_context *ctx;
 	struct nfs4_delegation *dp = NULL;
 	struct file_lease *fl;
 	struct nfs4_cb_fattr *ncf;
-	struct inode *inode = d_inode(dentry);
 	__be32 status;
+	int err;
 
 	ctx = locks_inode_context(inode);
 	if (!ctx)
@@ -9516,20 +9519,30 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct dentry *dentry,
 		if (status != nfserr_jukebox ||
 		    !nfsd_wait_for_delegreturn(rqstp, inode))
 			goto out_status;
+		status = nfs_ok;
+		goto out_status;
+	}
+	if (!ncf->ncf_file_modified) {
+		if (ncf->ncf_initial_cinfo != ncf->ncf_cb_change) {
+			ncf->ncf_file_modified = true;
+		} else {
+			err = vfs_getattr(path, stat, STATX_SIZE,
+					  AT_STATX_SYNC_AS_STAT);
+			if (err) {
+				status = nfserrno(err);
+				goto out_status;
+			}
+			if (stat->size != ncf->ncf_cb_fsize)
+				ncf->ncf_file_modified = true;
+		}
 	}
-	if (!ncf->ncf_file_modified &&
-	    (ncf->ncf_initial_cinfo != ncf->ncf_cb_change ||
-	     ncf->ncf_cur_fsize != ncf->ncf_cb_fsize))
-		ncf->ncf_file_modified = true;
 	if (ncf->ncf_file_modified) {
-		int err;
-
 		/*
 		 * Per section 10.4.3 of RFC 8881, the server would
 		 * not update the file's metadata with the client's
 		 * modified size
 		 */
-		err = cb_getattr_update_times(dentry, dp);
+		err = cb_getattr_update_times(path->dentry, dp);
 		if (err) {
 			status = nfserrno(err);
 			goto out_status;
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 2a0946c630e1..5e09682c8135 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -3914,7 +3914,8 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
 	    (attrmask[1] & (FATTR4_WORD1_TIME_ACCESS |
 			    FATTR4_WORD1_TIME_MODIFY |
 			    FATTR4_WORD1_TIME_METADATA))) {
-		status = nfsd4_deleg_getattr_conflict(rqstp, dentry, &dp);
+		status = nfsd4_deleg_getattr_conflict(rqstp, &path, &args.stat,
+						      &dp);
 		if (status)
 			goto out;
 	}
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 811c148f36fc..4fa6329c75b4 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -904,7 +904,8 @@ static inline bool try_to_expire_client(struct nfs4_client *clp)
 }
 
 extern __be32 nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp,
-		struct dentry *dentry, struct nfs4_delegation **pdp);
+		struct path *path, struct kstat *stat,
+		struct nfs4_delegation **pdp);
 
 struct nfsd4_get_dir_delegation;
 struct nfs4_delegation *nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-04-06 22:14 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-04  0:54 [PATCH v2] nfsd: fix file change detection in CB_GETATTR Scott Mayhew
2026-04-04 15:07 ` Chuck Lever
2026-04-05 22:13   ` Scott Mayhew
2026-04-06 14:12     ` Chuck Lever
2026-04-06 14:38       ` Jeff Layton
2026-04-06 17:47         ` Scott Mayhew
2026-04-06 18:13           ` Jeff Layton
2026-04-06 18:48           ` Jeff Layton
2026-04-06 22:14             ` Scott Mayhew
2026-04-06 17:20       ` Scott Mayhew

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox