diff mbox series

[07/19] NFSv4: Add support for delegated atime and mtime attributes

Message ID 20240613041136.506908-8-trond.myklebust@hammerspace.com (mailing list archive)
State New
Headers show
Series OPEN optimisations and Attribute delegations | expand

Commit Message

Trond Myklebust June 13, 2024, 4:11 a.m. UTC
From: Trond Myklebust <trond.myklebust@primarydata.com>

Ensure that we update the mtime and atime correctly when we read
or write data to the file and when we truncate. Let the server manage
ctime on other attribute updates.

Signed-off-by: Trond Myklebust <trond.myklebust@primarydata.com>
Signed-off-by: Lance Shelton <lance.shelton@hammerspace.com>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
---
 fs/nfs/delegation.c | 24 +++++++++++++++-----
 fs/nfs/delegation.h | 25 +++++++++++++++++++--
 fs/nfs/inode.c      | 54 +++++++++++++++++++++++++++++++++++++++++----
 fs/nfs/nfs4proc.c   | 21 ++++++++++--------
 fs/nfs/read.c       |  3 +++
 fs/nfs/write.c      |  9 ++++++++
 6 files changed, 116 insertions(+), 20 deletions(-)

Comments

Anna Schumaker June 13, 2024, 8:26 p.m. UTC | #1
Hi Trond,

On Thu, Jun 13, 2024 at 12:17 AM <trondmy@gmail.com> wrote:
>
> From: Trond Myklebust <trond.myklebust@primarydata.com>
>
> Ensure that we update the mtime and atime correctly when we read
> or write data to the file and when we truncate. Let the server manage
> ctime on other attribute updates.
>
> Signed-off-by: Trond Myklebust <trond.myklebust@primarydata.com>
> Signed-off-by: Lance Shelton <lance.shelton@hammerspace.com>
> Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
> ---
>  fs/nfs/delegation.c | 24 +++++++++++++++-----
>  fs/nfs/delegation.h | 25 +++++++++++++++++++--
>  fs/nfs/inode.c      | 54 +++++++++++++++++++++++++++++++++++++++++----
>  fs/nfs/nfs4proc.c   | 21 ++++++++++--------
>  fs/nfs/read.c       |  3 +++
>  fs/nfs/write.c      |  9 ++++++++
>  6 files changed, 116 insertions(+), 20 deletions(-)
>
> diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
> index 6fdffd25cb2b..e72eead06c08 100644
> --- a/fs/nfs/delegation.c
> +++ b/fs/nfs/delegation.c
> @@ -115,6 +115,9 @@ static int nfs4_do_check_delegation(struct inode *inode, fmode_t type,
>                 if (mark)
>                         nfs_mark_delegation_referenced(delegation);
>                 ret = 1;
> +               if ((flags & NFS_DELEGATION_FLAG_TIME) &&
> +                   !test_bit(NFS_DELEGATION_DELEGTIME, &delegation->flags))
> +                       ret = 0;
>         }
>         rcu_read_unlock();
>         return ret;
> @@ -221,11 +224,12 @@ static int nfs_delegation_claim_opens(struct inode *inode,
>   * @type: delegation type
>   * @stateid: delegation stateid
>   * @pagemod_limit: write delegation "space_limit"
> + * @deleg_type: raw delegation type
>   *
>   */
>  void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
>                                   fmode_t type, const nfs4_stateid *stateid,
> -                                 unsigned long pagemod_limit)
> +                                 unsigned long pagemod_limit, u32 deleg_type)
>  {
>         struct nfs_delegation *delegation;
>         const struct cred *oldcred = NULL;
> @@ -250,7 +254,7 @@ void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
>         } else {
>                 rcu_read_unlock();
>                 nfs_inode_set_delegation(inode, cred, type, stateid,
> -                                        pagemod_limit);
> +                                        pagemod_limit, deleg_type);
>         }
>  }
>
> @@ -418,13 +422,13 @@ nfs_update_inplace_delegation(struct nfs_delegation *delegation,
>   * @type: delegation type
>   * @stateid: delegation stateid
>   * @pagemod_limit: write delegation "space_limit"
> + * @deleg_type: raw delegation type
>   *
>   * Returns zero on success, or a negative errno value.
>   */
>  int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
> -                                 fmode_t type,
> -                                 const nfs4_stateid *stateid,
> -                                 unsigned long pagemod_limit)
> +                            fmode_t type, const nfs4_stateid *stateid,
> +                            unsigned long pagemod_limit, u32 deleg_type)
>  {
>         struct nfs_server *server = NFS_SERVER(inode);
>         struct nfs_client *clp = server->nfs_client;
> @@ -444,6 +448,11 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
>         delegation->cred = get_cred(cred);
>         delegation->inode = inode;
>         delegation->flags = 1<<NFS_DELEGATION_REFERENCED;
> +       switch (deleg_type) {
> +       case NFS4_OPEN_DELEGATE_READ_ATTRS_DELEG:
> +       case NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG:
> +               delegation->flags |= BIT(NFS_DELEGATION_DELEGTIME);
> +       }
>         delegation->test_gen = 0;
>         spin_lock_init(&delegation->lock);
>
> @@ -508,6 +517,11 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
>         atomic_long_inc(&nfs_active_delegations);
>
>         trace_nfs4_set_delegation(inode, type);
> +
> +       /* If we hold writebacks and have delegated mtime then update */
> +       if (deleg_type == NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG &&
> +           nfs_have_writebacks(inode))
> +               nfs_update_delegated_mtime(inode);
>  out:
>         spin_unlock(&clp->cl_lock);
>         if (delegation != NULL)
> diff --git a/fs/nfs/delegation.h b/fs/nfs/delegation.h
> index 257b3d726043..2e9ad83acf2c 100644
> --- a/fs/nfs/delegation.h
> +++ b/fs/nfs/delegation.h
> @@ -38,12 +38,17 @@ enum {
>         NFS_DELEGATION_TEST_EXPIRED,
>         NFS_DELEGATION_INODE_FREEING,
>         NFS_DELEGATION_RETURN_DELAYED,
> +       NFS_DELEGATION_DELEGTIME,
>  };
>
> +#define NFS_DELEGATION_FLAG_TIME       BIT(1)

This flag is defined under an "#if IS_ENABLED(CONFIG_NFS_V4)" guard,
so it doesn't exist for the functions farther down in this file when
v4 is disabled.

Anna

> +
>  int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
> -               fmode_t type, const nfs4_stateid *stateid, unsigned long pagemod_limit);
> +                            fmode_t type, const nfs4_stateid *stateid,
> +                            unsigned long pagemod_limit, u32 deleg_type);
>  void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
> -               fmode_t type, const nfs4_stateid *stateid, unsigned long pagemod_limit);
> +                                 fmode_t type, const nfs4_stateid *stateid,
> +                                 unsigned long pagemod_limit, u32 deleg_type);
>  int nfs4_inode_return_delegation(struct inode *inode);
>  void nfs4_inode_return_delegation_on_close(struct inode *inode);
>  int nfs_async_inode_return_delegation(struct inode *inode, const nfs4_stateid *stateid);
> @@ -84,6 +89,10 @@ int nfs4_inode_make_writeable(struct inode *inode);
>
>  #endif
>
> +void nfs_update_delegated_atime(struct inode *inode);
> +void nfs_update_delegated_mtime(struct inode *inode);
> +void nfs_update_delegated_mtime_locked(struct inode *inode);
> +
>  static inline int nfs_have_read_or_write_delegation(struct inode *inode)
>  {
>         return NFS_PROTO(inode)->have_delegation(inode, FMODE_READ, 0);
> @@ -99,4 +108,16 @@ static inline int nfs_have_delegated_attributes(struct inode *inode)
>         return NFS_PROTO(inode)->have_delegation(inode, FMODE_READ, 0);
>  }
>
> +static inline int nfs_have_delegated_atime(struct inode *inode)
> +{
> +       return NFS_PROTO(inode)->have_delegation(inode, FMODE_READ,
> +                                                NFS_DELEGATION_FLAG_TIME);
> +}
> +
> +static inline int nfs_have_delegated_mtime(struct inode *inode)
> +{
> +       return NFS_PROTO(inode)->have_delegation(inode, FMODE_WRITE,
> +                                                NFS_DELEGATION_FLAG_TIME);
> +}
> +
>  #endif
> diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> index 89722919b463..91c0aeaf6c1e 100644
> --- a/fs/nfs/inode.c
> +++ b/fs/nfs/inode.c
> @@ -275,6 +275,8 @@ EXPORT_SYMBOL_GPL(nfs_zap_acl_cache);
>
>  void nfs_invalidate_atime(struct inode *inode)
>  {
> +       if (nfs_have_delegated_atime(inode))
> +               return;
>         spin_lock(&inode->i_lock);
>         nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATIME);
>         spin_unlock(&inode->i_lock);
> @@ -603,6 +605,33 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
>  }
>  EXPORT_SYMBOL_GPL(nfs_fhget);
>
> +void nfs_update_delegated_atime(struct inode *inode)
> +{
> +       spin_lock(&inode->i_lock);
> +       if (nfs_have_delegated_atime(inode)) {
> +               inode_update_timestamps(inode, S_ATIME);
> +               NFS_I(inode)->cache_validity &= ~NFS_INO_INVALID_ATIME;
> +       }
> +       spin_unlock(&inode->i_lock);
> +}
> +
> +void nfs_update_delegated_mtime_locked(struct inode *inode)
> +{
> +       if (nfs_have_delegated_mtime(inode)) {
> +               inode_update_timestamps(inode, S_CTIME | S_MTIME);
> +               NFS_I(inode)->cache_validity &= ~(NFS_INO_INVALID_CTIME |
> +                                                 NFS_INO_INVALID_MTIME);
> +       }
> +}
> +
> +void nfs_update_delegated_mtime(struct inode *inode)
> +{
> +       spin_lock(&inode->i_lock);
> +       nfs_update_delegated_mtime_locked(inode);
> +       spin_unlock(&inode->i_lock);
> +}
> +EXPORT_SYMBOL_GPL(nfs_update_delegated_mtime);
> +
>  #define NFS_VALID_ATTRS (ATTR_MODE|ATTR_UID|ATTR_GID|ATTR_SIZE|ATTR_ATIME|ATTR_ATIME_SET|ATTR_MTIME|ATTR_MTIME_SET|ATTR_FILE|ATTR_OPEN)
>
>  int
> @@ -630,6 +659,17 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
>                         attr->ia_valid &= ~ATTR_SIZE;
>         }
>
> +       if (nfs_have_delegated_mtime(inode)) {
> +               if (attr->ia_valid & ATTR_MTIME) {
> +                       nfs_update_delegated_mtime(inode);
> +                       attr->ia_valid &= ~ATTR_MTIME;
> +               }
> +               if (attr->ia_valid & ATTR_ATIME) {
> +                       nfs_update_delegated_atime(inode);
> +                       attr->ia_valid &= ~ATTR_ATIME;
> +               }
> +       }
> +
>         /* Optimization: if the end result is no change, don't RPC */
>         if (((attr->ia_valid & NFS_VALID_ATTRS) & ~(ATTR_FILE|ATTR_OPEN)) == 0)
>                 return 0;
> @@ -685,6 +725,7 @@ static int nfs_vmtruncate(struct inode * inode, loff_t offset)
>
>         spin_unlock(&inode->i_lock);
>         truncate_pagecache(inode, offset);
> +       nfs_update_delegated_mtime_locked(inode);
>         spin_lock(&inode->i_lock);
>  out:
>         return err;
> @@ -708,8 +749,9 @@ void nfs_setattr_update_inode(struct inode *inode, struct iattr *attr,
>         spin_lock(&inode->i_lock);
>         NFS_I(inode)->attr_gencount = fattr->gencount;
>         if ((attr->ia_valid & ATTR_SIZE) != 0) {
> -               nfs_set_cache_invalid(inode, NFS_INO_INVALID_MTIME |
> -                                                    NFS_INO_INVALID_BLOCKS);
> +               if (!nfs_have_delegated_mtime(inode))
> +                       nfs_set_cache_invalid(inode, NFS_INO_INVALID_MTIME);
> +               nfs_set_cache_invalid(inode, NFS_INO_INVALID_BLOCKS);
>                 nfs_inc_stats(inode, NFSIOS_SETATTRTRUNC);
>                 nfs_vmtruncate(inode, attr->ia_size);
>         }
> @@ -855,8 +897,12 @@ int nfs_getattr(struct mnt_idmap *idmap, const struct path *path,
>
>         /* Flush out writes to the server in order to update c/mtime/version.  */
>         if ((request_mask & (STATX_CTIME | STATX_MTIME | STATX_CHANGE_COOKIE)) &&
> -           S_ISREG(inode->i_mode))
> -               filemap_write_and_wait(inode->i_mapping);
> +           S_ISREG(inode->i_mode)) {
> +               if (nfs_have_delegated_mtime(inode))
> +                       filemap_fdatawrite(inode->i_mapping);
> +               else
> +                       filemap_write_and_wait(inode->i_mapping);
> +       }
>
>         /*
>          * We may force a getattr if the user cares about atime.
> diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
> index 27fb40653f1d..83edbc7a3bcc 100644
> --- a/fs/nfs/nfs4proc.c
> +++ b/fs/nfs/nfs4proc.c
> @@ -1245,7 +1245,8 @@ nfs4_update_changeattr_locked(struct inode *inode,
>         struct nfs_inode *nfsi = NFS_I(inode);
>         u64 change_attr = inode_peek_iversion_raw(inode);
>
> -       cache_validity |= NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME;
> +       if (!nfs_have_delegated_mtime(inode))
> +               cache_validity |= NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME;
>         if (S_ISDIR(inode->i_mode))
>                 cache_validity |= NFS_INO_INVALID_DATA;
>
> @@ -1961,6 +1962,8 @@ nfs4_process_delegation(struct inode *inode, const struct cred *cred,
>         switch (delegation->open_delegation_type) {
>         case NFS4_OPEN_DELEGATE_READ:
>         case NFS4_OPEN_DELEGATE_WRITE:
> +       case NFS4_OPEN_DELEGATE_READ_ATTRS_DELEG:
> +       case NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG:
>                 break;
>         default:
>                 return;
> @@ -1974,16 +1977,16 @@ nfs4_process_delegation(struct inode *inode, const struct cred *cred,
>                                    NFS_SERVER(inode)->nfs_client->cl_hostname);
>                 break;
>         case NFS4_OPEN_CLAIM_PREVIOUS:
> -               nfs_inode_reclaim_delegation(inode, cred,
> -                               delegation->type,
> -                               &delegation->stateid,
> -                               delegation->pagemod_limit);
> +               nfs_inode_reclaim_delegation(inode, cred, delegation->type,
> +                                            &delegation->stateid,
> +                                            delegation->pagemod_limit,
> +                                            delegation->open_delegation_type);
>                 break;
>         default:
> -               nfs_inode_set_delegation(inode, cred,
> -                               delegation->type,
> -                               &delegation->stateid,
> -                               delegation->pagemod_limit);
> +               nfs_inode_set_delegation(inode, cred, delegation->type,
> +                                        &delegation->stateid,
> +                                        delegation->pagemod_limit,
> +                                        delegation->open_delegation_type);
>         }
>         if (delegation->do_recall)
>                 nfs_async_inode_return_delegation(inode, &delegation->stateid);
> diff --git a/fs/nfs/read.c b/fs/nfs/read.c
> index a142287d86f6..1b0e06c11983 100644
> --- a/fs/nfs/read.c
> +++ b/fs/nfs/read.c
> @@ -28,6 +28,7 @@
>  #include "fscache.h"
>  #include "pnfs.h"
>  #include "nfstrace.h"
> +#include "delegation.h"
>
>  #define NFSDBG_FACILITY                NFSDBG_PAGECACHE
>
> @@ -372,6 +373,7 @@ int nfs_read_folio(struct file *file, struct folio *folio)
>                 goto out_put;
>
>         nfs_pageio_complete_read(&pgio);
> +       nfs_update_delegated_atime(inode);
>         ret = pgio.pg_error < 0 ? pgio.pg_error : 0;
>         if (!ret) {
>                 ret = folio_wait_locked_killable(folio);
> @@ -428,6 +430,7 @@ void nfs_readahead(struct readahead_control *ractl)
>         }
>
>         nfs_pageio_complete_read(&pgio);
> +       nfs_update_delegated_atime(inode);
>
>         put_nfs_open_context(ctx);
>  out:
> diff --git a/fs/nfs/write.c b/fs/nfs/write.c
> index a9186b113fe7..c0cd644b97ff 100644
> --- a/fs/nfs/write.c
> +++ b/fs/nfs/write.c
> @@ -289,6 +289,8 @@ static void nfs_grow_file(struct folio *folio, unsigned int offset,
>         NFS_I(inode)->cache_validity &= ~NFS_INO_INVALID_SIZE;
>         nfs_inc_stats(inode, NFSIOS_EXTENDWRITE);
>  out:
> +       /* Atomically update timestamps if they are delegated to us. */
> +       nfs_update_delegated_mtime_locked(inode);
>         spin_unlock(&inode->i_lock);
>         nfs_fscache_invalidate(inode, 0);
>  }
> @@ -1514,6 +1516,13 @@ void nfs_writeback_update_inode(struct nfs_pgio_header *hdr)
>         struct nfs_fattr *fattr = &hdr->fattr;
>         struct inode *inode = hdr->inode;
>
> +       if (nfs_have_delegated_mtime(inode)) {
> +               spin_lock(&inode->i_lock);
> +               nfs_set_cache_invalid(inode, NFS_INO_INVALID_BLOCKS);
> +               spin_unlock(&inode->i_lock);
> +               return;
> +       }
> +
>         spin_lock(&inode->i_lock);
>         nfs_writeback_check_extend(hdr, fattr);
>         nfs_post_op_update_inode_force_wcc_locked(inode, fattr);
> --
> 2.45.2
>
>
diff mbox series

Patch

diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c
index 6fdffd25cb2b..e72eead06c08 100644
--- a/fs/nfs/delegation.c
+++ b/fs/nfs/delegation.c
@@ -115,6 +115,9 @@  static int nfs4_do_check_delegation(struct inode *inode, fmode_t type,
 		if (mark)
 			nfs_mark_delegation_referenced(delegation);
 		ret = 1;
+		if ((flags & NFS_DELEGATION_FLAG_TIME) &&
+		    !test_bit(NFS_DELEGATION_DELEGTIME, &delegation->flags))
+			ret = 0;
 	}
 	rcu_read_unlock();
 	return ret;
@@ -221,11 +224,12 @@  static int nfs_delegation_claim_opens(struct inode *inode,
  * @type: delegation type
  * @stateid: delegation stateid
  * @pagemod_limit: write delegation "space_limit"
+ * @deleg_type: raw delegation type
  *
  */
 void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
 				  fmode_t type, const nfs4_stateid *stateid,
-				  unsigned long pagemod_limit)
+				  unsigned long pagemod_limit, u32 deleg_type)
 {
 	struct nfs_delegation *delegation;
 	const struct cred *oldcred = NULL;
@@ -250,7 +254,7 @@  void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
 	} else {
 		rcu_read_unlock();
 		nfs_inode_set_delegation(inode, cred, type, stateid,
-					 pagemod_limit);
+					 pagemod_limit, deleg_type);
 	}
 }
 
@@ -418,13 +422,13 @@  nfs_update_inplace_delegation(struct nfs_delegation *delegation,
  * @type: delegation type
  * @stateid: delegation stateid
  * @pagemod_limit: write delegation "space_limit"
+ * @deleg_type: raw delegation type
  *
  * Returns zero on success, or a negative errno value.
  */
 int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
-				  fmode_t type,
-				  const nfs4_stateid *stateid,
-				  unsigned long pagemod_limit)
+			     fmode_t type, const nfs4_stateid *stateid,
+			     unsigned long pagemod_limit, u32 deleg_type)
 {
 	struct nfs_server *server = NFS_SERVER(inode);
 	struct nfs_client *clp = server->nfs_client;
@@ -444,6 +448,11 @@  int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
 	delegation->cred = get_cred(cred);
 	delegation->inode = inode;
 	delegation->flags = 1<<NFS_DELEGATION_REFERENCED;
+	switch (deleg_type) {
+	case NFS4_OPEN_DELEGATE_READ_ATTRS_DELEG:
+	case NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG:
+		delegation->flags |= BIT(NFS_DELEGATION_DELEGTIME);
+	}
 	delegation->test_gen = 0;
 	spin_lock_init(&delegation->lock);
 
@@ -508,6 +517,11 @@  int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
 	atomic_long_inc(&nfs_active_delegations);
 
 	trace_nfs4_set_delegation(inode, type);
+
+	/* If we hold writebacks and have delegated mtime then update */
+	if (deleg_type == NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG &&
+	    nfs_have_writebacks(inode))
+		nfs_update_delegated_mtime(inode);
 out:
 	spin_unlock(&clp->cl_lock);
 	if (delegation != NULL)
diff --git a/fs/nfs/delegation.h b/fs/nfs/delegation.h
index 257b3d726043..2e9ad83acf2c 100644
--- a/fs/nfs/delegation.h
+++ b/fs/nfs/delegation.h
@@ -38,12 +38,17 @@  enum {
 	NFS_DELEGATION_TEST_EXPIRED,
 	NFS_DELEGATION_INODE_FREEING,
 	NFS_DELEGATION_RETURN_DELAYED,
+	NFS_DELEGATION_DELEGTIME,
 };
 
+#define NFS_DELEGATION_FLAG_TIME	BIT(1)
+
 int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
-		fmode_t type, const nfs4_stateid *stateid, unsigned long pagemod_limit);
+			     fmode_t type, const nfs4_stateid *stateid,
+			     unsigned long pagemod_limit, u32 deleg_type);
 void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
-		fmode_t type, const nfs4_stateid *stateid, unsigned long pagemod_limit);
+				  fmode_t type, const nfs4_stateid *stateid,
+				  unsigned long pagemod_limit, u32 deleg_type);
 int nfs4_inode_return_delegation(struct inode *inode);
 void nfs4_inode_return_delegation_on_close(struct inode *inode);
 int nfs_async_inode_return_delegation(struct inode *inode, const nfs4_stateid *stateid);
@@ -84,6 +89,10 @@  int nfs4_inode_make_writeable(struct inode *inode);
 
 #endif
 
+void nfs_update_delegated_atime(struct inode *inode);
+void nfs_update_delegated_mtime(struct inode *inode);
+void nfs_update_delegated_mtime_locked(struct inode *inode);
+
 static inline int nfs_have_read_or_write_delegation(struct inode *inode)
 {
 	return NFS_PROTO(inode)->have_delegation(inode, FMODE_READ, 0);
@@ -99,4 +108,16 @@  static inline int nfs_have_delegated_attributes(struct inode *inode)
 	return NFS_PROTO(inode)->have_delegation(inode, FMODE_READ, 0);
 }
 
+static inline int nfs_have_delegated_atime(struct inode *inode)
+{
+	return NFS_PROTO(inode)->have_delegation(inode, FMODE_READ,
+						 NFS_DELEGATION_FLAG_TIME);
+}
+
+static inline int nfs_have_delegated_mtime(struct inode *inode)
+{
+	return NFS_PROTO(inode)->have_delegation(inode, FMODE_WRITE,
+						 NFS_DELEGATION_FLAG_TIME);
+}
+
 #endif
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 89722919b463..91c0aeaf6c1e 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -275,6 +275,8 @@  EXPORT_SYMBOL_GPL(nfs_zap_acl_cache);
 
 void nfs_invalidate_atime(struct inode *inode)
 {
+	if (nfs_have_delegated_atime(inode))
+		return;
 	spin_lock(&inode->i_lock);
 	nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATIME);
 	spin_unlock(&inode->i_lock);
@@ -603,6 +605,33 @@  nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
 }
 EXPORT_SYMBOL_GPL(nfs_fhget);
 
+void nfs_update_delegated_atime(struct inode *inode)
+{
+	spin_lock(&inode->i_lock);
+	if (nfs_have_delegated_atime(inode)) {
+		inode_update_timestamps(inode, S_ATIME);
+		NFS_I(inode)->cache_validity &= ~NFS_INO_INVALID_ATIME;
+	}
+	spin_unlock(&inode->i_lock);
+}
+
+void nfs_update_delegated_mtime_locked(struct inode *inode)
+{
+	if (nfs_have_delegated_mtime(inode)) {
+		inode_update_timestamps(inode, S_CTIME | S_MTIME);
+		NFS_I(inode)->cache_validity &= ~(NFS_INO_INVALID_CTIME |
+						  NFS_INO_INVALID_MTIME);
+	}
+}
+
+void nfs_update_delegated_mtime(struct inode *inode)
+{
+	spin_lock(&inode->i_lock);
+	nfs_update_delegated_mtime_locked(inode);
+	spin_unlock(&inode->i_lock);
+}
+EXPORT_SYMBOL_GPL(nfs_update_delegated_mtime);
+
 #define NFS_VALID_ATTRS (ATTR_MODE|ATTR_UID|ATTR_GID|ATTR_SIZE|ATTR_ATIME|ATTR_ATIME_SET|ATTR_MTIME|ATTR_MTIME_SET|ATTR_FILE|ATTR_OPEN)
 
 int
@@ -630,6 +659,17 @@  nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 			attr->ia_valid &= ~ATTR_SIZE;
 	}
 
+	if (nfs_have_delegated_mtime(inode)) {
+		if (attr->ia_valid & ATTR_MTIME) {
+			nfs_update_delegated_mtime(inode);
+			attr->ia_valid &= ~ATTR_MTIME;
+		}
+		if (attr->ia_valid & ATTR_ATIME) {
+			nfs_update_delegated_atime(inode);
+			attr->ia_valid &= ~ATTR_ATIME;
+		}
+	}
+
 	/* Optimization: if the end result is no change, don't RPC */
 	if (((attr->ia_valid & NFS_VALID_ATTRS) & ~(ATTR_FILE|ATTR_OPEN)) == 0)
 		return 0;
@@ -685,6 +725,7 @@  static int nfs_vmtruncate(struct inode * inode, loff_t offset)
 
 	spin_unlock(&inode->i_lock);
 	truncate_pagecache(inode, offset);
+	nfs_update_delegated_mtime_locked(inode);
 	spin_lock(&inode->i_lock);
 out:
 	return err;
@@ -708,8 +749,9 @@  void nfs_setattr_update_inode(struct inode *inode, struct iattr *attr,
 	spin_lock(&inode->i_lock);
 	NFS_I(inode)->attr_gencount = fattr->gencount;
 	if ((attr->ia_valid & ATTR_SIZE) != 0) {
-		nfs_set_cache_invalid(inode, NFS_INO_INVALID_MTIME |
-						     NFS_INO_INVALID_BLOCKS);
+		if (!nfs_have_delegated_mtime(inode))
+			nfs_set_cache_invalid(inode, NFS_INO_INVALID_MTIME);
+		nfs_set_cache_invalid(inode, NFS_INO_INVALID_BLOCKS);
 		nfs_inc_stats(inode, NFSIOS_SETATTRTRUNC);
 		nfs_vmtruncate(inode, attr->ia_size);
 	}
@@ -855,8 +897,12 @@  int nfs_getattr(struct mnt_idmap *idmap, const struct path *path,
 
 	/* Flush out writes to the server in order to update c/mtime/version.  */
 	if ((request_mask & (STATX_CTIME | STATX_MTIME | STATX_CHANGE_COOKIE)) &&
-	    S_ISREG(inode->i_mode))
-		filemap_write_and_wait(inode->i_mapping);
+	    S_ISREG(inode->i_mode)) {
+		if (nfs_have_delegated_mtime(inode))
+			filemap_fdatawrite(inode->i_mapping);
+		else
+			filemap_write_and_wait(inode->i_mapping);
+	}
 
 	/*
 	 * We may force a getattr if the user cares about atime.
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 27fb40653f1d..83edbc7a3bcc 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -1245,7 +1245,8 @@  nfs4_update_changeattr_locked(struct inode *inode,
 	struct nfs_inode *nfsi = NFS_I(inode);
 	u64 change_attr = inode_peek_iversion_raw(inode);
 
-	cache_validity |= NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME;
+	if (!nfs_have_delegated_mtime(inode))
+		cache_validity |= NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME;
 	if (S_ISDIR(inode->i_mode))
 		cache_validity |= NFS_INO_INVALID_DATA;
 
@@ -1961,6 +1962,8 @@  nfs4_process_delegation(struct inode *inode, const struct cred *cred,
 	switch (delegation->open_delegation_type) {
 	case NFS4_OPEN_DELEGATE_READ:
 	case NFS4_OPEN_DELEGATE_WRITE:
+	case NFS4_OPEN_DELEGATE_READ_ATTRS_DELEG:
+	case NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG:
 		break;
 	default:
 		return;
@@ -1974,16 +1977,16 @@  nfs4_process_delegation(struct inode *inode, const struct cred *cred,
 				   NFS_SERVER(inode)->nfs_client->cl_hostname);
 		break;
 	case NFS4_OPEN_CLAIM_PREVIOUS:
-		nfs_inode_reclaim_delegation(inode, cred,
-				delegation->type,
-				&delegation->stateid,
-				delegation->pagemod_limit);
+		nfs_inode_reclaim_delegation(inode, cred, delegation->type,
+					     &delegation->stateid,
+					     delegation->pagemod_limit,
+					     delegation->open_delegation_type);
 		break;
 	default:
-		nfs_inode_set_delegation(inode, cred,
-				delegation->type,
-				&delegation->stateid,
-				delegation->pagemod_limit);
+		nfs_inode_set_delegation(inode, cred, delegation->type,
+					 &delegation->stateid,
+					 delegation->pagemod_limit,
+					 delegation->open_delegation_type);
 	}
 	if (delegation->do_recall)
 		nfs_async_inode_return_delegation(inode, &delegation->stateid);
diff --git a/fs/nfs/read.c b/fs/nfs/read.c
index a142287d86f6..1b0e06c11983 100644
--- a/fs/nfs/read.c
+++ b/fs/nfs/read.c
@@ -28,6 +28,7 @@ 
 #include "fscache.h"
 #include "pnfs.h"
 #include "nfstrace.h"
+#include "delegation.h"
 
 #define NFSDBG_FACILITY		NFSDBG_PAGECACHE
 
@@ -372,6 +373,7 @@  int nfs_read_folio(struct file *file, struct folio *folio)
 		goto out_put;
 
 	nfs_pageio_complete_read(&pgio);
+	nfs_update_delegated_atime(inode);
 	ret = pgio.pg_error < 0 ? pgio.pg_error : 0;
 	if (!ret) {
 		ret = folio_wait_locked_killable(folio);
@@ -428,6 +430,7 @@  void nfs_readahead(struct readahead_control *ractl)
 	}
 
 	nfs_pageio_complete_read(&pgio);
+	nfs_update_delegated_atime(inode);
 
 	put_nfs_open_context(ctx);
 out:
diff --git a/fs/nfs/write.c b/fs/nfs/write.c
index a9186b113fe7..c0cd644b97ff 100644
--- a/fs/nfs/write.c
+++ b/fs/nfs/write.c
@@ -289,6 +289,8 @@  static void nfs_grow_file(struct folio *folio, unsigned int offset,
 	NFS_I(inode)->cache_validity &= ~NFS_INO_INVALID_SIZE;
 	nfs_inc_stats(inode, NFSIOS_EXTENDWRITE);
 out:
+	/* Atomically update timestamps if they are delegated to us. */
+	nfs_update_delegated_mtime_locked(inode);
 	spin_unlock(&inode->i_lock);
 	nfs_fscache_invalidate(inode, 0);
 }
@@ -1514,6 +1516,13 @@  void nfs_writeback_update_inode(struct nfs_pgio_header *hdr)
 	struct nfs_fattr *fattr = &hdr->fattr;
 	struct inode *inode = hdr->inode;
 
+	if (nfs_have_delegated_mtime(inode)) {
+		spin_lock(&inode->i_lock);
+		nfs_set_cache_invalid(inode, NFS_INO_INVALID_BLOCKS);
+		spin_unlock(&inode->i_lock);
+		return;
+	}
+
 	spin_lock(&inode->i_lock);
 	nfs_writeback_check_extend(hdr, fattr);
 	nfs_post_op_update_inode_force_wcc_locked(inode, fattr);