fs/nfsd: fix update of inode attrs in CB_GETATTR
authorJeff Layton <jlayton@kernel.org>
Mon, 26 Aug 2024 14:32:34 +0000 (10:32 -0400)
committerChuck Lever <chuck.lever@oracle.com>
Mon, 26 Aug 2024 23:04:00 +0000 (19:04 -0400)
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: c5967721e106 ("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>
fs/attr.c
fs/nfsd/nfs4state.c
fs/nfsd/nfs4xdr.c
fs/nfsd/state.h
include/linux/fs.h

index 960a310581ebb9e00d77b323ad812a2fc34ada1b..0dbf43b6555c83b3127e0630450198d067731216 100644 (file)
--- a/fs/attr.c
+++ b/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);
index 02d43f95146eef128be689cbdcf87846816d5406..07f2496850c4c63e0e0bd765160da71e10aacedb 100644 (file)
@@ -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;
index 43ccf6119cf128e8e62ccd75c4e68a2736fd907d..97f583777972618f831e8bdec953c9218a00c00c 100644 (file)
@@ -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;
index ffc217099d19135c8e534622dd5bc9229704d58e..ec4559ecd193b28852911fdf851f5d910db31723 100644 (file)
@@ -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 */
index 0283cf366c2a93fd0799e7acf14973b93fb64301..bafc1d134b9486e628d4f527c41326c4152d09a1 100644 (file)
@@ -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