[PATCH v2 5/5] nfsd: mark the delegation written on COPY/CLONE instead of stamping c/mtime
From: Jeff Layton
Date: Fri Nov 06 2026 - 15:41:21 EST
Under a timestamp delegation, nfsd stamps the destination's c/mtime to
the server's "now" after a COPY or CLONE. Since commit 6e7daa3dad29
("NFSv4.2: fix CLONE/COPY attrs in presence of delegated attributes"),
the client also stamps its own c/mtime afterwards and ignores the
server's. If the client's coarse stamp lands even slightly before the
server's, the server can't move ctime back when the holder returns its
times, and ctime changes after DELEGRETURN. COPY returns no attributes,
so the client can't avoid this.
Treat COPY/CLONE like WRITE: mark the delegation written and let the
holder supply the times. If the holder never returns them, DELEGRETURN
stamps them via nfsd4_finalize_deleg_timestamps(); other clients' GETATTRs
go through CB_GETATTR. The async COPY thread has no filehandle, so look
up the nfs4_file at launch, and have the thread mark the delegation when
the copy has written something. That also covers a delegation granted
while the copy ran.
Fixes: 2863bac7f49c ("nfsd: update mtime/ctime on CLONE in presense of delegated attributes")
Fixes: 4183cf383b6f ("nfsd: update mtime/ctime on COPY in presence of delegated attributes")
Signed-off-by: Jeff Layton <jlayton@xxxxxxxxxx>
---
fs/nfsd/nfs4proc.c | 49 ++++++++++++++++++++++++++++++++++++++-----------
fs/nfsd/nfs4state.c | 2 +-
fs/nfsd/state.h | 1 +
fs/nfsd/xdr4.h | 3 ++-
4 files changed, 42 insertions(+), 13 deletions(-)
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 25613c1ffaaf..c7ede1e7f697 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1438,6 +1438,25 @@ static void nfsd4_file_mark_deleg_written(struct nfs4_file *fi)
spin_unlock(&fi->fi_lock);
}
+/*
+ * A COPY or CLONE into a file under a timestamp delegation is the holder's
+ * modification, like a WRITE: the holder supplies the c/mtime, and DELEGRETURN
+ * stamps them if it doesn't.
+ */
+static void nfsd4_copy_mark_deleg_written(struct svc_fh *fhp,
+ struct nfsd_file *dst)
+{
+ struct nfs4_file *fi;
+
+ if (!(READ_ONCE(dst->nf_file->f_mode) & FMODE_NOCMTIME))
+ return;
+ fi = nfsd4_file_hash_lookup(fhp);
+ if (fi) {
+ nfsd4_file_mark_deleg_written(fi);
+ put_nfs4_file(fi);
+ }
+}
+
static __be32
nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
union nfsd4_op_u *u)
@@ -1576,8 +1595,8 @@ nfsd4_clone(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
}
status = nfserrno(host_err);
- if (!status && (READ_ONCE(dst->nf_file->f_mode) & FMODE_NOCMTIME) != 0)
- nfsd_update_cmtime_attr(dst->nf_file, 0);
+ if (!status)
+ nfsd4_copy_mark_deleg_written(&cstate->current_fh, dst);
nfsd_file_put(dst);
nfsd_file_put(src);
@@ -2198,6 +2217,10 @@ static void release_copy_files(struct nfsd4_copy *copy)
nfsd_file_put(copy->nf_dst);
copy->nf_dst = NULL;
}
+ if (copy->cp_dst_fi) {
+ put_nfs4_file(copy->cp_dst_fi);
+ copy->cp_dst_fi = NULL;
+ }
}
/*
@@ -2301,8 +2324,9 @@ static int nfsd4_do_async_copy(void *data)
set_bit(NFSD4_COPY_F_COMPLETED, ©->cp_flags);
trace_nfsd_copy_async_done(async->cp_stid.sc_client, copy);
atomic_dec(©->cp_nn->pending_async_copies);
- if (copy->cp_res.wr_bytes_written > 0 && copy->attr_update)
- nfsd_update_cmtime_attr(copy->nf_dst->nf_file, 0);
+ /* Before CB_OFFLOAD tells the holder to supply the times */
+ if (copy->cp_res.wr_bytes_written > 0 && copy->cp_dst_fi)
+ nfsd4_file_mark_deleg_written(copy->cp_dst_fi);
nfsd4_send_cb_offload(async);
/* Drop the kthread's reference (taken in nfsd4_copy()); copy may be freed after this. */
nfs4_put_copy(async);
@@ -2382,9 +2406,13 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
* the callback stateid zeroed.
*/
dup_copy_fields(copy, &async_copy->cp_copy);
- if ((READ_ONCE(copy->nf_dst->nf_file->f_mode) &
- FMODE_NOCMTIME) != 0)
- async_copy->cp_copy.attr_update = true;
+ /*
+ * The copy thread has no filehandle. Look up the file now;
+ * the thread marks its delegation if the copy writes.
+ */
+ if (READ_ONCE(copy->nf_dst->nf_file->f_mode) & FMODE_NOCMTIME)
+ async_copy->cp_copy.cp_dst_fi =
+ nfsd4_file_hash_lookup(&cstate->current_fh);
memcpy(async_copy->cp_cb_offload.co_referring_sessionid.data,
cstate->session->se_sessionid.data,
NFS4_MAX_SESSIONID_LEN);
@@ -2415,10 +2443,9 @@ nfsd4_copy(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
} else {
status = nfsd4_do_copy(copy, copy->nf_src->nf_file,
copy->nf_dst->nf_file, true);
- if ((READ_ONCE(copy->nf_dst->nf_file->f_mode) &
- FMODE_NOCMTIME) != 0 &&
- copy->cp_res.wr_bytes_written > 0)
- nfsd_update_cmtime_attr(copy->nf_dst->nf_file, 0);
+ if (copy->cp_res.wr_bytes_written > 0)
+ nfsd4_copy_mark_deleg_written(&cstate->current_fh,
+ copy->nf_dst);
}
out:
trace_nfsd_copy_done(copy, status);
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 360b8886dbc0..fcf7874514cf 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -6288,7 +6288,7 @@ move_to_close_lru(struct nfs4_ol_stateid *s, struct net *net)
nfs4_put_stid(&last->st_stid);
}
-static noinline_for_stack struct nfs4_file *
+noinline_for_stack struct nfs4_file *
nfsd4_file_hash_lookup(const struct svc_fh *fhp)
{
struct inode *inode = d_inode(fhp->fh_dentry);
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 9c36076921de..f7a8c04be540 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -988,6 +988,7 @@ int nfsd_handle_dir_event(u32 mask, const struct inode *dir, const void *data,
int data_type, const struct qstr *name);
void put_nfs4_file(struct nfs4_file *fi);
+struct nfs4_file *nfsd4_file_hash_lookup(const struct svc_fh *fhp);
extern void nfs4_put_cpntf_state(struct nfsd_net *nn,
struct nfs4_cpntf_state *cps);
extern __be32 manage_cpntf_state(struct nfsd_net *nn, stateid_t *st,
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index feda5464f260..8cebeafbfc20 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -646,7 +646,8 @@ struct nfsd4_copy {
struct nfsd_file *nf_src;
struct nfsd_file *nf_dst;
- bool attr_update;
+ /* async: file whose timestamp delegation the copy marks written */
+ struct nfs4_file *cp_dst_fi;
struct nfsd4_ssc_umount_item *ss_nsui;
struct nfs_fh c_fh;
--
2.56.0