[PATCH v3 5/8] nfsd: mark the delegation written on COPY/CLONE instead of stamping c/mtime
From: Jeff Layton
Date: Mon Nov 09 2026 - 15:45: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. Do the lookup
for every async COPY, so a delegation granted while the copy ran is
covered too. nfsd_update_cmtime_attr() is left without callers; remove
it.
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 | 48 +++++++++++++++++++++++++++++++++++++-----------
fs/nfsd/nfs4state.c | 30 +-----------------------------
fs/nfsd/state.h | 2 +-
fs/nfsd/xdr4.h | 3 ++-
4 files changed, 41 insertions(+), 42 deletions(-)
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 25613c1ffaaf..a65e13f0f77a 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);
+ /* The holder may DELEGRETURN as soon as it sees CB_OFFLOAD */
+ 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,12 @@ 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. A timestamp delegation
+ * may be granted while the copy runs.
+ */
+ 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 +2442,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 4566fa86f82f..3bb518270b8b 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -6304,7 +6304,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);
@@ -11177,34 +11177,6 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
return ERR_PTR(status);
}
-/**
- * nfsd_update_cmtime_attr - update file's delegated ctime/mtime,
- * and optionally other attributes (ie ATTR_ATIME).
- * @f: pointer to an opened file
- * @flags: any additional flags that should be updated
- *
- * Given upon opening a file delegated attributes were issues, update
- * @f attributes to current times.
- */
-void nfsd_update_cmtime_attr(struct file *f, unsigned int flags)
-{
- int ret;
- struct inode *inode = file_inode(f);
- struct iattr attr = {
- .ia_valid = ATTR_CTIME | ATTR_MTIME | ATTR_DELEG | flags,
- };
-
- inode_lock(inode);
- ret = notify_change(&nop_mnt_idmap, f->f_path.dentry, &attr, NULL);
- inode_unlock(inode);
- if (ret)
- pr_notice_ratelimited("nfsd: Unable to update timestamps on "
- "inode %02x:%02x:%llu: %d\n",
- MAJOR(inode->i_sb->s_dev),
- MINOR(inode->i_sb->s_dev),
- inode->i_ino, ret);
-}
-
static void
nfsd4_run_cb_notify(struct nfsd4_cb_notify *ncn)
{
diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h
index 9e8699810e33..8a189f1b0133 100644
--- a/fs/nfsd/state.h
+++ b/fs/nfsd/state.h
@@ -981,13 +981,13 @@ void nfsd4_get_client(struct nfs4_client *clp);
void nfsd4_put_client(struct nfs4_client *clp);
void nfsd4_async_copy_reaper(struct nfsd_net *nn);
bool nfsd4_has_active_async_copies(struct nfs4_client *clp);
-void nfsd_update_cmtime_attr(struct file *f, unsigned int flags);
extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(struct xdr_netobj name,
struct xdr_netobj princhash, struct nfsd_net *nn);
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