fs/nfsd: fix update of inode attrs in CB_GETATTR
Currently, we copy the mtime and ctime to the in-core inode and then
mark the inode dirty. This is fine for certain types of filesystems, but
not all. Some require a real setattr to properly change these values
(e.g. ceph or reexported NFS).
Fix this code to call notify_change() instead, which is the proper way
to effect a setattr. There is one problem though:
In this case, the client is holding a write delegation and has sent us
attributes to update our cache. We don't want to break the delegation
for this since that would defeat the purpose. Add a new ATTR_DELEG flag
that makes notify_change bypass the try_break_deleg call.
Fixes: c5967721e1
("NFSD: handle GETATTR conflict with write delegation")
Reviewed-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
parent
1116e0e372
commit
7e8ae8486e
14
fs/attr.c
14
fs/attr.c
@ -489,9 +489,17 @@ int notify_change(struct mnt_idmap *idmap, struct dentry *dentry,
|
||||
error = security_inode_setattr(idmap, dentry, attr);
|
||||
if (error)
|
||||
return error;
|
||||
error = try_break_deleg(inode, delegated_inode);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
/*
|
||||
* If ATTR_DELEG is set, then these attributes are being set on
|
||||
* behalf of the holder of a write delegation. We want to avoid
|
||||
* breaking the delegation in this case.
|
||||
*/
|
||||
if (!(ia_valid & ATTR_DELEG)) {
|
||||
error = try_break_deleg(inode, delegated_inode);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
if (inode->i_op->setattr)
|
||||
error = inode->i_op->setattr(idmap, dentry, attr);
|
||||
|
@ -8815,7 +8815,7 @@ nfsd4_get_writestateid(struct nfsd4_compound_state *cstate,
|
||||
/**
|
||||
* nfsd4_deleg_getattr_conflict - Recall if GETATTR causes conflict
|
||||
* @rqstp: RPC transaction context
|
||||
* @inode: file to be checked for a conflict
|
||||
* @dentry: dentry of inode to be checked for a conflict
|
||||
* @modified: return true if file was modified
|
||||
* @size: new size of file if modified is true
|
||||
*
|
||||
@ -8830,7 +8830,7 @@ nfsd4_get_writestateid(struct nfsd4_compound_state *cstate,
|
||||
* code is returned.
|
||||
*/
|
||||
__be32
|
||||
nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode,
|
||||
nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct dentry *dentry,
|
||||
bool *modified, u64 *size)
|
||||
{
|
||||
__be32 status;
|
||||
@ -8839,6 +8839,7 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode,
|
||||
struct file_lease *fl;
|
||||
struct iattr attrs;
|
||||
struct nfs4_cb_fattr *ncf;
|
||||
struct inode *inode = d_inode(dentry);
|
||||
|
||||
*modified = false;
|
||||
ctx = locks_inode_context(inode);
|
||||
@ -8890,15 +8891,22 @@ break_lease:
|
||||
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
|
||||
*/
|
||||
attrs.ia_mtime = attrs.ia_ctime = current_time(inode);
|
||||
attrs.ia_valid = ATTR_MTIME | ATTR_CTIME;
|
||||
setattr_copy(&nop_mnt_idmap, inode, &attrs);
|
||||
mark_inode_dirty(inode);
|
||||
attrs.ia_valid = ATTR_MTIME | ATTR_CTIME | ATTR_DELEG;
|
||||
inode_lock(inode);
|
||||
err = notify_change(&nop_mnt_idmap, dentry, &attrs, NULL);
|
||||
inode_unlock(inode);
|
||||
if (err) {
|
||||
nfs4_put_stid(&dp->dl_stid);
|
||||
return nfserrno(err);
|
||||
}
|
||||
ncf->ncf_cur_fsize = ncf->ncf_cb_fsize;
|
||||
*size = ncf->ncf_cur_fsize;
|
||||
*modified = true;
|
||||
|
@ -3565,7 +3565,7 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
|
||||
}
|
||||
args.size = 0;
|
||||
if (attrmask[0] & (FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE)) {
|
||||
status = nfsd4_deleg_getattr_conflict(rqstp, d_inode(dentry),
|
||||
status = nfsd4_deleg_getattr_conflict(rqstp, dentry,
|
||||
&file_modified, &size);
|
||||
if (status)
|
||||
goto out;
|
||||
|
@ -781,5 +781,5 @@ static inline bool try_to_expire_client(struct nfs4_client *clp)
|
||||
}
|
||||
|
||||
extern __be32 nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp,
|
||||
struct inode *inode, bool *file_modified, u64 *size);
|
||||
struct dentry *dentry, bool *file_modified, u64 *size);
|
||||
#endif /* NFSD4_STATE_H */
|
||||
|
@ -208,6 +208,7 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
|
||||
#define ATTR_OPEN (1 << 15) /* Truncating from open(O_TRUNC) */
|
||||
#define ATTR_TIMES_SET (1 << 16)
|
||||
#define ATTR_TOUCH (1 << 17)
|
||||
#define ATTR_DELEG (1 << 18) /* Delegated attrs. Don't break write delegations */
|
||||
|
||||
/*
|
||||
* Whiteout is represented by a char device. The following constants define the
|
||||
|
Loading…
Reference in New Issue
Block a user