Skip to content

Commit 7e8ae84

Browse files
jtlaytonchucklever
authored andcommitted
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: c596772 ("NFSD: handle GETATTR conflict with write delegation") Reviewed-by: Christian Brauner <[email protected]> Signed-off-by: Jeff Layton <[email protected]> Signed-off-by: Chuck Lever <[email protected]>
1 parent 1116e0e commit 7e8ae84

File tree

5 files changed

+27
-10
lines changed

5 files changed

+27
-10
lines changed

fs/attr.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -489,9 +489,17 @@ int notify_change(struct mnt_idmap *idmap, struct dentry *dentry,
489489
error = security_inode_setattr(idmap, dentry, attr);
490490
if (error)
491491
return error;
492-
error = try_break_deleg(inode, delegated_inode);
493-
if (error)
494-
return error;
492+
493+
/*
494+
* If ATTR_DELEG is set, then these attributes are being set on
495+
* behalf of the holder of a write delegation. We want to avoid
496+
* breaking the delegation in this case.
497+
*/
498+
if (!(ia_valid & ATTR_DELEG)) {
499+
error = try_break_deleg(inode, delegated_inode);
500+
if (error)
501+
return error;
502+
}
495503

496504
if (inode->i_op->setattr)
497505
error = inode->i_op->setattr(idmap, dentry, attr);

fs/nfsd/nfs4state.c

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8815,7 +8815,7 @@ nfsd4_get_writestateid(struct nfsd4_compound_state *cstate,
88158815
/**
88168816
* nfsd4_deleg_getattr_conflict - Recall if GETATTR causes conflict
88178817
* @rqstp: RPC transaction context
8818-
* @inode: file to be checked for a conflict
8818+
* @dentry: dentry of inode to be checked for a conflict
88198819
* @modified: return true if file was modified
88208820
* @size: new size of file if modified is true
88218821
*
@@ -8830,7 +8830,7 @@ nfsd4_get_writestateid(struct nfsd4_compound_state *cstate,
88308830
* code is returned.
88318831
*/
88328832
__be32
8833-
nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode,
8833+
nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct dentry *dentry,
88348834
bool *modified, u64 *size)
88358835
{
88368836
__be32 status;
@@ -8839,6 +8839,7 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode,
88398839
struct file_lease *fl;
88408840
struct iattr attrs;
88418841
struct nfs4_cb_fattr *ncf;
8842+
struct inode *inode = d_inode(dentry);
88428843

88438844
*modified = false;
88448845
ctx = locks_inode_context(inode);
@@ -8890,15 +8891,22 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode,
88908891
ncf->ncf_cur_fsize != ncf->ncf_cb_fsize))
88918892
ncf->ncf_file_modified = true;
88928893
if (ncf->ncf_file_modified) {
8894+
int err;
8895+
88938896
/*
88948897
* Per section 10.4.3 of RFC 8881, the server would
88958898
* not update the file's metadata with the client's
88968899
* modified size
88978900
*/
88988901
attrs.ia_mtime = attrs.ia_ctime = current_time(inode);
8899-
attrs.ia_valid = ATTR_MTIME | ATTR_CTIME;
8900-
setattr_copy(&nop_mnt_idmap, inode, &attrs);
8901-
mark_inode_dirty(inode);
8902+
attrs.ia_valid = ATTR_MTIME | ATTR_CTIME | ATTR_DELEG;
8903+
inode_lock(inode);
8904+
err = notify_change(&nop_mnt_idmap, dentry, &attrs, NULL);
8905+
inode_unlock(inode);
8906+
if (err) {
8907+
nfs4_put_stid(&dp->dl_stid);
8908+
return nfserrno(err);
8909+
}
89028910
ncf->ncf_cur_fsize = ncf->ncf_cb_fsize;
89038911
*size = ncf->ncf_cur_fsize;
89048912
*modified = true;

fs/nfsd/nfs4xdr.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3565,7 +3565,7 @@ nfsd4_encode_fattr4(struct svc_rqst *rqstp, struct xdr_stream *xdr,
35653565
}
35663566
args.size = 0;
35673567
if (attrmask[0] & (FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE)) {
3568-
status = nfsd4_deleg_getattr_conflict(rqstp, d_inode(dentry),
3568+
status = nfsd4_deleg_getattr_conflict(rqstp, dentry,
35693569
&file_modified, &size);
35703570
if (status)
35713571
goto out;

fs/nfsd/state.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,5 +781,5 @@ static inline bool try_to_expire_client(struct nfs4_client *clp)
781781
}
782782

783783
extern __be32 nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp,
784-
struct inode *inode, bool *file_modified, u64 *size);
784+
struct dentry *dentry, bool *file_modified, u64 *size);
785785
#endif /* NFSD4_STATE_H */

include/linux/fs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
208208
#define ATTR_OPEN (1 << 15) /* Truncating from open(O_TRUNC) */
209209
#define ATTR_TIMES_SET (1 << 16)
210210
#define ATTR_TOUCH (1 << 17)
211+
#define ATTR_DELEG (1 << 18) /* Delegated attrs. Don't break write delegations */
211212

212213
/*
213214
* Whiteout is represented by a char device. The following constants define the

0 commit comments

Comments
 (0)