nfsd: freeze c/mtime updates with outstanding WRITE_ATTRS delegation

Instead of allowing the ctime to roll backward with a WRITE_ATTRS
delegation, set FMODE_NOCMTIME on the file and have it skip mtime and
ctime updates.

It is possible that the client will never send a SETATTR to set the
times before returning the delegation. Add two new bools to struct
nfs4_delegation:

dl_written: tracks whether the file has been written since the
delegation was granted. This is set in the WRITE and LAYOUTCOMMIT
handlers.

dl_setattr: tracks whether the client has sent at least one valid
mtime that can also update the ctime in a SETATTR.

When unlocking the lease for the delegation, clear FMODE_NOCMTIME. If
the file has been written, but no setattr for the delegated mtime and
ctime has been done, update the timestamps to current_time().

Suggested-by: NeilBrown <neil@brown.name>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
Jeff Layton 2025-07-30 09:24:37 -04:00 committed by Chuck Lever
parent b40b1ba37a
commit e5e9b24ab8
3 changed files with 69 additions and 3 deletions

View File

@ -1151,7 +1151,9 @@ vet_deleg_attrs(struct nfsd4_setattr *setattr, struct nfs4_delegation *dp)
if (setattr->sa_bmval[2] & FATTR4_WORD2_TIME_DELEG_MODIFY) { if (setattr->sa_bmval[2] & FATTR4_WORD2_TIME_DELEG_MODIFY) {
if (nfsd4_vet_deleg_time(&iattr->ia_mtime, &dp->dl_mtime, &now)) { if (nfsd4_vet_deleg_time(&iattr->ia_mtime, &dp->dl_mtime, &now)) {
iattr->ia_ctime = iattr->ia_mtime; iattr->ia_ctime = iattr->ia_mtime;
if (!nfsd4_vet_deleg_time(&iattr->ia_ctime, &dp->dl_ctime, &now)) if (nfsd4_vet_deleg_time(&iattr->ia_ctime, &dp->dl_ctime, &now))
dp->dl_setattr = true;
else
iattr->ia_valid &= ~(ATTR_CTIME | ATTR_CTIME_SET); iattr->ia_valid &= ~(ATTR_CTIME | ATTR_CTIME_SET);
} else { } else {
iattr->ia_valid &= ~(ATTR_CTIME | ATTR_CTIME_SET | iattr->ia_valid &= ~(ATTR_CTIME | ATTR_CTIME_SET |
@ -1238,12 +1240,26 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
return status; return status;
} }
static void nfsd4_file_mark_deleg_written(struct nfs4_file *fi)
{
spin_lock(&fi->fi_lock);
if (!list_empty(&fi->fi_delegations)) {
struct nfs4_delegation *dp = list_first_entry(&fi->fi_delegations,
struct nfs4_delegation, dl_perfile);
if (dp->dl_type == OPEN_DELEGATE_WRITE_ATTRS_DELEG)
dp->dl_written = true;
}
spin_unlock(&fi->fi_lock);
}
static __be32 static __be32
nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
union nfsd4_op_u *u) union nfsd4_op_u *u)
{ {
struct nfsd4_write *write = &u->write; struct nfsd4_write *write = &u->write;
stateid_t *stateid = &write->wr_stateid; stateid_t *stateid = &write->wr_stateid;
struct nfs4_stid *stid = NULL;
struct nfsd_file *nf = NULL; struct nfsd_file *nf = NULL;
__be32 status = nfs_ok; __be32 status = nfs_ok;
unsigned long cnt; unsigned long cnt;
@ -1256,10 +1272,15 @@ nfsd4_write(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
trace_nfsd_write_start(rqstp, &cstate->current_fh, trace_nfsd_write_start(rqstp, &cstate->current_fh,
write->wr_offset, cnt); write->wr_offset, cnt);
status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh, status = nfs4_preprocess_stateid_op(rqstp, cstate, &cstate->current_fh,
stateid, WR_STATE, &nf, NULL); stateid, WR_STATE, &nf, &stid);
if (status) if (status)
return status; return status;
if (stid) {
nfsd4_file_mark_deleg_written(stid->sc_file);
nfs4_put_stid(stid);
}
write->wr_how_written = write->wr_stable_how; write->wr_how_written = write->wr_stable_how;
status = nfsd_vfs_write(rqstp, &cstate->current_fh, nf, status = nfsd_vfs_write(rqstp, &cstate->current_fh, nf,
write->wr_offset, &write->wr_payload, write->wr_offset, &write->wr_payload,
@ -2550,6 +2571,7 @@ nfsd4_layoutcommit(struct svc_rqst *rqstp,
mutex_unlock(&ls->ls_mutex); mutex_unlock(&ls->ls_mutex);
nfserr = ops->proc_layoutcommit(inode, rqstp, lcp); nfserr = ops->proc_layoutcommit(inode, rqstp, lcp);
nfsd4_file_mark_deleg_written(ls->ls_stid.sc_file);
nfs4_put_stid(&ls->ls_stid); nfs4_put_stid(&ls->ls_stid);
out: out:
return nfserr; return nfserr;

View File

@ -1222,6 +1222,42 @@ static void put_deleg_file(struct nfs4_file *fp)
nfs4_file_put_access(fp, NFS4_SHARE_ACCESS_READ); nfs4_file_put_access(fp, NFS4_SHARE_ACCESS_READ);
} }
static void nfsd4_finalize_deleg_timestamps(struct nfs4_delegation *dp, struct file *f)
{
struct iattr ia = { .ia_valid = ATTR_ATIME | ATTR_CTIME | ATTR_MTIME };
struct inode *inode = file_inode(f);
int ret;
/* don't do anything if FMODE_NOCMTIME isn't set */
if ((READ_ONCE(f->f_mode) & FMODE_NOCMTIME) == 0)
return;
spin_lock(&f->f_lock);
f->f_mode &= ~FMODE_NOCMTIME;
spin_unlock(&f->f_lock);
/* was it never written? */
if (!dp->dl_written)
return;
/* did it get a setattr for the timestamps at some point? */
if (dp->dl_setattr)
return;
/* Stamp everything to "now" */
inode_lock(inode);
ret = notify_change(&nop_mnt_idmap, f->f_path.dentry, &ia, NULL);
inode_unlock(inode);
if (ret) {
struct inode *inode = file_inode(f);
pr_notice_ratelimited("Unable to update timestamps on inode %02x:%02x:%lu: %d\n",
MAJOR(inode->i_sb->s_dev),
MINOR(inode->i_sb->s_dev),
inode->i_ino, ret);
}
}
static void nfs4_unlock_deleg_lease(struct nfs4_delegation *dp) static void nfs4_unlock_deleg_lease(struct nfs4_delegation *dp)
{ {
struct nfs4_file *fp = dp->dl_stid.sc_file; struct nfs4_file *fp = dp->dl_stid.sc_file;
@ -1229,6 +1265,7 @@ static void nfs4_unlock_deleg_lease(struct nfs4_delegation *dp)
WARN_ON_ONCE(!fp->fi_delegees); WARN_ON_ONCE(!fp->fi_delegees);
nfsd4_finalize_deleg_timestamps(dp, nf->nf_file);
kernel_setlease(nf->nf_file, F_UNLCK, NULL, (void **)&dp); kernel_setlease(nf->nf_file, F_UNLCK, NULL, (void **)&dp);
put_deleg_file(fp); put_deleg_file(fp);
} }
@ -6265,6 +6302,8 @@ nfs4_open_delegation(struct svc_rqst *rqstp, struct nfsd4_open *open,
memcpy(&open->op_delegate_stateid, &dp->dl_stid.sc_stateid, sizeof(dp->dl_stid.sc_stateid)); memcpy(&open->op_delegate_stateid, &dp->dl_stid.sc_stateid, sizeof(dp->dl_stid.sc_stateid));
if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE) { if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE) {
struct file *f = dp->dl_stid.sc_file->fi_deleg_file->nf_file;
if (!nfsd4_add_rdaccess_to_wrdeleg(rqstp, open, fh, stp) || if (!nfsd4_add_rdaccess_to_wrdeleg(rqstp, open, fh, stp) ||
!nfs4_delegation_stat(dp, currentfh, &stat)) { !nfs4_delegation_stat(dp, currentfh, &stat)) {
nfs4_put_stid(&dp->dl_stid); nfs4_put_stid(&dp->dl_stid);
@ -6278,6 +6317,9 @@ nfs4_open_delegation(struct svc_rqst *rqstp, struct nfsd4_open *open,
dp->dl_atime = stat.atime; dp->dl_atime = stat.atime;
dp->dl_ctime = stat.ctime; dp->dl_ctime = stat.ctime;
dp->dl_mtime = stat.mtime; dp->dl_mtime = stat.mtime;
spin_lock(&f->f_lock);
f->f_mode |= FMODE_NOCMTIME;
spin_unlock(&f->f_lock);
trace_nfsd_deleg_write(&dp->dl_stid.sc_stateid); trace_nfsd_deleg_write(&dp->dl_stid.sc_stateid);
} else { } else {
open->op_delegate_type = deleg_ts && nfs4_delegation_stat(dp, currentfh, &stat) ? open->op_delegate_type = deleg_ts && nfs4_delegation_stat(dp, currentfh, &stat) ?

View File

@ -217,10 +217,12 @@ struct nfs4_delegation {
struct nfs4_clnt_odstate *dl_clnt_odstate; struct nfs4_clnt_odstate *dl_clnt_odstate;
time64_t dl_time; time64_t dl_time;
u32 dl_type; u32 dl_type;
/* For recall: */ /* For recall: */
int dl_retries; int dl_retries;
struct nfsd4_callback dl_recall; struct nfsd4_callback dl_recall;
bool dl_recalled; bool dl_recalled;
bool dl_written;
bool dl_setattr;
/* for CB_GETATTR */ /* for CB_GETATTR */
struct nfs4_cb_fattr dl_cb_fattr; struct nfs4_cb_fattr dl_cb_fattr;