Merge tag 'nfsd-6.9-1' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux
[sfrench/cifs-2.6.git] / fs / nfsd / nfs4state.c
index 7d6c657e0409ddc62567554304e4a51779dd2934..2391ab3c3231975bde1606875339b7736975054f 100644 (file)
@@ -87,6 +87,7 @@ static void nfs4_free_ol_stateid(struct nfs4_stid *stid);
 void nfsd4_end_grace(struct nfsd_net *nn);
 static void _free_cpntf_state_locked(struct nfsd_net *nn, struct nfs4_cpntf_state *cps);
 static void nfsd4_file_hash_remove(struct nfs4_file *fi);
+static void deleg_reaper(struct nfsd_net *nn);
 
 /* Locking: */
 
@@ -127,6 +128,7 @@ static void free_session(struct nfsd4_session *);
 
 static const struct nfsd4_callback_ops nfsd4_cb_recall_ops;
 static const struct nfsd4_callback_ops nfsd4_cb_notify_lock_ops;
+static const struct nfsd4_callback_ops nfsd4_cb_getattr_ops;
 
 static struct workqueue_struct *laundry_wq;
 
@@ -318,6 +320,7 @@ free_nbl(struct kref *kref)
        struct nfsd4_blocked_lock *nbl;
 
        nbl = container_of(kref, struct nfsd4_blocked_lock, nbl_kref);
+       locks_release_private(&nbl->nbl_lock);
        kfree(nbl);
 }
 
@@ -325,7 +328,6 @@ static void
 free_blocked_lock(struct nfsd4_blocked_lock *nbl)
 {
        locks_delete_block(&nbl->nbl_lock);
-       locks_release_private(&nbl->nbl_lock);
        kref_put(&nbl->nbl_kref, free_nbl);
 }
 
@@ -1189,6 +1191,10 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp,
        dp->dl_recalled = false;
        nfsd4_init_cb(&dp->dl_recall, dp->dl_stid.sc_client,
                      &nfsd4_cb_recall_ops, NFSPROC4_CLNT_CB_RECALL);
+       nfsd4_init_cb(&dp->dl_cb_fattr.ncf_getattr, dp->dl_stid.sc_client,
+                       &nfsd4_cb_getattr_ops, NFSPROC4_CLNT_CB_GETATTR);
+       dp->dl_cb_fattr.ncf_file_modified = false;
+       dp->dl_cb_fattr.ncf_cb_bmap[0] = FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE;
        get_nfs4_file(fp);
        dp->dl_stid.sc_file = fp;
        return dp;
@@ -1210,6 +1216,8 @@ nfs4_put_stid(struct nfs4_stid *s)
                return;
        }
        idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id);
+       if (s->sc_status & SC_STATUS_ADMIN_REVOKED)
+               atomic_dec(&s->sc_client->cl_admin_revoked);
        nfs4_free_cpntf_statelist(clp->net, s);
        spin_unlock(&clp->cl_lock);
        s->sc_free(s);
@@ -1249,7 +1257,7 @@ static void nfs4_unlock_deleg_lease(struct nfs4_delegation *dp)
 
        WARN_ON_ONCE(!fp->fi_delegees);
 
-       vfs_setlease(nf->nf_file, F_UNLCK, NULL, (void **)&dp);
+       kernel_setlease(nf->nf_file, F_UNLCK, NULL, (void **)&dp);
        put_deleg_file(fp);
 }
 
@@ -1260,11 +1268,6 @@ static void destroy_unhashed_deleg(struct nfs4_delegation *dp)
        nfs4_put_stid(&dp->dl_stid);
 }
 
-void nfs4_unhash_stid(struct nfs4_stid *s)
-{
-       s->sc_type = 0;
-}
-
 /**
  * nfs4_delegation_exists - Discover if this delegation already exists
  * @clp:     a pointer to the nfs4_client we're granting a delegation to
@@ -1312,11 +1315,12 @@ hash_delegation_locked(struct nfs4_delegation *dp, struct nfs4_file *fp)
 
        lockdep_assert_held(&state_lock);
        lockdep_assert_held(&fp->fi_lock);
+       lockdep_assert_held(&clp->cl_lock);
 
        if (nfs4_delegation_exists(clp, fp))
                return -EAGAIN;
        refcount_inc(&dp->dl_stid.sc_count);
-       dp->dl_stid.sc_type = NFS4_DELEG_STID;
+       dp->dl_stid.sc_type = SC_TYPE_DELEG;
        list_add(&dp->dl_perfile, &fp->fi_delegations);
        list_add(&dp->dl_perclnt, &clp->cl_delegations);
        return 0;
@@ -1328,7 +1332,7 @@ static bool delegation_hashed(struct nfs4_delegation *dp)
 }
 
 static bool
-unhash_delegation_locked(struct nfs4_delegation *dp)
+unhash_delegation_locked(struct nfs4_delegation *dp, unsigned short statusmask)
 {
        struct nfs4_file *fp = dp->dl_stid.sc_file;
 
@@ -1337,7 +1341,13 @@ unhash_delegation_locked(struct nfs4_delegation *dp)
        if (!delegation_hashed(dp))
                return false;
 
-       dp->dl_stid.sc_type = NFS4_CLOSED_DELEG_STID;
+       if (statusmask == SC_STATUS_REVOKED &&
+           dp->dl_stid.sc_client->cl_minorversion == 0)
+               statusmask = SC_STATUS_CLOSED;
+       dp->dl_stid.sc_status |= statusmask;
+       if (statusmask & SC_STATUS_ADMIN_REVOKED)
+               atomic_inc(&dp->dl_stid.sc_client->cl_admin_revoked);
+
        /* Ensure that deleg break won't try to requeue it */
        ++dp->dl_time;
        spin_lock(&fp->fi_lock);
@@ -1353,7 +1363,7 @@ static void destroy_delegation(struct nfs4_delegation *dp)
        bool unhashed;
 
        spin_lock(&state_lock);
-       unhashed = unhash_delegation_locked(dp);
+       unhashed = unhash_delegation_locked(dp, SC_STATUS_CLOSED);
        spin_unlock(&state_lock);
        if (unhashed)
                destroy_unhashed_deleg(dp);
@@ -1367,9 +1377,9 @@ static void revoke_delegation(struct nfs4_delegation *dp)
 
        trace_nfsd_stid_revoke(&dp->dl_stid);
 
-       if (clp->cl_minorversion) {
+       if (dp->dl_stid.sc_status &
+           (SC_STATUS_REVOKED | SC_STATUS_ADMIN_REVOKED)) {
                spin_lock(&clp->cl_lock);
-               dp->dl_stid.sc_type = NFS4_REVOKED_DELEG_STID;
                refcount_inc(&dp->dl_stid.sc_count);
                list_add(&dp->dl_recall_lru, &clp->cl_revoked);
                spin_unlock(&clp->cl_lock);
@@ -1377,8 +1387,8 @@ static void revoke_delegation(struct nfs4_delegation *dp)
        destroy_unhashed_deleg(dp);
 }
 
-/* 
- * SETCLIENTID state 
+/*
+ * SETCLIENTID state
  */
 
 static unsigned int clientid_hashval(u32 id)
@@ -1531,6 +1541,8 @@ static void put_ol_stateid_locked(struct nfs4_ol_stateid *stp,
        }
 
        idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id);
+       if (s->sc_status & SC_STATUS_ADMIN_REVOKED)
+               atomic_dec(&s->sc_client->cl_admin_revoked);
        list_add(&stp->st_locks, reaplist);
 }
 
@@ -1541,7 +1553,7 @@ static bool unhash_lock_stateid(struct nfs4_ol_stateid *stp)
        if (!unhash_ol_stateid(stp))
                return false;
        list_del_init(&stp->st_locks);
-       nfs4_unhash_stid(&stp->st_stid);
+       stp->st_stid.sc_status |= SC_STATUS_CLOSED;
        return true;
 }
 
@@ -1599,7 +1611,7 @@ static void release_open_stateid_locks(struct nfs4_ol_stateid *open_stp,
        while (!list_empty(&open_stp->st_locks)) {
                stp = list_entry(open_stp->st_locks.next,
                                struct nfs4_ol_stateid, st_locks);
-               WARN_ON(!unhash_lock_stateid(stp));
+               unhash_lock_stateid(stp);
                put_ol_stateid_locked(stp, reaplist);
        }
 }
@@ -1620,6 +1632,7 @@ static void release_open_stateid(struct nfs4_ol_stateid *stp)
        LIST_HEAD(reaplist);
 
        spin_lock(&stp->st_stid.sc_client->cl_lock);
+       stp->st_stid.sc_status |= SC_STATUS_CLOSED;
        if (unhash_open_stateid(stp, &reaplist))
                put_ol_stateid_locked(stp, &reaplist);
        spin_unlock(&stp->st_stid.sc_client->cl_lock);
@@ -1675,6 +1688,136 @@ static void release_openowner(struct nfs4_openowner *oo)
        nfs4_put_stateowner(&oo->oo_owner);
 }
 
+static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp,
+                                         struct super_block *sb,
+                                         unsigned int sc_types)
+{
+       unsigned long id, tmp;
+       struct nfs4_stid *stid;
+
+       spin_lock(&clp->cl_lock);
+       idr_for_each_entry_ul(&clp->cl_stateids, stid, tmp, id)
+               if ((stid->sc_type & sc_types) &&
+                   stid->sc_status == 0 &&
+                   stid->sc_file->fi_inode->i_sb == sb) {
+                       refcount_inc(&stid->sc_count);
+                       break;
+               }
+       spin_unlock(&clp->cl_lock);
+       return stid;
+}
+
+/**
+ * nfsd4_revoke_states - revoke all nfsv4 states associated with given filesystem
+ * @net:  used to identify instance of nfsd (there is one per net namespace)
+ * @sb:   super_block used to identify target filesystem
+ *
+ * All nfs4 states (open, lock, delegation, layout) held by the server instance
+ * and associated with a file on the given filesystem will be revoked resulting
+ * in any files being closed and so all references from nfsd to the filesystem
+ * being released.  Thus nfsd will no longer prevent the filesystem from being
+ * unmounted.
+ *
+ * The clients which own the states will subsequently being notified that the
+ * states have been "admin-revoked".
+ */
+void nfsd4_revoke_states(struct net *net, struct super_block *sb)
+{
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+       unsigned int idhashval;
+       unsigned int sc_types;
+
+       sc_types = SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG | SC_TYPE_LAYOUT;
+
+       spin_lock(&nn->client_lock);
+       for (idhashval = 0; idhashval < CLIENT_HASH_MASK; idhashval++) {
+               struct list_head *head = &nn->conf_id_hashtbl[idhashval];
+               struct nfs4_client *clp;
+       retry:
+               list_for_each_entry(clp, head, cl_idhash) {
+                       struct nfs4_stid *stid = find_one_sb_stid(clp, sb,
+                                                                 sc_types);
+                       if (stid) {
+                               struct nfs4_ol_stateid *stp;
+                               struct nfs4_delegation *dp;
+                               struct nfs4_layout_stateid *ls;
+
+                               spin_unlock(&nn->client_lock);
+                               switch (stid->sc_type) {
+                               case SC_TYPE_OPEN:
+                                       stp = openlockstateid(stid);
+                                       mutex_lock_nested(&stp->st_mutex,
+                                                         OPEN_STATEID_MUTEX);
+
+                                       spin_lock(&clp->cl_lock);
+                                       if (stid->sc_status == 0) {
+                                               stid->sc_status |=
+                                                       SC_STATUS_ADMIN_REVOKED;
+                                               atomic_inc(&clp->cl_admin_revoked);
+                                               spin_unlock(&clp->cl_lock);
+                                               release_all_access(stp);
+                                       } else
+                                               spin_unlock(&clp->cl_lock);
+                                       mutex_unlock(&stp->st_mutex);
+                                       break;
+                               case SC_TYPE_LOCK:
+                                       stp = openlockstateid(stid);
+                                       mutex_lock_nested(&stp->st_mutex,
+                                                         LOCK_STATEID_MUTEX);
+                                       spin_lock(&clp->cl_lock);
+                                       if (stid->sc_status == 0) {
+                                               struct nfs4_lockowner *lo =
+                                                       lockowner(stp->st_stateowner);
+                                               struct nfsd_file *nf;
+
+                                               stid->sc_status |=
+                                                       SC_STATUS_ADMIN_REVOKED;
+                                               atomic_inc(&clp->cl_admin_revoked);
+                                               spin_unlock(&clp->cl_lock);
+                                               nf = find_any_file(stp->st_stid.sc_file);
+                                               if (nf) {
+                                                       get_file(nf->nf_file);
+                                                       filp_close(nf->nf_file,
+                                                                  (fl_owner_t)lo);
+                                                       nfsd_file_put(nf);
+                                               }
+                                               release_all_access(stp);
+                                       } else
+                                               spin_unlock(&clp->cl_lock);
+                                       mutex_unlock(&stp->st_mutex);
+                                       break;
+                               case SC_TYPE_DELEG:
+                                       dp = delegstateid(stid);
+                                       spin_lock(&state_lock);
+                                       if (!unhash_delegation_locked(
+                                                   dp, SC_STATUS_ADMIN_REVOKED))
+                                               dp = NULL;
+                                       spin_unlock(&state_lock);
+                                       if (dp)
+                                               revoke_delegation(dp);
+                                       break;
+                               case SC_TYPE_LAYOUT:
+                                       ls = layoutstateid(stid);
+                                       nfsd4_close_layout(ls);
+                                       break;
+                               }
+                               nfs4_put_stid(stid);
+                               spin_lock(&nn->client_lock);
+                               if (clp->cl_minorversion == 0)
+                                       /* Allow cleanup after a lease period.
+                                        * store_release ensures cleanup will
+                                        * see any newly revoked states if it
+                                        * sees the time updated.
+                                        */
+                                       nn->nfs40_last_revoke =
+                                               ktime_get_boottime_seconds();
+                               goto retry;
+                       }
+               }
+       }
+       spin_unlock(&nn->client_lock);
+}
+
 static inline int
 hash_sessionid(struct nfs4_sessionid *sessionid)
 {
@@ -2228,7 +2371,7 @@ __destroy_client(struct nfs4_client *clp)
        spin_lock(&state_lock);
        while (!list_empty(&clp->cl_delegations)) {
                dp = list_entry(clp->cl_delegations.next, struct nfs4_delegation, dl_perclnt);
-               WARN_ON(!unhash_delegation_locked(dp));
+               unhash_delegation_locked(dp, SC_STATUS_CLOSED);
                list_add(&dp->dl_recall_lru, &reaplist);
        }
        spin_unlock(&state_lock);
@@ -2460,14 +2603,16 @@ find_stateid_locked(struct nfs4_client *cl, stateid_t *t)
 }
 
 static struct nfs4_stid *
-find_stateid_by_type(struct nfs4_client *cl, stateid_t *t, char typemask)
+find_stateid_by_type(struct nfs4_client *cl, stateid_t *t,
+                    unsigned short typemask, unsigned short ok_states)
 {
        struct nfs4_stid *s;
 
        spin_lock(&cl->cl_lock);
        s = find_stateid_locked(cl, t);
        if (s != NULL) {
-               if (typemask & s->sc_type)
+               if ((s->sc_status & ~ok_states) == 0 &&
+                   (typemask & s->sc_type))
                        refcount_inc(&s->sc_count);
                else
                        s = NULL;
@@ -2487,9 +2632,9 @@ static struct nfs4_client *get_nfsdfs_clp(struct inode *inode)
 
 static void seq_quote_mem(struct seq_file *m, char *data, int len)
 {
-       seq_printf(m, "\"");
+       seq_puts(m, "\"");
        seq_escape_mem(m, data, len, ESCAPE_HEX | ESCAPE_NAP | ESCAPE_APPEND, "\"\\");
-       seq_printf(m, "\"");
+       seq_puts(m, "\"");
 }
 
 static const char *cb_state2str(int state)
@@ -2530,20 +2675,22 @@ static int client_info_show(struct seq_file *m, void *v)
                seq_puts(m, "status: unconfirmed\n");
        seq_printf(m, "seconds from last renew: %lld\n",
                ktime_get_boottime_seconds() - clp->cl_time);
-       seq_printf(m, "name: ");
+       seq_puts(m, "name: ");
        seq_quote_mem(m, clp->cl_name.data, clp->cl_name.len);
        seq_printf(m, "\nminor version: %d\n", clp->cl_minorversion);
        if (clp->cl_nii_domain.data) {
-               seq_printf(m, "Implementation domain: ");
+               seq_puts(m, "Implementation domain: ");
                seq_quote_mem(m, clp->cl_nii_domain.data,
                                        clp->cl_nii_domain.len);
-               seq_printf(m, "\nImplementation name: ");
+               seq_puts(m, "\nImplementation name: ");
                seq_quote_mem(m, clp->cl_nii_name.data, clp->cl_nii_name.len);
                seq_printf(m, "\nImplementation time: [%lld, %ld]\n",
                        clp->cl_nii_time.tv_sec, clp->cl_nii_time.tv_nsec);
        }
        seq_printf(m, "callback state: %s\n", cb_state2str(clp->cl_cb_state));
        seq_printf(m, "callback address: %pISpc\n", &clp->cl_cb_conn.cb_addr);
+       seq_printf(m, "admin-revoked states: %d\n",
+                  atomic_read(&clp->cl_admin_revoked));
        drop_client(clp);
 
        return 0;
@@ -2602,7 +2749,7 @@ static void nfs4_show_superblock(struct seq_file *s, struct nfsd_file *f)
 
 static void nfs4_show_owner(struct seq_file *s, struct nfs4_stateowner *oo)
 {
-       seq_printf(s, "owner: ");
+       seq_puts(s, "owner: ");
        seq_quote_mem(s, oo->so_owner.data, oo->so_owner.len);
 }
 
@@ -2620,20 +2767,13 @@ static int nfs4_show_open(struct seq_file *s, struct nfs4_stid *st)
        struct nfs4_stateowner *oo;
        unsigned int access, deny;
 
-       if (st->sc_type != NFS4_OPEN_STID && st->sc_type != NFS4_LOCK_STID)
-               return 0; /* XXX: or SEQ_SKIP? */
        ols = openlockstateid(st);
        oo = ols->st_stateowner;
        nf = st->sc_file;
 
-       spin_lock(&nf->fi_lock);
-       file = find_any_file_locked(nf);
-       if (!file)
-               goto out;
-
-       seq_printf(s, "- ");
+       seq_puts(s, "- ");
        nfs4_show_stateid(s, &st->sc_stateid);
-       seq_printf(s, ": { type: open, ");
+       seq_puts(s, ": { type: open, ");
 
        access = bmap_to_share_mode(ols->st_access_bmap);
        deny   = bmap_to_share_mode(ols->st_deny_bmap);
@@ -2645,14 +2785,19 @@ static int nfs4_show_open(struct seq_file *s, struct nfs4_stid *st)
                deny & NFS4_SHARE_ACCESS_READ ? "r" : "-",
                deny & NFS4_SHARE_ACCESS_WRITE ? "w" : "-");
 
-       nfs4_show_superblock(s, file);
-       seq_printf(s, ", ");
-       nfs4_show_fname(s, file);
-       seq_printf(s, ", ");
-       nfs4_show_owner(s, oo);
-       seq_printf(s, " }\n");
-out:
+       spin_lock(&nf->fi_lock);
+       file = find_any_file_locked(nf);
+       if (file) {
+               nfs4_show_superblock(s, file);
+               seq_puts(s, ", ");
+               nfs4_show_fname(s, file);
+               seq_puts(s, ", ");
+       }
        spin_unlock(&nf->fi_lock);
+       nfs4_show_owner(s, oo);
+       if (st->sc_status & SC_STATUS_ADMIN_REVOKED)
+               seq_puts(s, ", admin-revoked");
+       seq_puts(s, " }\n");
        return 0;
 }
 
@@ -2666,30 +2811,31 @@ static int nfs4_show_lock(struct seq_file *s, struct nfs4_stid *st)
        ols = openlockstateid(st);
        oo = ols->st_stateowner;
        nf = st->sc_file;
-       spin_lock(&nf->fi_lock);
-       file = find_any_file_locked(nf);
-       if (!file)
-               goto out;
 
-       seq_printf(s, "- ");
+       seq_puts(s, "- ");
        nfs4_show_stateid(s, &st->sc_stateid);
-       seq_printf(s, ": { type: lock, ");
+       seq_puts(s, ": { type: lock, ");
 
-       /*
-        * Note: a lock stateid isn't really the same thing as a lock,
-        * it's the locking state held by one owner on a file, and there
-        * may be multiple (or no) lock ranges associated with it.
-        * (Same for the matter is true of open stateids.)
-        */
+       spin_lock(&nf->fi_lock);
+       file = find_any_file_locked(nf);
+       if (file) {
+               /*
+                * Note: a lock stateid isn't really the same thing as a lock,
+                * it's the locking state held by one owner on a file, and there
+                * may be multiple (or no) lock ranges associated with it.
+                * (Same for the matter is true of open stateids.)
+                */
 
-       nfs4_show_superblock(s, file);
-       /* XXX: open stateid? */
-       seq_printf(s, ", ");
-       nfs4_show_fname(s, file);
-       seq_printf(s, ", ");
+               nfs4_show_superblock(s, file);
+               /* XXX: open stateid? */
+               seq_puts(s, ", ");
+               nfs4_show_fname(s, file);
+               seq_puts(s, ", ");
+       }
        nfs4_show_owner(s, oo);
-       seq_printf(s, " }\n");
-out:
+       if (st->sc_status & SC_STATUS_ADMIN_REVOKED)
+               seq_puts(s, ", admin-revoked");
+       seq_puts(s, " }\n");
        spin_unlock(&nf->fi_lock);
        return 0;
 }
@@ -2702,27 +2848,28 @@ static int nfs4_show_deleg(struct seq_file *s, struct nfs4_stid *st)
 
        ds = delegstateid(st);
        nf = st->sc_file;
-       spin_lock(&nf->fi_lock);
-       file = nf->fi_deleg_file;
-       if (!file)
-               goto out;
 
-       seq_printf(s, "- ");
+       seq_puts(s, "- ");
        nfs4_show_stateid(s, &st->sc_stateid);
-       seq_printf(s, ": { type: deleg, ");
+       seq_puts(s, ": { type: deleg, ");
 
-       /* Kinda dead code as long as we only support read delegs: */
-       seq_printf(s, "access: %s, ",
-               ds->dl_type == NFS4_OPEN_DELEGATE_READ ? "r" : "w");
+       seq_printf(s, "access: %s",
+                  ds->dl_type == NFS4_OPEN_DELEGATE_READ ? "r" : "w");
 
        /* XXX: lease time, whether it's being recalled. */
 
-       nfs4_show_superblock(s, file);
-       seq_printf(s, ", ");
-       nfs4_show_fname(s, file);
-       seq_printf(s, " }\n");
-out:
+       spin_lock(&nf->fi_lock);
+       file = nf->fi_deleg_file;
+       if (file) {
+               seq_puts(s, ", ");
+               nfs4_show_superblock(s, file);
+               seq_puts(s, ", ");
+               nfs4_show_fname(s, file);
+       }
        spin_unlock(&nf->fi_lock);
+       if (st->sc_status & SC_STATUS_ADMIN_REVOKED)
+               seq_puts(s, ", admin-revoked");
+       seq_puts(s, " }\n");
        return 0;
 }
 
@@ -2732,18 +2879,25 @@ static int nfs4_show_layout(struct seq_file *s, struct nfs4_stid *st)
        struct nfsd_file *file;
 
        ls = container_of(st, struct nfs4_layout_stateid, ls_stid);
-       file = ls->ls_file;
 
-       seq_printf(s, "- ");
+       seq_puts(s, "- ");
        nfs4_show_stateid(s, &st->sc_stateid);
-       seq_printf(s, ": { type: layout, ");
+       seq_puts(s, ": { type: layout");
 
        /* XXX: What else would be useful? */
 
-       nfs4_show_superblock(s, file);
-       seq_printf(s, ", ");
-       nfs4_show_fname(s, file);
-       seq_printf(s, " }\n");
+       spin_lock(&ls->ls_stid.sc_file->fi_lock);
+       file = ls->ls_file;
+       if (file) {
+               seq_puts(s, ", ");
+               nfs4_show_superblock(s, file);
+               seq_puts(s, ", ");
+               nfs4_show_fname(s, file);
+       }
+       spin_unlock(&ls->ls_stid.sc_file->fi_lock);
+       if (st->sc_status & SC_STATUS_ADMIN_REVOKED)
+               seq_puts(s, ", admin-revoked");
+       seq_puts(s, " }\n");
 
        return 0;
 }
@@ -2753,13 +2907,13 @@ static int states_show(struct seq_file *s, void *v)
        struct nfs4_stid *st = v;
 
        switch (st->sc_type) {
-       case NFS4_OPEN_STID:
+       case SC_TYPE_OPEN:
                return nfs4_show_open(s, st);
-       case NFS4_LOCK_STID:
+       case SC_TYPE_LOCK:
                return nfs4_show_lock(s, st);
-       case NFS4_DELEG_STID:
+       case SC_TYPE_DELEG:
                return nfs4_show_deleg(s, st);
-       case NFS4_LAYOUT_STID:
+       case SC_TYPE_LAYOUT:
                return nfs4_show_layout(s, st);
        default:
                return 0; /* XXX: or SEQ_SKIP? */
@@ -2896,11 +3050,59 @@ nfsd4_cb_recall_any_release(struct nfsd4_callback *cb)
        spin_unlock(&nn->client_lock);
 }
 
+static int
+nfsd4_cb_getattr_done(struct nfsd4_callback *cb, struct rpc_task *task)
+{
+       struct nfs4_cb_fattr *ncf =
+                       container_of(cb, struct nfs4_cb_fattr, ncf_getattr);
+
+       ncf->ncf_cb_status = task->tk_status;
+       switch (task->tk_status) {
+       case -NFS4ERR_DELAY:
+               rpc_delay(task, 2 * HZ);
+               return 0;
+       default:
+               return 1;
+       }
+}
+
+static void
+nfsd4_cb_getattr_release(struct nfsd4_callback *cb)
+{
+       struct nfs4_cb_fattr *ncf =
+                       container_of(cb, struct nfs4_cb_fattr, ncf_getattr);
+       struct nfs4_delegation *dp =
+                       container_of(ncf, struct nfs4_delegation, dl_cb_fattr);
+
+       nfs4_put_stid(&dp->dl_stid);
+       clear_bit(CB_GETATTR_BUSY, &ncf->ncf_cb_flags);
+       wake_up_bit(&ncf->ncf_cb_flags, CB_GETATTR_BUSY);
+}
+
 static const struct nfsd4_callback_ops nfsd4_cb_recall_any_ops = {
        .done           = nfsd4_cb_recall_any_done,
        .release        = nfsd4_cb_recall_any_release,
 };
 
+static const struct nfsd4_callback_ops nfsd4_cb_getattr_ops = {
+       .done           = nfsd4_cb_getattr_done,
+       .release        = nfsd4_cb_getattr_release,
+};
+
+static void nfs4_cb_getattr(struct nfs4_cb_fattr *ncf)
+{
+       struct nfs4_delegation *dp =
+                       container_of(ncf, struct nfs4_delegation, dl_cb_fattr);
+
+       if (test_and_set_bit(CB_GETATTR_BUSY, &ncf->ncf_cb_flags))
+               return;
+       /* set to proper status when nfsd4_cb_getattr_done runs */
+       ncf->ncf_cb_status = NFS4ERR_IO;
+
+       refcount_inc(&dp->dl_stid.sc_count);
+       nfsd4_run_cb(&ncf->ncf_getattr);
+}
+
 static struct nfs4_client *create_client(struct xdr_netobj name,
                struct svc_rqst *rqstp, nfs4_verifier *verf)
 {
@@ -3414,6 +3616,9 @@ out_new:
        new->cl_spo_must_allow.u.words[0] = exid->spo_must_allow[0];
        new->cl_spo_must_allow.u.words[1] = exid->spo_must_allow[1];
 
+       /* Contrived initial CREATE_SESSION response */
+       new->cl_cs_slot.sl_status = nfserr_seq_misordered;
+
        add_to_unconfirmed(new);
        swap(new, conf);
 out_copy:
@@ -3584,10 +3789,10 @@ nfsd4_create_session(struct svc_rqst *rqstp,
        struct nfsd4_create_session *cr_ses = &u->create_session;
        struct sockaddr *sa = svc_addr(rqstp);
        struct nfs4_client *conf, *unconf;
+       struct nfsd4_clid_slot *cs_slot;
        struct nfs4_client *old = NULL;
        struct nfsd4_session *new;
        struct nfsd4_conn *conn;
-       struct nfsd4_clid_slot *cs_slot = NULL;
        __be32 status = 0;
        struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
 
@@ -3611,53 +3816,63 @@ nfsd4_create_session(struct svc_rqst *rqstp,
                goto out_free_session;
 
        spin_lock(&nn->client_lock);
+
+       /* RFC 8881 Section 18.36.4 Phase 1: Client record look-up. */
        unconf = find_unconfirmed_client(&cr_ses->clientid, true, nn);
        conf = find_confirmed_client(&cr_ses->clientid, true, nn);
-       WARN_ON_ONCE(conf && unconf);
+       if (!conf && !unconf) {
+               status = nfserr_stale_clientid;
+               goto out_free_conn;
+       }
+
+       /* RFC 8881 Section 18.36.4 Phase 2: Sequence ID processing. */
+       if (conf)
+               cs_slot = &conf->cl_cs_slot;
+       else
+               cs_slot = &unconf->cl_cs_slot;
+       status = check_slot_seqid(cr_ses->seqid, cs_slot->sl_seqid, 0);
+       switch (status) {
+       case nfs_ok:
+               cs_slot->sl_seqid++;
+               cr_ses->seqid = cs_slot->sl_seqid;
+               break;
+       case nfserr_replay_cache:
+               status = nfsd4_replay_create_session(cr_ses, cs_slot);
+               fallthrough;
+       case nfserr_jukebox:
+               /* The server MUST NOT cache NFS4ERR_DELAY */
+               goto out_free_conn;
+       default:
+               goto out_cache_error;
+       }
 
+       /* RFC 8881 Section 18.36.4 Phase 3: Client ID confirmation. */
        if (conf) {
                status = nfserr_wrong_cred;
                if (!nfsd4_mach_creds_match(conf, rqstp))
-                       goto out_free_conn;
-               cs_slot = &conf->cl_cs_slot;
-               status = check_slot_seqid(cr_ses->seqid, cs_slot->sl_seqid, 0);
-               if (status) {
-                       if (status == nfserr_replay_cache)
-                               status = nfsd4_replay_create_session(cr_ses, cs_slot);
-                       goto out_free_conn;
-               }
-       } else if (unconf) {
+                       goto out_cache_error;
+       } else {
                status = nfserr_clid_inuse;
                if (!same_creds(&unconf->cl_cred, &rqstp->rq_cred) ||
                    !rpc_cmp_addr(sa, (struct sockaddr *) &unconf->cl_addr)) {
                        trace_nfsd_clid_cred_mismatch(unconf, rqstp);
-                       goto out_free_conn;
+                       goto out_cache_error;
                }
                status = nfserr_wrong_cred;
                if (!nfsd4_mach_creds_match(unconf, rqstp))
-                       goto out_free_conn;
-               cs_slot = &unconf->cl_cs_slot;
-               status = check_slot_seqid(cr_ses->seqid, cs_slot->sl_seqid, 0);
-               if (status) {
-                       /* an unconfirmed replay returns misordered */
-                       status = nfserr_seq_misordered;
-                       goto out_free_conn;
-               }
+                       goto out_cache_error;
                old = find_confirmed_client_by_name(&unconf->cl_name, nn);
                if (old) {
                        status = mark_client_expired_locked(old);
-                       if (status) {
-                               old = NULL;
-                               goto out_free_conn;
-                       }
+                       if (status)
+                               goto out_expired_error;
                        trace_nfsd_clid_replaced(&old->cl_clientid);
                }
                move_to_confirmed(unconf);
                conf = unconf;
-       } else {
-               status = nfserr_stale_clientid;
-               goto out_free_conn;
        }
+
+       /* RFC 8881 Section 18.36.4 Phase 4: Session creation. */
        status = nfs_ok;
        /* Persistent sessions are not supported */
        cr_ses->flags &= ~SESSION4_PERSIST;
@@ -3669,8 +3884,6 @@ nfsd4_create_session(struct svc_rqst *rqstp,
 
        memcpy(cr_ses->sessionid.data, new->se_sessionid.data,
               NFS4_MAX_SESSIONID_LEN);
-       cs_slot->sl_seqid++;
-       cr_ses->seqid = cs_slot->sl_seqid;
 
        /* cache solo and embedded create sessions under the client_lock */
        nfsd4_cache_create_session(cr_ses, cs_slot, status);
@@ -3683,6 +3896,20 @@ nfsd4_create_session(struct svc_rqst *rqstp,
        if (old)
                expire_client(old);
        return status;
+
+out_expired_error:
+       old = NULL;
+       /*
+        * Revert the slot seq_nr change so the server will process
+        * the client's resend instead of returning a cached response.
+        */
+       if (status == nfserr_jukebox) {
+               cs_slot->sl_seqid--;
+               cr_ses->seqid = cs_slot->sl_seqid;
+               goto out_free_conn;
+       }
+out_cache_error:
+       nfsd4_cache_create_session(cr_ses, cs_slot, status);
 out_free_conn:
        spin_unlock(&nn->client_lock);
        free_conn(conn);
@@ -4058,6 +4285,9 @@ out:
        }
        if (!list_empty(&clp->cl_revoked))
                seq->status_flags |= SEQ4_STATUS_RECALLABLE_STATE_REVOKED;
+       if (atomic_read(&clp->cl_admin_revoked))
+               seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED;
+       trace_nfsd_seq4_status(rqstp, seq);
 out_no_session:
        if (conn)
                free_conn(conn);
@@ -4352,32 +4582,25 @@ nfsd4_free_slabs(void)
 int
 nfsd4_init_slabs(void)
 {
-       client_slab = kmem_cache_create("nfsd4_clients",
-                       sizeof(struct nfs4_client), 0, 0, NULL);
+       client_slab = KMEM_CACHE(nfs4_client, 0);
        if (client_slab == NULL)
                goto out;
-       openowner_slab = kmem_cache_create("nfsd4_openowners",
-                       sizeof(struct nfs4_openowner), 0, 0, NULL);
+       openowner_slab = KMEM_CACHE(nfs4_openowner, 0);
        if (openowner_slab == NULL)
                goto out_free_client_slab;
-       lockowner_slab = kmem_cache_create("nfsd4_lockowners",
-                       sizeof(struct nfs4_lockowner), 0, 0, NULL);
+       lockowner_slab = KMEM_CACHE(nfs4_lockowner, 0);
        if (lockowner_slab == NULL)
                goto out_free_openowner_slab;
-       file_slab = kmem_cache_create("nfsd4_files",
-                       sizeof(struct nfs4_file), 0, 0, NULL);
+       file_slab = KMEM_CACHE(nfs4_file, 0);
        if (file_slab == NULL)
                goto out_free_lockowner_slab;
-       stateid_slab = kmem_cache_create("nfsd4_stateids",
-                       sizeof(struct nfs4_ol_stateid), 0, 0, NULL);
+       stateid_slab = KMEM_CACHE(nfs4_ol_stateid, 0);
        if (stateid_slab == NULL)
                goto out_free_file_slab;
-       deleg_slab = kmem_cache_create("nfsd4_delegations",
-                       sizeof(struct nfs4_delegation), 0, 0, NULL);
+       deleg_slab = KMEM_CACHE(nfs4_delegation, 0);
        if (deleg_slab == NULL)
                goto out_free_stateid_slab;
-       odstate_slab = kmem_cache_create("nfsd4_odstate",
-                       sizeof(struct nfs4_clnt_odstate), 0, 0, NULL);
+       odstate_slab = KMEM_CACHE(nfs4_clnt_odstate, 0);
        if (odstate_slab == NULL)
                goto out_free_deleg_slab;
        return 0;
@@ -4531,7 +4754,8 @@ nfsd4_find_existing_open(struct nfs4_file *fp, struct nfsd4_open *open)
                        continue;
                if (local->st_stateowner != &oo->oo_owner)
                        continue;
-               if (local->st_stid.sc_type == NFS4_OPEN_STID) {
+               if (local->st_stid.sc_type == SC_TYPE_OPEN &&
+                   !local->st_stid.sc_status) {
                        ret = local;
                        refcount_inc(&ret->st_stid.sc_count);
                        break;
@@ -4540,22 +4764,75 @@ nfsd4_find_existing_open(struct nfs4_file *fp, struct nfsd4_open *open)
        return ret;
 }
 
-static __be32
-nfsd4_verify_open_stid(struct nfs4_stid *s)
+static void nfsd4_drop_revoked_stid(struct nfs4_stid *s)
+       __releases(&s->sc_client->cl_lock)
 {
-       __be32 ret = nfs_ok;
+       struct nfs4_client *cl = s->sc_client;
+       LIST_HEAD(reaplist);
+       struct nfs4_ol_stateid *stp;
+       struct nfs4_delegation *dp;
+       bool unhashed;
 
        switch (s->sc_type) {
-       default:
+       case SC_TYPE_OPEN:
+               stp = openlockstateid(s);
+               if (unhash_open_stateid(stp, &reaplist))
+                       put_ol_stateid_locked(stp, &reaplist);
+               spin_unlock(&cl->cl_lock);
+               free_ol_stateid_reaplist(&reaplist);
                break;
-       case 0:
-       case NFS4_CLOSED_STID:
-       case NFS4_CLOSED_DELEG_STID:
-               ret = nfserr_bad_stateid;
+       case SC_TYPE_LOCK:
+               stp = openlockstateid(s);
+               unhashed = unhash_lock_stateid(stp);
+               spin_unlock(&cl->cl_lock);
+               if (unhashed)
+                       nfs4_put_stid(s);
                break;
-       case NFS4_REVOKED_DELEG_STID:
-               ret = nfserr_deleg_revoked;
+       case SC_TYPE_DELEG:
+               dp = delegstateid(s);
+               list_del_init(&dp->dl_recall_lru);
+               spin_unlock(&cl->cl_lock);
+               nfs4_put_stid(s);
+               break;
+       default:
+               spin_unlock(&cl->cl_lock);
        }
+}
+
+static void nfsd40_drop_revoked_stid(struct nfs4_client *cl,
+                                   stateid_t *stid)
+{
+       /* NFSv4.0 has no way for the client to tell the server
+        * that it can forget an admin-revoked stateid.
+        * So we keep it around until the first time that the
+        * client uses it, and drop it the first time
+        * nfserr_admin_revoked is returned.
+        * For v4.1 and later we wait until explicitly told
+        * to free the stateid.
+        */
+       if (cl->cl_minorversion == 0) {
+               struct nfs4_stid *st;
+
+               spin_lock(&cl->cl_lock);
+               st = find_stateid_locked(cl, stid);
+               if (st)
+                       nfsd4_drop_revoked_stid(st);
+               else
+                       spin_unlock(&cl->cl_lock);
+       }
+}
+
+static __be32
+nfsd4_verify_open_stid(struct nfs4_stid *s)
+{
+       __be32 ret = nfs_ok;
+
+       if (s->sc_status & SC_STATUS_ADMIN_REVOKED)
+               ret = nfserr_admin_revoked;
+       else if (s->sc_status & SC_STATUS_REVOKED)
+               ret = nfserr_deleg_revoked;
+       else if (s->sc_status & SC_STATUS_CLOSED)
+               ret = nfserr_bad_stateid;
        return ret;
 }
 
@@ -4567,6 +4844,10 @@ nfsd4_lock_ol_stateid(struct nfs4_ol_stateid *stp)
 
        mutex_lock_nested(&stp->st_mutex, LOCK_STATEID_MUTEX);
        ret = nfsd4_verify_open_stid(&stp->st_stid);
+       if (ret == nfserr_admin_revoked)
+               nfsd40_drop_revoked_stid(stp->st_stid.sc_client,
+                                       &stp->st_stid.sc_stateid);
+
        if (ret != nfs_ok)
                mutex_unlock(&stp->st_mutex);
        return ret;
@@ -4641,7 +4922,7 @@ retry:
 
        open->op_stp = NULL;
        refcount_inc(&stp->st_stid.sc_count);
-       stp->st_stid.sc_type = NFS4_OPEN_STID;
+       stp->st_stid.sc_type = SC_TYPE_OPEN;
        INIT_LIST_HEAD(&stp->st_locks);
        stp->st_stateowner = nfs4_get_stateowner(&oo->oo_owner);
        get_nfs4_file(fp);
@@ -4868,9 +5149,9 @@ static int nfsd4_cb_recall_done(struct nfsd4_callback *cb,
 
        trace_nfsd_cb_recall_done(&dp->dl_stid.sc_stateid, task);
 
-       if (dp->dl_stid.sc_type == NFS4_CLOSED_DELEG_STID ||
-           dp->dl_stid.sc_type == NFS4_REVOKED_DELEG_STID)
-               return 1;
+       if (dp->dl_stid.sc_status)
+               /* CLOSED or REVOKED */
+               return 1;
 
        switch (task->tk_status) {
        case 0:
@@ -4922,9 +5203,9 @@ static void nfsd_break_one_deleg(struct nfs4_delegation *dp)
 
 /* Called from break_lease() with flc_lock held. */
 static bool
-nfsd_break_deleg_cb(struct file_lock *fl)
+nfsd_break_deleg_cb(struct file_lease *fl)
 {
-       struct nfs4_delegation *dp = (struct nfs4_delegation *)fl->fl_owner;
+       struct nfs4_delegation *dp = (struct nfs4_delegation *) fl->c.flc_owner;
        struct nfs4_file *fp = dp->dl_stid.sc_file;
        struct nfs4_client *clp = dp->dl_stid.sc_client;
        struct nfsd_net *nn;
@@ -4958,9 +5239,9 @@ nfsd_break_deleg_cb(struct file_lock *fl)
  *   %true: Lease conflict was resolved
  *   %false: Lease conflict was not resolved.
  */
-static bool nfsd_breaker_owns_lease(struct file_lock *fl)
+static bool nfsd_breaker_owns_lease(struct file_lease *fl)
 {
-       struct nfs4_delegation *dl = fl->fl_owner;
+       struct nfs4_delegation *dl = fl->c.flc_owner;
        struct svc_rqst *rqst;
        struct nfs4_client *clp;
 
@@ -4975,10 +5256,10 @@ static bool nfsd_breaker_owns_lease(struct file_lock *fl)
 }
 
 static int
-nfsd_change_deleg_cb(struct file_lock *onlist, int arg,
+nfsd_change_deleg_cb(struct file_lease *onlist, int arg,
                     struct list_head *dispose)
 {
-       struct nfs4_delegation *dp = (struct nfs4_delegation *)onlist->fl_owner;
+       struct nfs4_delegation *dp = (struct nfs4_delegation *) onlist->c.flc_owner;
        struct nfs4_client *clp = dp->dl_stid.sc_client;
 
        if (arg & F_UNLCK) {
@@ -4989,7 +5270,7 @@ nfsd_change_deleg_cb(struct file_lock *onlist, int arg,
                return -EAGAIN;
 }
 
-static const struct lock_manager_operations nfsd_lease_mng_ops = {
+static const struct lease_manager_operations nfsd_lease_mng_ops = {
        .lm_breaker_owns_lease = nfsd_breaker_owns_lease,
        .lm_break = nfsd_break_deleg_cb,
        .lm_change = nfsd_change_deleg_cb,
@@ -5113,12 +5394,12 @@ static int share_access_to_flags(u32 share_access)
        return share_access == NFS4_SHARE_ACCESS_READ ? RD_STATE : WR_STATE;
 }
 
-static struct nfs4_delegation *find_deleg_stateid(struct nfs4_client *cl, stateid_t *s)
+static struct nfs4_delegation *find_deleg_stateid(struct nfs4_client *cl,
+                                                 stateid_t *s)
 {
        struct nfs4_stid *ret;
 
-       ret = find_stateid_by_type(cl, s,
-                               NFS4_DELEG_STID|NFS4_REVOKED_DELEG_STID);
+       ret = find_stateid_by_type(cl, s, SC_TYPE_DELEG, SC_STATUS_REVOKED);
        if (!ret)
                return NULL;
        return delegstateid(ret);
@@ -5141,10 +5422,15 @@ nfs4_check_deleg(struct nfs4_client *cl, struct nfsd4_open *open,
        deleg = find_deleg_stateid(cl, &open->op_delegate_stateid);
        if (deleg == NULL)
                goto out;
-       if (deleg->dl_stid.sc_type == NFS4_REVOKED_DELEG_STID) {
+       if (deleg->dl_stid.sc_status & SC_STATUS_ADMIN_REVOKED) {
                nfs4_put_stid(&deleg->dl_stid);
-               if (cl->cl_minorversion)
-                       status = nfserr_deleg_revoked;
+               status = nfserr_admin_revoked;
+               goto out;
+       }
+       if (deleg->dl_stid.sc_status & SC_STATUS_REVOKED) {
+               nfs4_put_stid(&deleg->dl_stid);
+               nfsd40_drop_revoked_stid(cl, &open->op_delegate_stateid);
+               status = nfserr_deleg_revoked;
                goto out;
        }
        flags = share_access_to_flags(open->op_share_access);
@@ -5189,7 +5475,7 @@ nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,
                return 0;
        if (!(open->op_share_access & NFS4_SHARE_ACCESS_WRITE))
                return nfserr_inval;
-       return nfsd_setattr(rqstp, fh, &attrs, 0, (time64_t)0);
+       return nfsd_setattr(rqstp, fh, &attrs, NULL);
 }
 
 static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
@@ -5329,21 +5615,20 @@ static bool nfsd4_cb_channel_good(struct nfs4_client *clp)
        return clp->cl_minorversion && clp->cl_cb_state == NFSD4_CB_UNKNOWN;
 }
 
-static struct file_lock *nfs4_alloc_init_lease(struct nfs4_delegation *dp,
+static struct file_lease *nfs4_alloc_init_lease(struct nfs4_delegation *dp,
                                                int flag)
 {
-       struct file_lock *fl;
+       struct file_lease *fl;
 
-       fl = locks_alloc_lock();
+       fl = locks_alloc_lease();
        if (!fl)
                return NULL;
        fl->fl_lmops = &nfsd_lease_mng_ops;
-       fl->fl_flags = FL_DELEG;
-       fl->fl_type = flag == NFS4_OPEN_DELEGATE_READ? F_RDLCK: F_WRLCK;
-       fl->fl_end = OFFSET_MAX;
-       fl->fl_owner = (fl_owner_t)dp;
-       fl->fl_pid = current->tgid;
-       fl->fl_file = dp->dl_stid.sc_file->fi_deleg_file->nf_file;
+       fl->c.flc_flags = FL_DELEG;
+       fl->c.flc_type = flag == NFS4_OPEN_DELEGATE_READ? F_RDLCK: F_WRLCK;
+       fl->c.flc_owner = (fl_owner_t)dp;
+       fl->c.flc_pid = current->tgid;
+       fl->c.flc_file = dp->dl_stid.sc_file->fi_deleg_file->nf_file;
        return fl;
 }
 
@@ -5461,7 +5746,7 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
        struct nfs4_clnt_odstate *odstate = stp->st_clnt_odstate;
        struct nfs4_delegation *dp;
        struct nfsd_file *nf = NULL;
-       struct file_lock *fl;
+       struct file_lease *fl;
        u32 dl_type;
 
        /*
@@ -5531,9 +5816,10 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
        if (!fl)
                goto out_clnt_odstate;
 
-       status = vfs_setlease(fp->fi_deleg_file->nf_file, fl->fl_type, &fl, NULL);
+       status = kernel_setlease(fp->fi_deleg_file->nf_file,
+                                     fl->c.flc_type, &fl, NULL);
        if (fl)
-               locks_free_lock(fl);
+               locks_free_lease(fl);
        if (status)
                goto out_clnt_odstate;
 
@@ -5560,9 +5846,11 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
                goto out_unlock;
 
        spin_lock(&state_lock);
+       spin_lock(&clp->cl_lock);
        spin_lock(&fp->fi_lock);
        status = hash_delegation_locked(dp, fp);
        spin_unlock(&fp->fi_lock);
+       spin_unlock(&clp->cl_lock);
        spin_unlock(&state_lock);
 
        if (status)
@@ -5570,7 +5858,7 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
 
        return dp;
 out_unlock:
-       vfs_setlease(fp->fi_deleg_file->nf_file, F_UNLCK, NULL, (void **)&dp);
+       kernel_setlease(fp->fi_deleg_file->nf_file, F_UNLCK, NULL, (void **)&dp);
 out_clnt_odstate:
        put_clnt_odstate(dp->dl_clnt_odstate);
        nfs4_put_stid(&dp->dl_stid);
@@ -5634,6 +5922,8 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
        struct svc_fh *parent = NULL;
        int cb_up;
        int status = 0;
+       struct kstat stat;
+       struct path path;
 
        cb_up = nfsd4_cb_channel_good(oo->oo_owner.so_client);
        open->op_recall = false;
@@ -5671,6 +5961,18 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,
        if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE) {
                open->op_delegate_type = NFS4_OPEN_DELEGATE_WRITE;
                trace_nfsd_deleg_write(&dp->dl_stid.sc_stateid);
+               path.mnt = currentfh->fh_export->ex_path.mnt;
+               path.dentry = currentfh->fh_dentry;
+               if (vfs_getattr(&path, &stat,
+                               (STATX_SIZE | STATX_CTIME | STATX_CHANGE_COOKIE),
+                               AT_STATX_SYNC_AS_STAT)) {
+                       nfs4_put_stid(&dp->dl_stid);
+                       destroy_delegation(dp);
+                       goto out_no_deleg;
+               }
+               dp->dl_cb_fattr.ncf_cur_fsize = stat.size;
+               dp->dl_cb_fattr.ncf_initial_cinfo =
+                       nfsd4_change_attribute(&stat, d_inode(currentfh->fh_dentry));
        } else {
                open->op_delegate_type = NFS4_OPEN_DELEGATE_READ;
                trace_nfsd_deleg_read(&dp->dl_stid.sc_stateid);
@@ -5774,7 +6076,6 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf
        } else {
                status = nfs4_get_vfs_file(rqstp, fp, current_fh, stp, open, true);
                if (status) {
-                       stp->st_stid.sc_type = NFS4_CLOSED_STID;
                        release_open_stateid(stp);
                        mutex_unlock(&stp->st_mutex);
                        goto out;
@@ -6128,6 +6429,43 @@ nfs4_process_client_reaplist(struct list_head *reaplist)
        }
 }
 
+static void nfs40_clean_admin_revoked(struct nfsd_net *nn,
+                                     struct laundry_time *lt)
+{
+       struct nfs4_client *clp;
+
+       spin_lock(&nn->client_lock);
+       if (nn->nfs40_last_revoke == 0 ||
+           nn->nfs40_last_revoke > lt->cutoff) {
+               spin_unlock(&nn->client_lock);
+               return;
+       }
+       nn->nfs40_last_revoke = 0;
+
+retry:
+       list_for_each_entry(clp, &nn->client_lru, cl_lru) {
+               unsigned long id, tmp;
+               struct nfs4_stid *stid;
+
+               if (atomic_read(&clp->cl_admin_revoked) == 0)
+                       continue;
+
+               spin_lock(&clp->cl_lock);
+               idr_for_each_entry_ul(&clp->cl_stateids, stid, tmp, id)
+                       if (stid->sc_status & SC_STATUS_ADMIN_REVOKED) {
+                               refcount_inc(&stid->sc_count);
+                               spin_unlock(&nn->client_lock);
+                               /* this function drops ->cl_lock */
+                               nfsd4_drop_revoked_stid(stid);
+                               nfs4_put_stid(stid);
+                               spin_lock(&nn->client_lock);
+                               goto retry;
+                       }
+               spin_unlock(&clp->cl_lock);
+       }
+       spin_unlock(&nn->client_lock);
+}
+
 static time64_t
 nfs4_laundromat(struct nfsd_net *nn)
 {
@@ -6161,12 +6499,14 @@ nfs4_laundromat(struct nfsd_net *nn)
        nfs4_get_client_reaplist(nn, &reaplist, &lt);
        nfs4_process_client_reaplist(&reaplist);
 
+       nfs40_clean_admin_revoked(nn, &lt);
+
        spin_lock(&state_lock);
        list_for_each_safe(pos, next, &nn->del_recall_lru) {
                dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
                if (!state_expired(&lt, dp->dl_time))
                        break;
-               WARN_ON(!unhash_delegation_locked(dp));
+               unhash_delegation_locked(dp, SC_STATUS_REVOKED);
                list_add(&dp->dl_recall_lru, &reaplist);
        }
        spin_unlock(&state_lock);
@@ -6225,6 +6565,8 @@ nfs4_laundromat(struct nfsd_net *nn)
        /* service the server-to-server copy delayed unmount list */
        nfsd4_ssc_expire_umount(nn);
 #endif
+       if (atomic_long_read(&num_delegations) >= max_delegations)
+               deleg_reaper(nn);
 out:
        return max_t(time64_t, lt.new_timeo, NFSD_LAUNDROMAT_MINTIMEOUT);
 }
@@ -6286,6 +6628,8 @@ deleg_reaper(struct nfsd_net *nn)
                list_del_init(&clp->cl_ra_cblist);
                clp->cl_ra->ra_keep = 0;
                clp->cl_ra->ra_bmval[0] = BIT(RCA4_TYPE_MASK_RDATA_DLG);
+               clp->cl_ra->ra_bmval[0] = BIT(RCA4_TYPE_MASK_RDATA_DLG) |
+                                               BIT(RCA4_TYPE_MASK_WDATA_DLG);
                trace_nfsd_cb_recall_any(clp->cl_ra);
                nfsd4_run_cb(&clp->cl_ra->ra_cb);
        }
@@ -6379,6 +6723,9 @@ static __be32 nfsd4_stid_check_stateid_generation(stateid_t *in, struct nfs4_sti
        if (ret == nfs_ok)
                ret = check_stateid_generation(in, &s->sc_stateid, has_session);
        spin_unlock(&s->sc_lock);
+       if (ret == nfserr_admin_revoked)
+               nfsd40_drop_revoked_stid(s->sc_client,
+                                       &s->sc_stateid);
        return ret;
 }
 
@@ -6405,32 +6752,33 @@ static __be32 nfsd4_validate_stateid(struct nfs4_client *cl, stateid_t *stateid)
        status = nfsd4_stid_check_stateid_generation(stateid, s, 1);
        if (status)
                goto out_unlock;
+       status = nfsd4_verify_open_stid(s);
+       if (status)
+               goto out_unlock;
+
        switch (s->sc_type) {
-       case NFS4_DELEG_STID:
+       case SC_TYPE_DELEG:
                status = nfs_ok;
                break;
-       case NFS4_REVOKED_DELEG_STID:
-               status = nfserr_deleg_revoked;
-               break;
-       case NFS4_OPEN_STID:
-       case NFS4_LOCK_STID:
+       case SC_TYPE_OPEN:
+       case SC_TYPE_LOCK:
                status = nfsd4_check_openowner_confirmed(openlockstateid(s));
                break;
        default:
                printk("unknown stateid type %x\n", s->sc_type);
-               fallthrough;
-       case NFS4_CLOSED_STID:
-       case NFS4_CLOSED_DELEG_STID:
                status = nfserr_bad_stateid;
        }
 out_unlock:
        spin_unlock(&cl->cl_lock);
+       if (status == nfserr_admin_revoked)
+               nfsd40_drop_revoked_stid(cl, stateid);
        return status;
 }
 
 __be32
 nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate,
-                    stateid_t *stateid, unsigned char typemask,
+                    stateid_t *stateid,
+                    unsigned short typemask, unsigned short statusmask,
                     struct nfs4_stid **s, struct nfsd_net *nn)
 {
        __be32 status;
@@ -6441,10 +6789,15 @@ nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate,
         *  only return revoked delegations if explicitly asked.
         *  otherwise we report revoked or bad_stateid status.
         */
-       if (typemask & NFS4_REVOKED_DELEG_STID)
+       if (statusmask & SC_STATUS_REVOKED)
                return_revoked = true;
-       else if (typemask & NFS4_DELEG_STID)
-               typemask |= NFS4_REVOKED_DELEG_STID;
+       if (typemask & SC_TYPE_DELEG)
+               /* Always allow REVOKED for DELEG so we can
+                * retturn the appropriate error.
+                */
+               statusmask |= SC_STATUS_REVOKED;
+
+       statusmask |= SC_STATUS_ADMIN_REVOKED;
 
        if (ZERO_STATEID(stateid) || ONE_STATEID(stateid) ||
                CLOSE_STATEID(stateid))
@@ -6457,14 +6810,17 @@ nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate,
        }
        if (status)
                return status;
-       stid = find_stateid_by_type(cstate->clp, stateid, typemask);
+       stid = find_stateid_by_type(cstate->clp, stateid, typemask, statusmask);
        if (!stid)
                return nfserr_bad_stateid;
-       if ((stid->sc_type == NFS4_REVOKED_DELEG_STID) && !return_revoked) {
+       if ((stid->sc_status & SC_STATUS_REVOKED) && !return_revoked) {
                nfs4_put_stid(stid);
-               if (cstate->minorversion)
-                       return nfserr_deleg_revoked;
-               return nfserr_bad_stateid;
+               return nfserr_deleg_revoked;
+       }
+       if (stid->sc_status & SC_STATUS_ADMIN_REVOKED) {
+               nfsd40_drop_revoked_stid(cstate->clp, stateid);
+               nfs4_put_stid(stid);
+               return nfserr_admin_revoked;
        }
        *s = stid;
        return nfs_ok;
@@ -6475,17 +6831,17 @@ nfs4_find_file(struct nfs4_stid *s, int flags)
 {
        struct nfsd_file *ret = NULL;
 
-       if (!s)
+       if (!s || s->sc_status)
                return NULL;
 
        switch (s->sc_type) {
-       case NFS4_DELEG_STID:
+       case SC_TYPE_DELEG:
                spin_lock(&s->sc_file->fi_lock);
                ret = nfsd_file_get(s->sc_file->fi_deleg_file);
                spin_unlock(&s->sc_file->fi_lock);
                break;
-       case NFS4_OPEN_STID:
-       case NFS4_LOCK_STID:
+       case SC_TYPE_OPEN:
+       case SC_TYPE_LOCK:
                if (flags & RD_STATE)
                        ret = find_readable_file(s->sc_file);
                else
@@ -6598,7 +6954,8 @@ static __be32 find_cpntf_state(struct nfsd_net *nn, stateid_t *st,
                goto out;
 
        *stid = find_stateid_by_type(found, &cps->cp_p_stateid,
-                       NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID);
+                                    SC_TYPE_DELEG|SC_TYPE_OPEN|SC_TYPE_LOCK,
+                                    0);
        if (*stid)
                status = nfs_ok;
        else
@@ -6655,8 +7012,8 @@ nfs4_preprocess_stateid_op(struct svc_rqst *rqstp,
        }
 
        status = nfsd4_lookup_stateid(cstate, stateid,
-                               NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID,
-                               &s, nn);
+                               SC_TYPE_DELEG|SC_TYPE_OPEN|SC_TYPE_LOCK,
+                               0, &s, nn);
        if (status == nfserr_bad_stateid)
                status = find_cpntf_state(nn, stateid, &s);
        if (status)
@@ -6667,16 +7024,13 @@ nfs4_preprocess_stateid_op(struct svc_rqst *rqstp,
                goto out;
 
        switch (s->sc_type) {
-       case NFS4_DELEG_STID:
+       case SC_TYPE_DELEG:
                status = nfs4_check_delegmode(delegstateid(s), flags);
                break;
-       case NFS4_OPEN_STID:
-       case NFS4_LOCK_STID:
+       case SC_TYPE_OPEN:
+       case SC_TYPE_LOCK:
                status = nfs4_check_olstateid(openlockstateid(s), flags);
                break;
-       default:
-               status = nfserr_bad_stateid;
-               break;
        }
        if (status)
                goto out;
@@ -6755,34 +7109,39 @@ nfsd4_free_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 
        spin_lock(&cl->cl_lock);
        s = find_stateid_locked(cl, stateid);
-       if (!s)
+       if (!s || s->sc_status & SC_STATUS_CLOSED)
                goto out_unlock;
+       if (s->sc_status & SC_STATUS_ADMIN_REVOKED) {
+               nfsd4_drop_revoked_stid(s);
+               ret = nfs_ok;
+               goto out;
+       }
        spin_lock(&s->sc_lock);
        switch (s->sc_type) {
-       case NFS4_DELEG_STID:
+       case SC_TYPE_DELEG:
+               if (s->sc_status & SC_STATUS_REVOKED) {
+                       spin_unlock(&s->sc_lock);
+                       dp = delegstateid(s);
+                       list_del_init(&dp->dl_recall_lru);
+                       spin_unlock(&cl->cl_lock);
+                       nfs4_put_stid(s);
+                       ret = nfs_ok;
+                       goto out;
+               }
                ret = nfserr_locks_held;
                break;
-       case NFS4_OPEN_STID:
+       case SC_TYPE_OPEN:
                ret = check_stateid_generation(stateid, &s->sc_stateid, 1);
                if (ret)
                        break;
                ret = nfserr_locks_held;
                break;
-       case NFS4_LOCK_STID:
+       case SC_TYPE_LOCK:
                spin_unlock(&s->sc_lock);
                refcount_inc(&s->sc_count);
                spin_unlock(&cl->cl_lock);
                ret = nfsd4_free_lock_stateid(stateid, s);
                goto out;
-       case NFS4_REVOKED_DELEG_STID:
-               spin_unlock(&s->sc_lock);
-               dp = delegstateid(s);
-               list_del_init(&dp->dl_recall_lru);
-               spin_unlock(&cl->cl_lock);
-               nfs4_put_stid(s);
-               ret = nfs_ok;
-               goto out;
-       /* Default falls through and returns nfserr_bad_stateid */
        }
        spin_unlock(&s->sc_lock);
 out_unlock:
@@ -6824,6 +7183,7 @@ static __be32 nfs4_seqid_op_checks(struct nfsd4_compound_state *cstate, stateid_
  * @seqid: seqid (provided by client)
  * @stateid: stateid (provided by client)
  * @typemask: mask of allowable types for this operation
+ * @statusmask: mask of allowed states: 0 or STID_CLOSED
  * @stpp: return pointer for the stateid found
  * @nn: net namespace for request
  *
@@ -6833,7 +7193,8 @@ static __be32 nfs4_seqid_op_checks(struct nfsd4_compound_state *cstate, stateid_
  */
 static __be32
 nfs4_preprocess_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid,
-                        stateid_t *stateid, char typemask,
+                        stateid_t *stateid,
+                        unsigned short typemask, unsigned short statusmask,
                         struct nfs4_ol_stateid **stpp,
                         struct nfsd_net *nn)
 {
@@ -6844,7 +7205,8 @@ nfs4_preprocess_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid,
        trace_nfsd_preprocess(seqid, stateid);
 
        *stpp = NULL;
-       status = nfsd4_lookup_stateid(cstate, stateid, typemask, &s, nn);
+       status = nfsd4_lookup_stateid(cstate, stateid,
+                                     typemask, statusmask, &s, nn);
        if (status)
                return status;
        stp = openlockstateid(s);
@@ -6866,7 +7228,7 @@ static __be32 nfs4_preprocess_confirmed_seqid_op(struct nfsd4_compound_state *cs
        struct nfs4_ol_stateid *stp;
 
        status = nfs4_preprocess_seqid_op(cstate, seqid, stateid,
-                                               NFS4_OPEN_STID, &stp, nn);
+                                         SC_TYPE_OPEN, 0, &stp, nn);
        if (status)
                return status;
        oo = openowner(stp->st_stateowner);
@@ -6897,8 +7259,8 @@ nfsd4_open_confirm(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                return status;
 
        status = nfs4_preprocess_seqid_op(cstate,
-                                       oc->oc_seqid, &oc->oc_req_stateid,
-                                       NFS4_OPEN_STID, &stp, nn);
+                                         oc->oc_seqid, &oc->oc_req_stateid,
+                                         SC_TYPE_OPEN, 0, &stp, nn);
        if (status)
                goto out;
        oo = openowner(stp->st_stateowner);
@@ -7028,18 +7390,20 @@ nfsd4_close(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        struct net *net = SVC_NET(rqstp);
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
-       dprintk("NFSD: nfsd4_close on file %pd\n", 
+       dprintk("NFSD: nfsd4_close on file %pd\n",
                        cstate->current_fh.fh_dentry);
 
        status = nfs4_preprocess_seqid_op(cstate, close->cl_seqid,
-                                       &close->cl_stateid,
-                                       NFS4_OPEN_STID|NFS4_CLOSED_STID,
-                                       &stp, nn);
+                                         &close->cl_stateid,
+                                         SC_TYPE_OPEN, SC_STATUS_CLOSED,
+                                         &stp, nn);
        nfsd4_bump_seqid(cstate, status);
        if (status)
-               goto out; 
+               goto out;
 
-       stp->st_stid.sc_type = NFS4_CLOSED_STID;
+       spin_lock(&stp->st_stid.sc_client->cl_lock);
+       stp->st_stid.sc_status |= SC_STATUS_CLOSED;
+       spin_unlock(&stp->st_stid.sc_client->cl_lock);
 
        /*
         * Technically we don't _really_ have to increment or copy it, since
@@ -7081,7 +7445,7 @@ nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        if ((status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0)))
                return status;
 
-       status = nfsd4_lookup_stateid(cstate, stateid, NFS4_DELEG_STID, &s, nn);
+       status = nfsd4_lookup_stateid(cstate, stateid, SC_TYPE_DELEG, 0, &s, nn);
        if (status)
                goto out;
        dp = delegstateid(s);
@@ -7148,7 +7512,7 @@ nfsd4_lm_put_owner(fl_owner_t owner)
 static bool
 nfsd4_lm_lock_expirable(struct file_lock *cfl)
 {
-       struct nfs4_lockowner *lo = (struct nfs4_lockowner *)cfl->fl_owner;
+       struct nfs4_lockowner *lo = (struct nfs4_lockowner *) cfl->c.flc_owner;
        struct nfs4_client *clp = lo->lo_owner.so_client;
        struct nfsd_net *nn;
 
@@ -7170,7 +7534,7 @@ nfsd4_lm_expire_lock(void)
 static void
 nfsd4_lm_notify(struct file_lock *fl)
 {
-       struct nfs4_lockowner           *lo = (struct nfs4_lockowner *)fl->fl_owner;
+       struct nfs4_lockowner           *lo = (struct nfs4_lockowner *) fl->c.flc_owner;
        struct net                      *net = lo->lo_owner.so_client->net;
        struct nfsd_net                 *nn = net_generic(net, nfsd_net_id);
        struct nfsd4_blocked_lock       *nbl = container_of(fl,
@@ -7207,7 +7571,7 @@ nfs4_set_lock_denied(struct file_lock *fl, struct nfsd4_lock_denied *deny)
        struct nfs4_lockowner *lo;
 
        if (fl->fl_lmops == &nfsd_posix_mng_ops) {
-               lo = (struct nfs4_lockowner *) fl->fl_owner;
+               lo = (struct nfs4_lockowner *) fl->c.flc_owner;
                xdr_netobj_dup(&deny->ld_owner, &lo->lo_owner.so_owner,
                                                GFP_KERNEL);
                if (!deny->ld_owner.data)
@@ -7226,7 +7590,7 @@ nevermind:
        if (fl->fl_end != NFS4_MAX_UINT64)
                deny->ld_length = fl->fl_end - fl->fl_start + 1;        
        deny->ld_type = NFS4_READ_LT;
-       if (fl->fl_type != F_RDLCK)
+       if (fl->c.flc_type != F_RDLCK)
                deny->ld_type = NFS4_WRITE_LT;
 }
 
@@ -7348,7 +7712,7 @@ retry:
        if (retstp)
                goto out_found;
        refcount_inc(&stp->st_stid.sc_count);
-       stp->st_stid.sc_type = NFS4_LOCK_STID;
+       stp->st_stid.sc_type = SC_TYPE_LOCK;
        stp->st_stateowner = nfs4_get_stateowner(&lo->lo_owner);
        get_nfs4_file(fp);
        stp->st_stid.sc_file = fp;
@@ -7492,8 +7856,8 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        int lkflg;
        int err;
        bool new = false;
-       unsigned char fl_type;
-       unsigned int fl_flags = FL_POSIX;
+       unsigned char type;
+       unsigned int flags = FL_POSIX;
        struct net *net = SVC_NET(rqstp);
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
@@ -7535,9 +7899,10 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                                                        &lock_stp, &new);
        } else {
                status = nfs4_preprocess_seqid_op(cstate,
-                                      lock->lk_old_lock_seqid,
-                                      &lock->lk_old_lock_stateid,
-                                      NFS4_LOCK_STID, &lock_stp, nn);
+                                                 lock->lk_old_lock_seqid,
+                                                 &lock->lk_old_lock_stateid,
+                                                 SC_TYPE_LOCK, 0, &lock_stp,
+                                                 nn);
        }
        if (status)
                goto out;
@@ -7556,14 +7921,14 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                goto out;
 
        if (lock->lk_reclaim)
-               fl_flags |= FL_RECLAIM;
+               flags |= FL_RECLAIM;
 
        fp = lock_stp->st_stid.sc_file;
        switch (lock->lk_type) {
                case NFS4_READW_LT:
                        if (nfsd4_has_session(cstate) ||
                            exportfs_lock_op_is_async(sb->s_export_op))
-                               fl_flags |= FL_SLEEP;
+                               flags |= FL_SLEEP;
                        fallthrough;
                case NFS4_READ_LT:
                        spin_lock(&fp->fi_lock);
@@ -7571,12 +7936,12 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                        if (nf)
                                get_lock_access(lock_stp, NFS4_SHARE_ACCESS_READ);
                        spin_unlock(&fp->fi_lock);
-                       fl_type = F_RDLCK;
+                       type = F_RDLCK;
                        break;
                case NFS4_WRITEW_LT:
                        if (nfsd4_has_session(cstate) ||
                            exportfs_lock_op_is_async(sb->s_export_op))
-                               fl_flags |= FL_SLEEP;
+                               flags |= FL_SLEEP;
                        fallthrough;
                case NFS4_WRITE_LT:
                        spin_lock(&fp->fi_lock);
@@ -7584,7 +7949,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                        if (nf)
                                get_lock_access(lock_stp, NFS4_SHARE_ACCESS_WRITE);
                        spin_unlock(&fp->fi_lock);
-                       fl_type = F_WRLCK;
+                       type = F_WRLCK;
                        break;
                default:
                        status = nfserr_inval;
@@ -7604,7 +7969,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
         * on those filesystems:
         */
        if (!exportfs_lock_op_is_async(sb->s_export_op))
-               fl_flags &= ~FL_SLEEP;
+               flags &= ~FL_SLEEP;
 
        nbl = find_or_allocate_block(lock_sop, &fp->fi_fhandle, nn);
        if (!nbl) {
@@ -7614,11 +7979,11 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        }
 
        file_lock = &nbl->nbl_lock;
-       file_lock->fl_type = fl_type;
-       file_lock->fl_owner = (fl_owner_t)lockowner(nfs4_get_stateowner(&lock_sop->lo_owner));
-       file_lock->fl_pid = current->tgid;
-       file_lock->fl_file = nf->nf_file;
-       file_lock->fl_flags = fl_flags;
+       file_lock->c.flc_type = type;
+       file_lock->c.flc_owner = (fl_owner_t)lockowner(nfs4_get_stateowner(&lock_sop->lo_owner));
+       file_lock->c.flc_pid = current->tgid;
+       file_lock->c.flc_file = nf->nf_file;
+       file_lock->c.flc_flags = flags;
        file_lock->fl_lmops = &nfsd_posix_mng_ops;
        file_lock->fl_start = lock->lk_offset;
        file_lock->fl_end = last_byte_offset(lock->lk_offset, lock->lk_length);
@@ -7631,7 +7996,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                goto out;
        }
 
-       if (fl_flags & FL_SLEEP) {
+       if (flags & FL_SLEEP) {
                nbl->nbl_time = ktime_get_boottime_seconds();
                spin_lock(&nn->blocked_locks_lock);
                list_add_tail(&nbl->nbl_list, &lock_sop->lo_blocked);
@@ -7668,7 +8033,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 out:
        if (nbl) {
                /* dequeue it if we queued it before */
-               if (fl_flags & FL_SLEEP) {
+               if (flags & FL_SLEEP) {
                        spin_lock(&nn->blocked_locks_lock);
                        if (!list_empty(&nbl->nbl_list) &&
                            !list_empty(&nbl->nbl_lru)) {
@@ -7736,9 +8101,9 @@ static __be32 nfsd_test_lock(struct svc_rqst *rqstp, struct svc_fh *fhp, struct
        err = nfserrno(nfsd_open_break_lease(inode, NFSD_MAY_READ));
        if (err)
                goto out;
-       lock->fl_file = nf->nf_file;
+       lock->c.flc_file = nf->nf_file;
        err = nfserrno(vfs_test_lock(nf->nf_file, lock));
-       lock->fl_file = NULL;
+       lock->c.flc_file = NULL;
 out:
        inode_unlock(inode);
        nfsd_file_put(nf);
@@ -7783,11 +8148,11 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        switch (lockt->lt_type) {
                case NFS4_READ_LT:
                case NFS4_READW_LT:
-                       file_lock->fl_type = F_RDLCK;
+                       file_lock->c.flc_type = F_RDLCK;
                        break;
                case NFS4_WRITE_LT:
                case NFS4_WRITEW_LT:
-                       file_lock->fl_type = F_WRLCK;
+                       file_lock->c.flc_type = F_WRLCK;
                        break;
                default:
                        dprintk("NFSD: nfs4_lockt: bad lock type!\n");
@@ -7797,9 +8162,9 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 
        lo = find_lockowner_str(cstate->clp, &lockt->lt_owner);
        if (lo)
-               file_lock->fl_owner = (fl_owner_t)lo;
-       file_lock->fl_pid = current->tgid;
-       file_lock->fl_flags = FL_POSIX;
+               file_lock->c.flc_owner = (fl_owner_t)lo;
+       file_lock->c.flc_pid = current->tgid;
+       file_lock->c.flc_flags = FL_POSIX;
 
        file_lock->fl_start = lockt->lt_offset;
        file_lock->fl_end = last_byte_offset(lockt->lt_offset, lockt->lt_length);
@@ -7810,7 +8175,7 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        if (status)
                goto out;
 
-       if (file_lock->fl_type != F_UNLCK) {
+       if (file_lock->c.flc_type != F_UNLCK) {
                status = nfserr_denied;
                nfs4_set_lock_denied(file_lock, &lockt->lt_denied);
        }
@@ -7850,8 +8215,8 @@ nfsd4_locku(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                 return nfserr_inval;
 
        status = nfs4_preprocess_seqid_op(cstate, locku->lu_seqid,
-                                       &locku->lu_stateid, NFS4_LOCK_STID,
-                                       &stp, nn);
+                                         &locku->lu_stateid, SC_TYPE_LOCK, 0,
+                                         &stp, nn);
        if (status)
                goto out;
        nf = find_any_file(stp->st_stid.sc_file);
@@ -7866,11 +8231,11 @@ nfsd4_locku(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                goto put_file;
        }
 
-       file_lock->fl_type = F_UNLCK;
-       file_lock->fl_owner = (fl_owner_t)lockowner(nfs4_get_stateowner(stp->st_stateowner));
-       file_lock->fl_pid = current->tgid;
-       file_lock->fl_file = nf->nf_file;
-       file_lock->fl_flags = FL_POSIX;
+       file_lock->c.flc_type = F_UNLCK;
+       file_lock->c.flc_owner = (fl_owner_t)lockowner(nfs4_get_stateowner(stp->st_stateowner));
+       file_lock->c.flc_pid = current->tgid;
+       file_lock->c.flc_file = nf->nf_file;
+       file_lock->c.flc_flags = FL_POSIX;
        file_lock->fl_lmops = &nfsd_posix_mng_ops;
        file_lock->fl_start = locku->lu_offset;
 
@@ -7927,8 +8292,8 @@ check_for_locks(struct nfs4_file *fp, struct nfs4_lockowner *lowner)
 
        if (flctx && !list_empty_careful(&flctx->flc_posix)) {
                spin_lock(&flctx->flc_lock);
-               list_for_each_entry(fl, &flctx->flc_posix, fl_list) {
-                       if (fl->fl_owner == (fl_owner_t)lowner) {
+               for_each_file_lock(fl, &flctx->flc_posix) {
+                       if (fl->c.flc_owner == (fl_owner_t)lowner) {
                                status = true;
                                break;
                        }
@@ -7996,7 +8361,7 @@ nfsd4_release_lockowner(struct svc_rqst *rqstp,
                stp = list_first_entry(&lo->lo_owner.so_stateids,
                                       struct nfs4_ol_stateid,
                                       st_perstateowner);
-               WARN_ON(!unhash_lock_stateid(stp));
+               unhash_lock_stateid(stp);
                put_ol_stateid_locked(stp, &reaplist);
        }
        spin_unlock(&clp->cl_lock);
@@ -8289,7 +8654,7 @@ nfs4_state_shutdown_net(struct net *net)
        spin_lock(&state_lock);
        list_for_each_safe(pos, next, &nn->del_recall_lru) {
                dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);
-               WARN_ON(!unhash_delegation_locked(dp));
+               unhash_delegation_locked(dp, SC_STATUS_CLOSED);
                list_add(&dp->dl_recall_lru, &reaplist);
        }
        spin_unlock(&state_lock);
@@ -8431,6 +8796,8 @@ 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
+ * @modified: return true if file was modified
+ * @size: new size of file if modified is true
  *
  * This function is called when there is a conflict between a write
  * delegation and a change/size GETATTR from another client. The server
@@ -8439,27 +8806,30 @@ nfsd4_get_writestateid(struct nfsd4_compound_state *cstate,
  * delegation before replying to the GETATTR. See RFC 8881 section
  * 18.7.4.
  *
- * The current implementation does not support CB_GETATTR yet. However
- * this can avoid recalling the delegation could be added in follow up
- * work.
- *
  * Returns 0 if there is no conflict; otherwise an nfs_stat
  * code is returned.
  */
 __be32
-nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode)
+nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode,
+                               bool *modified, u64 *size)
 {
        __be32 status;
+       struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
        struct file_lock_context *ctx;
-       struct file_lock *fl;
+       struct file_lease *fl;
        struct nfs4_delegation *dp;
+       struct iattr attrs;
+       struct nfs4_cb_fattr *ncf;
 
+       *modified = false;
        ctx = locks_inode_context(inode);
        if (!ctx)
                return 0;
        spin_lock(&ctx->flc_lock);
-       list_for_each_entry(fl, &ctx->flc_lease, fl_list) {
-               if (fl->fl_flags == FL_LAYOUT)
+       for_each_file_lock(fl, &ctx->flc_lease) {
+               unsigned char type = fl->c.flc_type;
+
+               if (fl->c.flc_flags == FL_LAYOUT)
                        continue;
                if (fl->fl_lmops != &nfsd_lease_mng_ops) {
                        /*
@@ -8467,23 +8837,49 @@ nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp, struct inode *inode)
                         * we are done; there isn't any write delegation
                         * on this inode
                         */
-                       if (fl->fl_type == F_RDLCK)
+                       if (type == F_RDLCK)
                                break;
                        goto break_lease;
                }
-               if (fl->fl_type == F_WRLCK) {
-                       dp = fl->fl_owner;
+               if (type == F_WRLCK) {
+                       dp = fl->c.flc_owner;
                        if (dp->dl_recall.cb_clp == *(rqstp->rq_lease_breaker)) {
                                spin_unlock(&ctx->flc_lock);
                                return 0;
                        }
 break_lease:
+                       nfsd_stats_wdeleg_getattr_inc(nn);
+                       dp = fl->c.flc_owner;
+                       ncf = &dp->dl_cb_fattr;
+                       nfs4_cb_getattr(&dp->dl_cb_fattr);
                        spin_unlock(&ctx->flc_lock);
-                       nfsd_stats_wdeleg_getattr_inc();
-                       status = nfserrno(nfsd_open_break_lease(inode, NFSD_MAY_READ));
-                       if (status != nfserr_jukebox ||
-                                       !nfsd_wait_for_delegreturn(rqstp, inode))
-                               return status;
+                       wait_on_bit_timeout(&ncf->ncf_cb_flags, CB_GETATTR_BUSY,
+                                       TASK_INTERRUPTIBLE, NFSD_CB_GETATTR_TIMEOUT);
+                       if (ncf->ncf_cb_status) {
+                               /* Recall delegation only if client didn't respond */
+                               status = nfserrno(nfsd_open_break_lease(inode, NFSD_MAY_READ));
+                               if (status != nfserr_jukebox ||
+                                               !nfsd_wait_for_delegreturn(rqstp, inode))
+                                       return status;
+                       }
+                       if (!ncf->ncf_file_modified &&
+                                       (ncf->ncf_initial_cinfo != ncf->ncf_cb_change ||
+                                       ncf->ncf_cur_fsize != ncf->ncf_cb_fsize))
+                               ncf->ncf_file_modified = true;
+                       if (ncf->ncf_file_modified) {
+                               /*
+                                * 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);
+                               ncf->ncf_cur_fsize = ncf->ncf_cb_fsize;
+                               *size = ncf->ncf_cur_fsize;
+                               *modified = true;
+                       }
                        return 0;
                }
                break;