NFSv4: Add support for delegated atime and mtime attributes
authorTrond Myklebust <trond.myklebust@primarydata.com>
Mon, 17 Jun 2024 01:21:25 +0000 (21:21 -0400)
committerAnna Schumaker <Anna.Schumaker@Netapp.com>
Mon, 8 Jul 2024 17:47:25 +0000 (13:47 -0400)
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>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com>
fs/nfs/delegation.c
fs/nfs/delegation.h
fs/nfs/inode.c
fs/nfs/nfs4proc.c
fs/nfs/read.c
fs/nfs/write.c

index 6fdffd25cb2b1850d11f1166e61af01a29f4724e..d9117630e062057f2f33a24ce48ff66e68e1f6f4 100644 (file)
@@ -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 @@ again:
  * @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;
@@ -239,6 +243,14 @@ void nfs_inode_reclaim_delegation(struct inode *inode, const struct cred *cred,
                delegation->pagemod_limit = pagemod_limit;
                oldcred = delegation->cred;
                delegation->cred = get_cred(cred);
+               switch (deleg_type) {
+               case NFS4_OPEN_DELEGATE_READ_ATTRS_DELEG:
+               case NFS4_OPEN_DELEGATE_WRITE_ATTRS_DELEG:
+                       set_bit(NFS_DELEGATION_DELEGTIME, &delegation->flags);
+                       break;
+               default:
+                       clear_bit(NFS_DELEGATION_DELEGTIME, &delegation->flags);
+               }
                clear_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags);
                if (test_and_clear_bit(NFS_DELEGATION_REVOKED,
                                       &delegation->flags))
@@ -250,7 +262,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 +430,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 +456,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 +525,11 @@ add_new:
        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)
index 257b3d7260435fe70bd558f37a0ad6c2ef561be2..001551e2ab6092f016285d8023dcf9869a937b24 100644 (file)
@@ -38,12 +38,15 @@ enum {
        NFS_DELEGATION_TEST_EXPIRED,
        NFS_DELEGATION_INODE_FREEING,
        NFS_DELEGATION_RETURN_DELAYED,
+       NFS_DELEGATION_DELEGTIME,
 };
 
 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 +87,12 @@ int nfs4_inode_make_writeable(struct inode *inode);
 
 #endif
 
+#define NFS_DELEGATION_FLAG_TIME       BIT(1)
+
+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
index f1bfe453aa84d8fb72f8f934a7aa97526a3a02a8..6185e6e158201b6aa606e1b3d7b075d8c28e524c 100644 (file)
@@ -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);
@@ -604,6 +606,33 @@ out_no_inode:
 }
 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
@@ -631,6 +660,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;
@@ -686,6 +726,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;
@@ -709,8 +750,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);
        }
@@ -856,8 +898,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.
index 4455ee510c2f93eb39ab6cb57745f74f00293820..f4215dcf36149a36fd0a808bac445f863fcf169a 100644 (file)
@@ -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);
index a142287d86f68ed411dce6f9c410e41948c570b2..1b0e06c11983cb462ad47864c332396ed993770c 100644 (file)
@@ -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:
index be19ac3110d15b0e27b401be2db496e4c9c132b2..f5414c96381ac87abb9286dbc4dc0b7f7fbd43e4 100644 (file)
@@ -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);