nfsd: reduce some spinlocking in put_client_renew
[cascardo/linux.git] / fs / nfsd / nfs4state.c
index c0d45ce..3704789 100644 (file)
@@ -41,6 +41,7 @@
 #include <linux/ratelimit.h>
 #include <linux/sunrpc/svcauth_gss.h>
 #include <linux/sunrpc/addr.h>
+#include <linux/hash.h>
 #include "xdr4.h"
 #include "xdr4cb.h"
 #include "vfs.h"
@@ -102,12 +103,6 @@ static bool is_session_dead(struct nfsd4_session *ses)
        return ses->se_flags & NFS4_SESSION_DEAD;
 }
 
-void nfsd4_put_session(struct nfsd4_session *ses)
-{
-       if (atomic_dec_and_test(&ses->se_ref) && is_session_dead(ses))
-               free_session(ses);
-}
-
 static __be32 mark_session_dead_locked(struct nfsd4_session *ses, int ref_held_by_me)
 {
        if (atomic_read(&ses->se_ref) > ref_held_by_me)
@@ -116,14 +111,6 @@ static __be32 mark_session_dead_locked(struct nfsd4_session *ses, int ref_held_b
        return nfs_ok;
 }
 
-static __be32 nfsd4_get_session_locked(struct nfsd4_session *ses)
-{
-       if (is_session_dead(ses))
-               return nfserr_badsession;
-       atomic_inc(&ses->se_ref);
-       return nfs_ok;
-}
-
 void
 nfs4_unlock_state(void)
 {
@@ -202,7 +189,7 @@ static void put_client_renew_locked(struct nfs4_client *clp)
                renew_client_locked(clp);
 }
 
-void put_client_renew(struct nfs4_client *clp)
+static void put_client_renew(struct nfs4_client *clp)
 {
        struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
 
@@ -213,6 +200,38 @@ void put_client_renew(struct nfs4_client *clp)
        spin_unlock(&nn->client_lock);
 }
 
+static __be32 nfsd4_get_session_locked(struct nfsd4_session *ses)
+{
+       __be32 status;
+
+       if (is_session_dead(ses))
+               return nfserr_badsession;
+       status = get_client_locked(ses->se_client);
+       if (status)
+               return status;
+       atomic_inc(&ses->se_ref);
+       return nfs_ok;
+}
+
+static void nfsd4_put_session_locked(struct nfsd4_session *ses)
+{
+       struct nfs4_client *clp = ses->se_client;
+
+       if (atomic_dec_and_test(&ses->se_ref) && is_session_dead(ses))
+               free_session(ses);
+       put_client_renew_locked(clp);
+}
+
+static void nfsd4_put_session(struct nfsd4_session *ses)
+{
+       struct nfs4_client *clp = ses->se_client;
+       struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
+
+       spin_lock(&nn->client_lock);
+       nfsd4_put_session_locked(ses);
+       spin_unlock(&nn->client_lock);
+}
+
 
 static inline u32
 opaque_hashval(const void *ptr, int nbytes)
@@ -364,6 +383,79 @@ static struct nfs4_ol_stateid * nfs4_alloc_stateid(struct nfs4_client *clp)
        return openlockstateid(nfs4_alloc_stid(clp, stateid_slab));
 }
 
+/*
+ * When we recall a delegation, we should be careful not to hand it
+ * out again straight away.
+ * To ensure this we keep a pair of bloom filters ('new' and 'old')
+ * in which the filehandles of recalled delegations are "stored".
+ * If a filehandle appear in either filter, a delegation is blocked.
+ * When a delegation is recalled, the filehandle is stored in the "new"
+ * filter.
+ * Every 30 seconds we swap the filters and clear the "new" one,
+ * unless both are empty of course.
+ *
+ * Each filter is 256 bits.  We hash the filehandle to 32bit and use the
+ * low 3 bytes as hash-table indices.
+ *
+ * 'state_lock', which is always held when block_delegations() is called,
+ * is used to manage concurrent access.  Testing does not need the lock
+ * except when swapping the two filters.
+ */
+static struct bloom_pair {
+       int     entries, old_entries;
+       time_t  swap_time;
+       int     new; /* index into 'set' */
+       DECLARE_BITMAP(set[2], 256);
+} blocked_delegations;
+
+static int delegation_blocked(struct knfsd_fh *fh)
+{
+       u32 hash;
+       struct bloom_pair *bd = &blocked_delegations;
+
+       if (bd->entries == 0)
+               return 0;
+       if (seconds_since_boot() - bd->swap_time > 30) {
+               spin_lock(&state_lock);
+               if (seconds_since_boot() - bd->swap_time > 30) {
+                       bd->entries -= bd->old_entries;
+                       bd->old_entries = bd->entries;
+                       memset(bd->set[bd->new], 0,
+                              sizeof(bd->set[0]));
+                       bd->new = 1-bd->new;
+                       bd->swap_time = seconds_since_boot();
+               }
+               spin_unlock(&state_lock);
+       }
+       hash = arch_fast_hash(&fh->fh_base, fh->fh_size, 0);
+       if (test_bit(hash&255, bd->set[0]) &&
+           test_bit((hash>>8)&255, bd->set[0]) &&
+           test_bit((hash>>16)&255, bd->set[0]))
+               return 1;
+
+       if (test_bit(hash&255, bd->set[1]) &&
+           test_bit((hash>>8)&255, bd->set[1]) &&
+           test_bit((hash>>16)&255, bd->set[1]))
+               return 1;
+
+       return 0;
+}
+
+static void block_delegations(struct knfsd_fh *fh)
+{
+       u32 hash;
+       struct bloom_pair *bd = &blocked_delegations;
+
+       hash = arch_fast_hash(&fh->fh_base, fh->fh_size, 0);
+
+       __set_bit(hash&255, bd->set[bd->new]);
+       __set_bit((hash>>8)&255, bd->set[bd->new]);
+       __set_bit((hash>>16)&255, bd->set[bd->new]);
+       if (bd->entries == 0)
+               bd->swap_time = seconds_since_boot();
+       bd->entries += 1;
+}
+
 static struct nfs4_delegation *
 alloc_init_deleg(struct nfs4_client *clp, struct nfs4_ol_stateid *stp, struct svc_fh *current_fh)
 {
@@ -372,6 +464,8 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_ol_stateid *stp, struct sv
        dprintk("NFSD alloc_init_deleg\n");
        if (num_delegations > max_delegations)
                return NULL;
+       if (delegation_blocked(&current_fh->fh_handle))
+               return NULL;
        dp = delegstateid(nfs4_alloc_stid(clp, deleg_slab));
        if (dp == NULL)
                return dp;
@@ -631,10 +725,11 @@ static void free_generic_stateid(struct nfs4_ol_stateid *stp)
        nfs4_free_stid(stateid_slab, &stp->st_stid);
 }
 
-static void release_lock_stateid(struct nfs4_ol_stateid *stp)
+static void __release_lock_stateid(struct nfs4_ol_stateid *stp)
 {
        struct file *file;
 
+       list_del(&stp->st_locks);
        unhash_generic_stateid(stp);
        unhash_stid(&stp->st_stid);
        file = find_any_file(stp->st_file);
@@ -649,12 +744,10 @@ static void unhash_lockowner(struct nfs4_lockowner *lo)
        struct nfs4_ol_stateid *stp;
 
        list_del(&lo->lo_owner.so_strhash);
-       list_del(&lo->lo_perstateid);
-       list_del(&lo->lo_owner_ino_hash);
        while (!list_empty(&lo->lo_owner.so_stateids)) {
                stp = list_first_entry(&lo->lo_owner.so_stateids,
                                struct nfs4_ol_stateid, st_perstateowner);
-               release_lock_stateid(stp);
+               __release_lock_stateid(stp);
        }
 }
 
@@ -670,22 +763,36 @@ static void release_lockowner(struct nfs4_lockowner *lo)
        nfs4_free_lockowner(lo);
 }
 
-static void
-release_stateid_lockowners(struct nfs4_ol_stateid *open_stp)
+static void release_lockowner_if_empty(struct nfs4_lockowner *lo)
+{
+       if (list_empty(&lo->lo_owner.so_stateids))
+               release_lockowner(lo);
+}
+
+static void release_lock_stateid(struct nfs4_ol_stateid *stp)
 {
        struct nfs4_lockowner *lo;
 
-       while (!list_empty(&open_stp->st_lockowners)) {
-               lo = list_entry(open_stp->st_lockowners.next,
-                               struct nfs4_lockowner, lo_perstateid);
-               release_lockowner(lo);
+       lo = lockowner(stp->st_stateowner);
+       __release_lock_stateid(stp);
+       release_lockowner_if_empty(lo);
+}
+
+static void release_open_stateid_locks(struct nfs4_ol_stateid *open_stp)
+{
+       struct nfs4_ol_stateid *stp;
+
+       while (!list_empty(&open_stp->st_locks)) {
+               stp = list_entry(open_stp->st_locks.next,
+                               struct nfs4_ol_stateid, st_locks);
+               release_lock_stateid(stp);
        }
 }
 
 static void unhash_open_stateid(struct nfs4_ol_stateid *stp)
 {
        unhash_generic_stateid(stp);
-       release_stateid_lockowners(stp);
+       release_open_stateid_locks(stp);
        close_generic_stateid(stp);
 }
 
@@ -1044,7 +1151,7 @@ static void init_session(struct svc_rqst *rqstp, struct nfsd4_session *new, stru
 
 /* caller must hold client_lock */
 static struct nfsd4_session *
-find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net)
+__find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net)
 {
        struct nfsd4_session *elem;
        int idx;
@@ -1064,6 +1171,24 @@ find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net)
        return NULL;
 }
 
+static struct nfsd4_session *
+find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net,
+               __be32 *ret)
+{
+       struct nfsd4_session *session;
+       __be32 status = nfserr_badsession;
+
+       session = __find_in_sessionid_hashtbl(sessionid, net);
+       if (!session)
+               goto out;
+       status = nfsd4_get_session_locked(session);
+       if (status)
+               session = NULL;
+out:
+       *ret = status;
+       return session;
+}
+
 /* caller must hold client_lock */
 static void
 unhash_session(struct nfsd4_session *ses)
@@ -1165,6 +1290,8 @@ destroy_client(struct nfs4_client *clp)
        while (!list_empty(&clp->cl_delegations)) {
                dp = list_entry(clp->cl_delegations.next, struct nfs4_delegation, dl_perclnt);
                list_del_init(&dp->dl_perclnt);
+               /* Ensure that deleg break won't try to requeue it */
+               ++dp->dl_time;
                list_move(&dp->dl_recall_lru, &reaplist);
        }
        spin_unlock(&state_lock);
@@ -1345,8 +1472,12 @@ static void gen_confirm(struct nfs4_client *clp)
        __be32 verf[2];
        static u32 i;
 
-       verf[0] = (__be32)get_seconds();
-       verf[1] = (__be32)i++;
+       /*
+        * This is opaque to client, so no need to byte-swap. Use
+        * __force to keep sparse happy
+        */
+       verf[0] = (__force __be32)get_seconds();
+       verf[1] = (__force __be32)i++;
        memcpy(clp->cl_confirm.data, verf, sizeof(clp->cl_confirm.data));
 }
 
@@ -1566,7 +1697,7 @@ out_err:
 /*
  * Cache a reply. nfsd4_check_resp_size() has bounded the cache size.
  */
-void
+static void
 nfsd4_store_cache_entry(struct nfsd4_compoundres *resp)
 {
        struct xdr_buf *buf = resp->xdr.buf;
@@ -2076,17 +2207,17 @@ __be32 nfsd4_bind_conn_to_session(struct svc_rqst *rqstp,
        __be32 status;
        struct nfsd4_conn *conn;
        struct nfsd4_session *session;
-       struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+       struct net *net = SVC_NET(rqstp);
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
        if (!nfsd4_last_compound_op(rqstp))
                return nfserr_not_only_op;
        nfs4_lock_state();
        spin_lock(&nn->client_lock);
-       session = find_in_sessionid_hashtbl(&bcts->sessionid, SVC_NET(rqstp));
+       session = find_in_sessionid_hashtbl(&bcts->sessionid, net, &status);
        spin_unlock(&nn->client_lock);
-       status = nfserr_badsession;
        if (!session)
-               goto out;
+               goto out_no_session;
        status = nfserr_wrong_cred;
        if (!mach_creds_match(session->se_client, rqstp))
                goto out;
@@ -2100,6 +2231,8 @@ __be32 nfsd4_bind_conn_to_session(struct svc_rqst *rqstp,
        nfsd4_init_conn(rqstp, conn, session);
        status = nfs_ok;
 out:
+       nfsd4_put_session(session);
+out_no_session:
        nfs4_unlock_state();
        return status;
 }
@@ -2119,7 +2252,8 @@ nfsd4_destroy_session(struct svc_rqst *r,
        struct nfsd4_session *ses;
        __be32 status;
        int ref_held_by_me = 0;
-       struct nfsd_net *nn = net_generic(SVC_NET(r), nfsd_net_id);
+       struct net *net = SVC_NET(r);
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
        nfs4_lock_state();
        status = nfserr_not_only_op;
@@ -2130,14 +2264,12 @@ nfsd4_destroy_session(struct svc_rqst *r,
        }
        dump_sessionid(__func__, &sessionid->sessionid);
        spin_lock(&nn->client_lock);
-       ses = find_in_sessionid_hashtbl(&sessionid->sessionid, SVC_NET(r));
-       status = nfserr_badsession;
+       ses = find_in_sessionid_hashtbl(&sessionid->sessionid, net, &status);
        if (!ses)
                goto out_client_lock;
        status = nfserr_wrong_cred;
        if (!mach_creds_match(ses->se_client, r))
-               goto out_client_lock;
-       nfsd4_get_session_locked(ses);
+               goto out_put_session;
        status = mark_session_dead_locked(ses, 1 + ref_held_by_me);
        if (status)
                goto out_put_session;
@@ -2149,7 +2281,7 @@ nfsd4_destroy_session(struct svc_rqst *r,
        spin_lock(&nn->client_lock);
        status = nfs_ok;
 out_put_session:
-       nfsd4_put_session(ses);
+       nfsd4_put_session_locked(ses);
 out_client_lock:
        spin_unlock(&nn->client_lock);
 out:
@@ -2224,7 +2356,8 @@ nfsd4_sequence(struct svc_rqst *rqstp,
        struct nfsd4_conn *conn;
        __be32 status;
        int buflen;
-       struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+       struct net *net = SVC_NET(rqstp);
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
        if (resp->opcnt != 1)
                return nfserr_sequence_pos;
@@ -2238,17 +2371,10 @@ nfsd4_sequence(struct svc_rqst *rqstp,
                return nfserr_jukebox;
 
        spin_lock(&nn->client_lock);
-       status = nfserr_badsession;
-       session = find_in_sessionid_hashtbl(&seq->sessionid, SVC_NET(rqstp));
+       session = find_in_sessionid_hashtbl(&seq->sessionid, net, &status);
        if (!session)
                goto out_no_session;
        clp = session->se_client;
-       status = get_client_locked(clp);
-       if (status)
-               goto out_no_session;
-       status = nfsd4_get_session_locked(session);
-       if (status)
-               goto out_put_client;
 
        status = nfserr_too_many_ops;
        if (nfsd4_session_too_many_ops(rqstp, session))
@@ -2278,6 +2404,7 @@ nfsd4_sequence(struct svc_rqst *rqstp,
                        goto out_put_session;
                cstate->slot = slot;
                cstate->session = session;
+               cstate->clp = clp;
                /* Return the cached reply status and set cstate->status
                 * for nfsd4_proc_compound processing */
                status = nfsd4_replay_cache_entry(resp, seq);
@@ -2312,6 +2439,7 @@ nfsd4_sequence(struct svc_rqst *rqstp,
 
        cstate->slot = slot;
        cstate->session = session;
+       cstate->clp = clp;
 
 out:
        switch (clp->cl_cb_state) {
@@ -2332,12 +2460,26 @@ out_no_session:
        spin_unlock(&nn->client_lock);
        return status;
 out_put_session:
-       nfsd4_put_session(session);
-out_put_client:
-       put_client_renew_locked(clp);
+       nfsd4_put_session_locked(session);
        goto out_no_session;
 }
 
+void
+nfsd4_sequence_done(struct nfsd4_compoundres *resp)
+{
+       struct nfsd4_compound_state *cs = &resp->cstate;
+
+       if (nfsd4_has_session(cs)) {
+               if (cs->status != nfserr_replay_cache) {
+                       nfsd4_store_cache_entry(resp);
+                       cs->slot->sl_flags &= ~NFSD4_SLOT_INUSE;
+               }
+               /* Drop session reference that was taken in nfsd4_sequence() */
+               nfsd4_put_session(cs->session);
+       } else if (cs->clp)
+               put_client_renew(cs->clp);
+}
+
 __be32
 nfsd4_destroy_clientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, struct nfsd4_destroy_clientid *dc)
 {
@@ -2531,17 +2673,18 @@ static void nfsd4_init_file(struct nfs4_file *fp, struct inode *ino)
 {
        unsigned int hashval = file_hashval(ino);
 
+       lockdep_assert_held(&state_lock);
+
        atomic_set(&fp->fi_ref, 1);
        INIT_LIST_HEAD(&fp->fi_stateids);
        INIT_LIST_HEAD(&fp->fi_delegations);
-       fp->fi_inode = igrab(ino);
+       ihold(ino);
+       fp->fi_inode = ino;
        fp->fi_had_conflict = false;
        fp->fi_lease = NULL;
        memset(fp->fi_fds, 0, sizeof(fp->fi_fds));
        memset(fp->fi_access, 0, sizeof(fp->fi_access));
-       spin_lock(&state_lock);
        hlist_add_head(&fp->fi_hash, &file_hashtbl[hashval]);
-       spin_unlock(&state_lock);
 }
 
 void
@@ -2629,7 +2772,10 @@ static void hash_openowner(struct nfs4_openowner *oo, struct nfs4_client *clp, u
 }
 
 static struct nfs4_openowner *
-alloc_init_open_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct nfsd4_open *open) {
+alloc_init_open_stateowner(unsigned int strhashval, struct nfsd4_open *open,
+                          struct nfsd4_compound_state *cstate)
+{
+       struct nfs4_client *clp = cstate->clp;
        struct nfs4_openowner *oo;
 
        oo = alloc_stateowner(openowner_slab, &open->op_owner, clp);
@@ -2638,6 +2784,8 @@ alloc_init_open_stateowner(unsigned int strhashval, struct nfs4_client *clp, str
        oo->oo_owner.so_is_open_owner = 1;
        oo->oo_owner.so_seqid = open->op_seqid;
        oo->oo_flags = NFS4_OO_NEW;
+       if (nfsd4_has_session(cstate))
+               oo->oo_flags |= NFS4_OO_CONFIRMED;
        oo->oo_time = 0;
        oo->oo_last_closed_stid = NULL;
        INIT_LIST_HEAD(&oo->oo_close_lru);
@@ -2649,7 +2797,7 @@ static void init_open_stateid(struct nfs4_ol_stateid *stp, struct nfs4_file *fp,
        struct nfs4_openowner *oo = open->op_openowner;
 
        stp->st_stid.sc_type = NFS4_OPEN_STID;
-       INIT_LIST_HEAD(&stp->st_lockowners);
+       INIT_LIST_HEAD(&stp->st_locks);
        list_add(&stp->st_perstateowner, &oo->oo_owner.so_stateids);
        list_add(&stp->st_perfile, &fp->fi_stateids);
        stp->st_stateowner = &oo->oo_owner;
@@ -2707,23 +2855,49 @@ find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open,
 
 /* search file_hashtbl[] for file */
 static struct nfs4_file *
-find_file(struct inode *ino)
+find_file_locked(struct inode *ino)
 {
        unsigned int hashval = file_hashval(ino);
        struct nfs4_file *fp;
 
-       spin_lock(&state_lock);
+       lockdep_assert_held(&state_lock);
+
        hlist_for_each_entry(fp, &file_hashtbl[hashval], fi_hash) {
                if (fp->fi_inode == ino) {
                        get_nfs4_file(fp);
-                       spin_unlock(&state_lock);
                        return fp;
                }
        }
-       spin_unlock(&state_lock);
        return NULL;
 }
 
+static struct nfs4_file *
+find_file(struct inode *ino)
+{
+       struct nfs4_file *fp;
+
+       spin_lock(&state_lock);
+       fp = find_file_locked(ino);
+       spin_unlock(&state_lock);
+       return fp;
+}
+
+static struct nfs4_file *
+find_or_add_file(struct inode *ino, struct nfs4_file *new)
+{
+       struct nfs4_file *fp;
+
+       spin_lock(&state_lock);
+       fp = find_file_locked(ino);
+       if (fp == NULL) {
+               nfsd4_init_file(new, ino);
+               fp = new;
+       }
+       spin_unlock(&state_lock);
+
+       return fp;
+}
+
 /*
  * Called to check deny when READ with all zero stateid or
  * WRITE with all zero or all one stateid
@@ -2765,10 +2939,16 @@ static void nfsd_break_one_deleg(struct nfs4_delegation *dp)
         * it's safe to take a reference: */
        atomic_inc(&dp->dl_count);
 
-       list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru);
+       /*
+        * If the dl_time != 0, then we know that it has already been
+        * queued for a lease break. Don't queue it again.
+        */
+       if (dp->dl_time == 0) {
+               list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru);
+               dp->dl_time = get_seconds();
+       }
 
-       /* Only place dl_time is set; protected by i_lock: */
-       dp->dl_time = get_seconds();
+       block_delegations(&dp->dl_fh);
 
        nfsd4_cb_recall(dp);
 }
@@ -2826,6 +3006,38 @@ static __be32 nfsd4_check_seqid(struct nfsd4_compound_state *cstate, struct nfs4
        return nfserr_bad_seqid;
 }
 
+static __be32 lookup_clientid(clientid_t *clid,
+               struct nfsd4_compound_state *cstate,
+               struct nfsd_net *nn)
+{
+       struct nfs4_client *found;
+
+       if (cstate->clp) {
+               found = cstate->clp;
+               if (!same_clid(&found->cl_clientid, clid))
+                       return nfserr_stale_clientid;
+               return nfs_ok;
+       }
+
+       if (STALE_CLIENTID(clid, nn))
+               return nfserr_stale_clientid;
+
+       /*
+        * For v4.1+ we get the client in the SEQUENCE op. If we don't have one
+        * cached already then we know this is for is for v4.0 and "sessions"
+        * will be false.
+        */
+       WARN_ON_ONCE(cstate->session);
+       found = find_confirmed_client(clid, false, nn);
+       if (!found)
+               return nfserr_expired;
+
+       /* Cache the nfs4_client in cstate! */
+       cstate->clp = found;
+       atomic_inc(&found->cl_refcount);
+       return nfs_ok;
+}
+
 __be32
 nfsd4_process_open1(struct nfsd4_compound_state *cstate,
                    struct nfsd4_open *open, struct nfsd_net *nn)
@@ -2846,19 +3058,19 @@ nfsd4_process_open1(struct nfsd4_compound_state *cstate,
        if (open->op_file == NULL)
                return nfserr_jukebox;
 
+       status = lookup_clientid(clientid, cstate, nn);
+       if (status)
+               return status;
+       clp = cstate->clp;
+
        strhashval = ownerstr_hashval(clientid->cl_id, &open->op_owner);
        oo = find_openstateowner_str(strhashval, open, cstate->minorversion, nn);
        open->op_openowner = oo;
        if (!oo) {
-               clp = find_confirmed_client(clientid, cstate->minorversion,
-                                           nn);
-               if (clp == NULL)
-                       return nfserr_expired;
                goto new_owner;
        }
        if (!(oo->oo_flags & NFS4_OO_CONFIRMED)) {
                /* Replace unconfirmed owners without checking for replay. */
-               clp = oo->oo_owner.so_client;
                release_openowner(oo);
                open->op_openowner = NULL;
                goto new_owner;
@@ -2866,10 +3078,9 @@ nfsd4_process_open1(struct nfsd4_compound_state *cstate,
        status = nfsd4_check_seqid(cstate, &oo->oo_owner, open->op_seqid);
        if (status)
                return status;
-       clp = oo->oo_owner.so_client;
        goto alloc_stateid;
 new_owner:
-       oo = alloc_init_open_stateowner(strhashval, clp, open);
+       oo = alloc_init_open_stateowner(strhashval, open, cstate);
        if (oo == NULL)
                return nfserr_jukebox;
        open->op_openowner = oo;
@@ -2964,6 +3175,21 @@ static inline int nfs4_access_to_access(u32 nfs4_access)
        return flags;
 }
 
+static inline __be32
+nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,
+               struct nfsd4_open *open)
+{
+       struct iattr iattr = {
+               .ia_valid = ATTR_SIZE,
+               .ia_size = 0,
+       };
+       if (!open->op_truncate)
+               return 0;
+       if (!(open->op_share_access & NFS4_SHARE_ACCESS_WRITE))
+               return nfserr_inval;
+       return nfsd_setattr(rqstp, fh, &iattr, 0, (time_t)0);
+}
+
 static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
                struct svc_fh *cur_fh, struct nfsd4_open *open)
 {
@@ -2975,53 +3201,39 @@ static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
                status = nfsd_open(rqstp, cur_fh, S_IFREG, access,
                        &fp->fi_fds[oflag]);
                if (status)
-                       return status;
+                       goto out;
        }
        nfs4_file_get_access(fp, oflag);
 
+       status = nfsd4_truncate(rqstp, cur_fh, open);
+       if (status)
+               goto out_put_access;
+
        return nfs_ok;
-}
 
-static inline __be32
-nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,
-               struct nfsd4_open *open)
-{
-       struct iattr iattr = {
-               .ia_valid = ATTR_SIZE,
-               .ia_size = 0,
-       };
-       if (!open->op_truncate)
-               return 0;
-       if (!(open->op_share_access & NFS4_SHARE_ACCESS_WRITE))
-               return nfserr_inval;
-       return nfsd_setattr(rqstp, fh, &iattr, 0, (time_t)0);
+out_put_access:
+       nfs4_file_put_access(fp, oflag);
+out:
+       return status;
 }
 
 static __be32
 nfs4_upgrade_open(struct svc_rqst *rqstp, struct nfs4_file *fp, struct svc_fh *cur_fh, struct nfs4_ol_stateid *stp, struct nfsd4_open *open)
 {
        u32 op_share_access = open->op_share_access;
-       bool new_access;
        __be32 status;
 
-       new_access = !test_access(op_share_access, stp);
-       if (new_access) {
+       if (!test_access(op_share_access, stp))
                status = nfs4_get_vfs_file(rqstp, fp, cur_fh, open);
-               if (status)
-                       return status;
-       }
-       status = nfsd4_truncate(rqstp, cur_fh, open);
-       if (status) {
-               if (new_access) {
-                       int oflag = nfs4_access_to_omode(op_share_access);
-                       nfs4_file_put_access(fp, oflag);
-               }
+       else
+               status = nfsd4_truncate(rqstp, cur_fh, open);
+
+       if (status)
                return status;
-       }
+
        /* remember the open */
        set_access(op_share_access, stp);
        set_deny(open->op_share_deny, stp);
-
        return nfs_ok;
 }
 
@@ -3242,21 +3454,19 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf
         * and check for delegations in the process of being recalled.
         * If not found, create the nfs4_file struct
         */
-       fp = find_file(ino);
-       if (fp) {
+       fp = find_or_add_file(ino, open->op_file);
+       if (fp != open->op_file) {
                if ((status = nfs4_check_open(fp, open, &stp)))
                        goto out;
                status = nfs4_check_deleg(cl, open, &dp);
                if (status)
                        goto out;
        } else {
+               open->op_file = NULL;
                status = nfserr_bad_stateid;
                if (nfsd4_is_deleg_cur(open))
                        goto out;
                status = nfserr_jukebox;
-               fp = open->op_file;
-               open->op_file = NULL;
-               nfsd4_init_file(fp, ino);
        }
 
        /*
@@ -3270,9 +3480,6 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf
                        goto out;
        } else {
                status = nfs4_get_vfs_file(rqstp, fp, current_fh, open);
-               if (status)
-                       goto out;
-               status = nfsd4_truncate(rqstp, current_fh, open);
                if (status)
                        goto out;
                stp = open->op_stp;
@@ -3283,8 +3490,6 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf
        memcpy(&open->op_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
 
        if (nfsd4_has_session(&resp->cstate)) {
-               open->op_openowner->oo_flags |= NFS4_OO_CONFIRMED;
-
                if (open->op_deleg_want & NFS4_SHARE_WANT_NO_DELEG) {
                        open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE_EXT;
                        open->op_why_no_deleg = WND4_NOT_WANTED;
@@ -3344,18 +3549,6 @@ void nfsd4_cleanup_open_state(struct nfsd4_open *open, __be32 status)
                free_generic_stateid(open->op_stp);
 }
 
-static __be32 lookup_clientid(clientid_t *clid, bool session, struct nfsd_net *nn, struct nfs4_client **clp)
-{
-       struct nfs4_client *found;
-
-       if (STALE_CLIENTID(clid, nn))
-               return nfserr_stale_clientid;
-       found = find_confirmed_client(clid, session, nn);
-       if (clp)
-               *clp = found;
-       return found ? nfs_ok : nfserr_expired;
-}
-
 __be32
 nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
            clientid_t *clid)
@@ -3367,9 +3560,10 @@ nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        nfs4_lock_state();
        dprintk("process_renew(%08x/%08x): starting\n", 
                        clid->cl_boot, clid->cl_id);
-       status = lookup_clientid(clid, cstate->minorversion, nn, &clp);
+       status = lookup_clientid(clid, cstate, nn);
        if (status)
                goto out;
+       clp = cstate->clp;
        status = nfserr_cb_path_down;
        if (!list_empty(&clp->cl_delegations)
                        && clp->cl_cb_state != NFSD4_CB_UP)
@@ -3627,25 +3821,24 @@ static __be32 nfsd4_validate_stateid(struct nfs4_client *cl, stateid_t *stateid)
        }
 }
 
-static __be32 nfsd4_lookup_stateid(stateid_t *stateid, unsigned char typemask,
-                                  struct nfs4_stid **s, bool sessions,
-                                  struct nfsd_net *nn)
+static __be32
+nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate,
+                    stateid_t *stateid, unsigned char typemask,
+                    struct nfs4_stid **s, struct nfsd_net *nn)
 {
-       struct nfs4_client *cl;
        __be32 status;
 
        if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
                return nfserr_bad_stateid;
-       status = lookup_clientid(&stateid->si_opaque.so_clid, sessions,
-                                                       nn, &cl);
+       status = lookup_clientid(&stateid->si_opaque.so_clid, cstate, nn);
        if (status == nfserr_stale_clientid) {
-               if (sessions)
+               if (cstate->session)
                        return nfserr_bad_stateid;
                return nfserr_stale_stateid;
        }
        if (status)
                return status;
-       *s = find_stateid_by_type(cl, stateid, typemask);
+       *s = find_stateid_by_type(cstate->clp, stateid, typemask);
        if (!*s)
                return nfserr_bad_stateid;
        return nfs_ok;
@@ -3678,8 +3871,9 @@ nfs4_preprocess_stateid_op(struct net *net, struct nfsd4_compound_state *cstate,
 
        nfs4_lock_state();
 
-       status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID,
-                                     &s, cstate->minorversion, nn);
+       status = nfsd4_lookup_stateid(cstate, stateid,
+                               NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID,
+                               &s, nn);
        if (status)
                goto out;
        status = check_stateid_generation(stateid, &s->sc_stateid, nfsd4_has_session(cstate));
@@ -3738,12 +3932,7 @@ nfsd4_free_lock_stateid(struct nfs4_ol_stateid *stp)
 
        if (check_for_locks(stp->st_file, lo))
                return nfserr_locks_held;
-       /*
-        * Currently there's a 1-1 lock stateid<->lockowner
-        * correspondance, and we have to delete the lockowner when we
-        * delete the lock stateid:
-        */
-       release_lockowner(lo);
+       release_lockowner_if_empty(lo);
        return nfs_ok;
 }
 
@@ -3848,20 +4037,23 @@ nfs4_preprocess_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid,
 {
        __be32 status;
        struct nfs4_stid *s;
+       struct nfs4_ol_stateid *stp = NULL;
 
        dprintk("NFSD: %s: seqid=%d stateid = " STATEID_FMT "\n", __func__,
                seqid, STATEID_VAL(stateid));
 
        *stpp = NULL;
-       status = nfsd4_lookup_stateid(stateid, typemask, &s,
-                                     cstate->minorversion, nn);
+       status = nfsd4_lookup_stateid(cstate, stateid, typemask, &s, nn);
        if (status)
                return status;
-       *stpp = openlockstateid(s);
+       stp = openlockstateid(s);
        if (!nfsd4_has_session(cstate))
-               cstate->replay_owner = (*stpp)->st_stateowner;
+               cstate->replay_owner = stp->st_stateowner;
 
-       return nfs4_seqid_op_checks(cstate, stateid, seqid, *stpp);
+       status = nfs4_seqid_op_checks(cstate, stateid, seqid, stp);
+       if (!status)
+               *stpp = stp;
+       return status;
 }
 
 static __be32 nfs4_preprocess_confirmed_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid,
@@ -4007,8 +4199,25 @@ out:
 
 static void nfsd4_close_open_stateid(struct nfs4_ol_stateid *s)
 {
-       unhash_open_stateid(s);
+       struct nfs4_client *clp = s->st_stid.sc_client;
+       struct nfs4_openowner *oo = openowner(s->st_stateowner);
+
        s->st_stid.sc_type = NFS4_CLOSED_STID;
+       unhash_open_stateid(s);
+
+       if (clp->cl_minorversion) {
+               free_generic_stateid(s);
+               if (list_empty(&oo->oo_owner.so_stateids))
+                       release_openowner(oo);
+       } else {
+               oo->oo_last_closed_stid = s;
+               /*
+                * In the 4.0 case we need to keep the owners around a
+                * little while to handle CLOSE replay.
+                */
+               if (list_empty(&oo->oo_owner.so_stateids))
+                       move_to_close_lru(oo, clp->net);
+       }
 }
 
 /*
@@ -4019,7 +4228,6 @@ nfsd4_close(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
            struct nfsd4_close *close)
 {
        __be32 status;
-       struct nfs4_openowner *oo;
        struct nfs4_ol_stateid *stp;
        struct net *net = SVC_NET(rqstp);
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
@@ -4035,28 +4243,10 @@ nfsd4_close(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        nfsd4_bump_seqid(cstate, status);
        if (status)
                goto out; 
-       oo = openowner(stp->st_stateowner);
        update_stateid(&stp->st_stid.sc_stateid);
        memcpy(&close->cl_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
 
        nfsd4_close_open_stateid(stp);
-
-       if (cstate->minorversion)
-               free_generic_stateid(stp);
-       else
-               oo->oo_last_closed_stid = stp;
-
-       if (list_empty(&oo->oo_owner.so_stateids)) {
-               if (cstate->minorversion)
-                       release_openowner(oo);
-               else {
-                       /*
-                        * In the 4.0 case we need to keep the owners around a
-                        * little while to handle CLOSE replay.
-                        */
-                       move_to_close_lru(oo, SVC_NET(rqstp));
-               }
-       }
 out:
        if (!cstate->replay_owner)
                nfs4_unlock_state();
@@ -4077,8 +4267,7 @@ nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                return status;
 
        nfs4_lock_state();
-       status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID, &s,
-                                     cstate->minorversion, nn);
+       status = nfsd4_lookup_stateid(cstate, stateid, NFS4_DELEG_STID, &s, nn);
        if (status)
                goto out;
        dp = delegstateid(s);
@@ -4096,8 +4285,6 @@ out:
 
 #define LOFF_OVERFLOW(start, len)      ((u64)(len) > ~(u64)(start))
 
-#define LOCKOWNER_INO_HASH_MASK (LOCKOWNER_INO_HASH_SIZE - 1)
-
 static inline u64
 end_offset(u64 start, u64 len)
 {
@@ -4118,13 +4305,6 @@ last_byte_offset(u64 start, u64 len)
        return end > start ? end - 1: NFS4_MAX_UINT64;
 }
 
-static unsigned int lockowner_ino_hashval(struct inode *inode, u32 cl_id, struct xdr_netobj *ownername)
-{
-       return (file_hashval(inode) + cl_id
-                       + opaque_hashval(ownername->data, ownername->len))
-               & LOCKOWNER_INO_HASH_MASK;
-}
-
 /*
  * TODO: Linux file offsets are _signed_ 64-bit quantities, which means that
  * we can't properly handle lock requests that go beyond the (2^63 - 1)-th
@@ -4177,47 +4357,23 @@ nevermind:
                deny->ld_type = NFS4_WRITE_LT;
 }
 
-static bool same_lockowner_ino(struct nfs4_lockowner *lo, struct inode *inode, clientid_t *clid, struct xdr_netobj *owner)
-{
-       struct nfs4_ol_stateid *lst;
-
-       if (!same_owner_str(&lo->lo_owner, owner, clid))
-               return false;
-       if (list_empty(&lo->lo_owner.so_stateids)) {
-               WARN_ON_ONCE(1);
-               return false;
-       }
-       lst = list_first_entry(&lo->lo_owner.so_stateids,
-                              struct nfs4_ol_stateid, st_perstateowner);
-       return lst->st_file->fi_inode == inode;
-}
-
 static struct nfs4_lockowner *
-find_lockowner_str(struct inode *inode, clientid_t *clid,
-                  struct xdr_netobj *owner, struct nfsd_net *nn)
+find_lockowner_str(clientid_t *clid, struct xdr_netobj *owner,
+               struct nfsd_net *nn)
 {
-       unsigned int hashval = lockowner_ino_hashval(inode, clid->cl_id, owner);
-       struct nfs4_lockowner *lo;
+       unsigned int strhashval = ownerstr_hashval(clid->cl_id, owner);
+       struct nfs4_stateowner *so;
 
-       list_for_each_entry(lo, &nn->lockowner_ino_hashtbl[hashval], lo_owner_ino_hash) {
-               if (same_lockowner_ino(lo, inode, clid, owner))
-                       return lo;
+       list_for_each_entry(so, &nn->ownerstr_hashtbl[strhashval], so_strhash) {
+               if (so->so_is_open_owner)
+                       continue;
+               if (!same_owner_str(so, owner, clid))
+                       continue;
+               return lockowner(so);
        }
        return NULL;
 }
 
-static void hash_lockowner(struct nfs4_lockowner *lo, unsigned int strhashval, struct nfs4_client *clp, struct nfs4_ol_stateid *open_stp)
-{
-       struct inode *inode = open_stp->st_file->fi_inode;
-       unsigned int inohash = lockowner_ino_hashval(inode,
-                       clp->cl_clientid.cl_id, &lo->lo_owner.so_owner);
-       struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
-
-       list_add(&lo->lo_owner.so_strhash, &nn->ownerstr_hashtbl[strhashval]);
-       list_add(&lo->lo_owner_ino_hash, &nn->lockowner_ino_hashtbl[inohash]);
-       list_add(&lo->lo_perstateid, &open_stp->st_lockowners);
-}
-
 /*
  * Alloc a lock owner structure.
  * Called in nfsd4_lock - therefore, OPEN and OPEN_CONFIRM (if needed) has 
@@ -4225,10 +4381,10 @@ static void hash_lockowner(struct nfs4_lockowner *lo, unsigned int strhashval, s
  *
  * strhashval = ownerstr_hashval
  */
-
 static struct nfs4_lockowner *
 alloc_init_lock_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct nfs4_ol_stateid *open_stp, struct nfsd4_lock *lock) {
        struct nfs4_lockowner *lo;
+       struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
 
        lo = alloc_stateowner(lockowner_slab, &lock->lk_new_owner, clp);
        if (!lo)
@@ -4238,7 +4394,7 @@ alloc_init_lock_stateowner(unsigned int strhashval, struct nfs4_client *clp, str
        /* It is the openowner seqid that will be incremented in encode in the
         * case of new lockowners; so increment the lock seqid manually: */
        lo->lo_owner.so_seqid = lock->lk_new_lock_seqid + 1;
-       hash_lockowner(lo, strhashval, clp, open_stp);
+       list_add(&lo->lo_owner.so_strhash, &nn->ownerstr_hashtbl[strhashval]);
        return lo;
 }
 
@@ -4260,9 +4416,23 @@ alloc_init_lock_stateid(struct nfs4_lockowner *lo, struct nfs4_file *fp, struct
        stp->st_access_bmap = 0;
        stp->st_deny_bmap = open_stp->st_deny_bmap;
        stp->st_openstp = open_stp;
+       list_add(&stp->st_locks, &open_stp->st_locks);
        return stp;
 }
 
+static struct nfs4_ol_stateid *
+find_lock_stateid(struct nfs4_lockowner *lo, struct nfs4_file *fp)
+{
+       struct nfs4_ol_stateid *lst;
+
+       list_for_each_entry(lst, &lo->lo_owner.so_stateids, st_perstateowner) {
+               if (lst->st_file == fp)
+                       return lst;
+       }
+       return NULL;
+}
+
+
 static int
 check_lock_length(u64 offset, u64 length)
 {
@@ -4290,27 +4460,29 @@ static __be32 lookup_or_create_lock_state(struct nfsd4_compound_state *cstate, s
        unsigned int strhashval;
        struct nfsd_net *nn = net_generic(cl->net, nfsd_net_id);
 
-       lo = find_lockowner_str(fi->fi_inode, &cl->cl_clientid,
-                               &lock->v.new.owner, nn);
-       if (lo) {
-               if (!cstate->minorversion)
+       lo = find_lockowner_str(&cl->cl_clientid, &lock->v.new.owner, nn);
+       if (!lo) {
+               strhashval = ownerstr_hashval(cl->cl_clientid.cl_id,
+                               &lock->v.new.owner);
+               lo = alloc_init_lock_stateowner(strhashval, cl, ost, lock);
+               if (lo == NULL)
+                       return nfserr_jukebox;
+       } else {
+               /* with an existing lockowner, seqids must be the same */
+               if (!cstate->minorversion &&
+                   lock->lk_new_lock_seqid != lo->lo_owner.so_seqid)
                        return nfserr_bad_seqid;
-               /* XXX: a lockowner always has exactly one stateid: */
-               *lst = list_first_entry(&lo->lo_owner.so_stateids,
-                               struct nfs4_ol_stateid, st_perstateowner);
-               return nfs_ok;
        }
-       strhashval = ownerstr_hashval(cl->cl_clientid.cl_id,
-                       &lock->v.new.owner);
-       lo = alloc_init_lock_stateowner(strhashval, cl, ost, lock);
-       if (lo == NULL)
-               return nfserr_jukebox;
-       *lst = alloc_init_lock_stateid(lo, fi, ost);
+
+       *lst = find_lock_stateid(lo, fi);
        if (*lst == NULL) {
-               release_lockowner(lo);
-               return nfserr_jukebox;
+               *lst = alloc_init_lock_stateid(lo, fi, ost);
+               if (*lst == NULL) {
+                       release_lockowner_if_empty(lo);
+                       return nfserr_jukebox;
+               }
+               *new = true;
        }
-       *new = true;
        return nfs_ok;
 }
 
@@ -4467,7 +4639,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        }
 out:
        if (status && new_state)
-               release_lockowner(lock_sop);
+               release_lock_stateid(lock_stp);
        nfsd4_bump_seqid(cstate, status);
        if (!cstate->replay_owner)
                nfs4_unlock_state();
@@ -4502,7 +4674,6 @@ __be32
 nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
            struct nfsd4_lockt *lockt)
 {
-       struct inode *inode;
        struct file_lock *file_lock = NULL;
        struct nfs4_lockowner *lo;
        __be32 status;
@@ -4517,7 +4688,7 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        nfs4_lock_state();
 
        if (!nfsd4_has_session(cstate)) {
-               status = lookup_clientid(&lockt->lt_clientid, false, nn, NULL);
+               status = lookup_clientid(&lockt->lt_clientid, cstate, nn);
                if (status)
                        goto out;
        }
@@ -4525,7 +4696,6 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        if ((status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0)))
                goto out;
 
-       inode = cstate->current_fh.fh_dentry->d_inode;
        file_lock = locks_alloc_lock();
        if (!file_lock) {
                dprintk("NFSD: %s: unable to allocate lock!\n", __func__);
@@ -4548,7 +4718,7 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                goto out;
        }
 
-       lo = find_lockowner_str(inode, &lockt->lt_clientid, &lockt->lt_owner, nn);
+       lo = find_lockowner_str(&lockt->lt_clientid, &lockt->lt_owner, nn);
        if (lo)
                file_lock->fl_owner = (fl_owner_t)lo;
        file_lock->fl_pid = current->tgid;
@@ -4674,11 +4844,10 @@ nfsd4_release_lockowner(struct svc_rqst *rqstp,
                        struct nfsd4_release_lockowner *rlockowner)
 {
        clientid_t *clid = &rlockowner->rl_clientid;
-       struct nfs4_stateowner *sop;
+       struct nfs4_stateowner *sop = NULL, *tmp;
        struct nfs4_lockowner *lo;
        struct nfs4_ol_stateid *stp;
        struct xdr_netobj *owner = &rlockowner->rl_owner;
-       struct list_head matches;
        unsigned int hashval = ownerstr_hashval(clid->cl_id, owner);
        __be32 status;
        struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
@@ -4688,38 +4857,37 @@ nfsd4_release_lockowner(struct svc_rqst *rqstp,
 
        nfs4_lock_state();
 
-       status = lookup_clientid(clid, cstate->minorversion, nn, NULL);
+       status = lookup_clientid(clid, cstate, nn);
        if (status)
                goto out;
 
        status = nfserr_locks_held;
-       INIT_LIST_HEAD(&matches);
 
-       list_for_each_entry(sop, &nn->ownerstr_hashtbl[hashval], so_strhash) {
-               if (sop->so_is_open_owner)
+       /* Find the matching lock stateowner */
+       list_for_each_entry(tmp, &nn->ownerstr_hashtbl[hashval], so_strhash) {
+               if (tmp->so_is_open_owner)
                        continue;
-               if (!same_owner_str(sop, owner, clid))
-                       continue;
-               list_for_each_entry(stp, &sop->so_stateids,
-                               st_perstateowner) {
-                       lo = lockowner(sop);
-                       if (check_for_locks(stp->st_file, lo))
-                               goto out;
-                       list_add(&lo->lo_list, &matches);
+               if (same_owner_str(tmp, owner, clid)) {
+                       sop = tmp;
+                       break;
                }
        }
-       /* Clients probably won't expect us to return with some (but not all)
-        * of the lockowner state released; so don't release any until all
-        * have been checked. */
-       status = nfs_ok;
-       while (!list_empty(&matches)) {
-               lo = list_entry(matches.next, struct nfs4_lockowner,
-                                                               lo_list);
-               /* unhash_stateowner deletes so_perclient only
-                * for openowners. */
-               list_del(&lo->lo_list);
-               release_lockowner(lo);
+
+       /* No matching owner found, maybe a replay? Just declare victory... */
+       if (!sop) {
+               status = nfs_ok;
+               goto out;
+       }
+
+       lo = lockowner(sop);
+       /* see if there are still any locks associated with it */
+       list_for_each_entry(stp, &sop->so_stateids, st_perstateowner) {
+               if (check_for_locks(stp->st_file, lo))
+                       goto out;
        }
+
+       status = nfs_ok;
+       release_lockowner(lo);
 out:
        nfs4_unlock_state();
        return status;
@@ -4809,16 +4977,21 @@ nfsd4_find_reclaim_client(const char *recdir, struct nfsd_net *nn)
 * Called from OPEN. Look for clientid in reclaim list.
 */
 __be32
-nfs4_check_open_reclaim(clientid_t *clid, bool sessions, struct nfsd_net *nn)
+nfs4_check_open_reclaim(clientid_t *clid,
+               struct nfsd4_compound_state *cstate,
+               struct nfsd_net *nn)
 {
-       struct nfs4_client *clp;
+       __be32 status;
 
        /* find clientid in conf_id_hashtbl */
-       clp = find_confirmed_client(clid, sessions, nn);
-       if (clp == NULL)
+       status = lookup_clientid(clid, cstate, nn);
+       if (status)
                return nfserr_reclaim_bad;
 
-       return nfsd4_client_record_check(clp) ? nfserr_reclaim_bad : nfs_ok;
+       if (nfsd4_client_record_check(cstate->clp))
+               return nfserr_reclaim_bad;
+
+       return nfs_ok;
 }
 
 #ifdef CONFIG_NFSD_FAULT_INJECTION
@@ -4847,18 +5020,21 @@ static void nfsd_print_count(struct nfs4_client *clp, unsigned int count,
        printk(KERN_INFO "NFS Client: %s has %u %s\n", buf, count, type);
 }
 
-static u64 nfsd_foreach_client_lock(struct nfs4_client *clp, u64 max, void (*func)(struct nfs4_lockowner *))
+static u64 nfsd_foreach_client_lock(struct nfs4_client *clp, u64 max,
+                                   void (*func)(struct nfs4_ol_stateid *))
 {
        struct nfs4_openowner *oop;
-       struct nfs4_lockowner *lop, *lo_next;
        struct nfs4_ol_stateid *stp, *st_next;
+       struct nfs4_ol_stateid *lst, *lst_next;
        u64 count = 0;
 
        list_for_each_entry(oop, &clp->cl_openowners, oo_perclient) {
-               list_for_each_entry_safe(stp, st_next, &oop->oo_owner.so_stateids, st_perstateowner) {
-                       list_for_each_entry_safe(lop, lo_next, &stp->st_lockowners, lo_perstateid) {
+               list_for_each_entry_safe(stp, st_next,
+                               &oop->oo_owner.so_stateids, st_perstateowner) {
+                       list_for_each_entry_safe(lst, lst_next,
+                                       &stp->st_locks, st_locks) {
                                if (func)
-                                       func(lop);
+                                       func(lst);
                                if (++count == max)
                                        return count;
                        }
@@ -4870,7 +5046,7 @@ static u64 nfsd_foreach_client_lock(struct nfs4_client *clp, u64 max, void (*fun
 
 u64 nfsd_forget_client_locks(struct nfs4_client *clp, u64 max)
 {
-       return nfsd_foreach_client_lock(clp, max, release_lockowner);
+       return nfsd_foreach_client_lock(clp, max, release_lock_stateid);
 }
 
 u64 nfsd_print_client_locks(struct nfs4_client *clp, u64 max)
@@ -4915,8 +5091,23 @@ static u64 nfsd_find_all_delegations(struct nfs4_client *clp, u64 max,
 
        lockdep_assert_held(&state_lock);
        list_for_each_entry_safe(dp, next, &clp->cl_delegations, dl_perclnt) {
-               if (victims)
+               if (victims) {
+                       /*
+                        * It's not safe to mess with delegations that have a
+                        * non-zero dl_time. They might have already been broken
+                        * and could be processed by the laundromat outside of
+                        * the state_lock. Just leave them be.
+                        */
+                       if (dp->dl_time != 0)
+                               continue;
+
+                       /*
+                        * Increment dl_time to ensure that delegation breaks
+                        * don't monkey with it now that we are.
+                        */
+                       ++dp->dl_time;
                        list_move(&dp->dl_recall_lru, victims);
+               }
                if (++count == max)
                        break;
        }
@@ -4941,14 +5132,19 @@ u64 nfsd_forget_client_delegations(struct nfs4_client *clp, u64 max)
 
 u64 nfsd_recall_client_delegations(struct nfs4_client *clp, u64 max)
 {
-       struct nfs4_delegation *dp, *next;
+       struct nfs4_delegation *dp;
        LIST_HEAD(victims);
        u64 count;
 
        spin_lock(&state_lock);
        count = nfsd_find_all_delegations(clp, max, &victims);
-       list_for_each_entry_safe(dp, next, &victims, dl_recall_lru)
+       while (!list_empty(&victims)) {
+               dp = list_first_entry(&victims, struct nfs4_delegation,
+                                       dl_recall_lru);
+               list_del_init(&dp->dl_recall_lru);
+               dp->dl_time = 0;
                nfsd_break_one_deleg(dp);
+       }
        spin_unlock(&state_lock);
 
        return count;
@@ -5039,10 +5235,6 @@ static int nfs4_state_create_net(struct net *net)
                        OWNER_HASH_SIZE, GFP_KERNEL);
        if (!nn->ownerstr_hashtbl)
                goto err_ownerstr;
-       nn->lockowner_ino_hashtbl = kmalloc(sizeof(struct list_head) *
-                       LOCKOWNER_INO_HASH_SIZE, GFP_KERNEL);
-       if (!nn->lockowner_ino_hashtbl)
-               goto err_lockowner_ino;
        nn->sessionid_hashtbl = kmalloc(sizeof(struct list_head) *
                        SESSION_HASH_SIZE, GFP_KERNEL);
        if (!nn->sessionid_hashtbl)
@@ -5054,8 +5246,6 @@ static int nfs4_state_create_net(struct net *net)
        }
        for (i = 0; i < OWNER_HASH_SIZE; i++)
                INIT_LIST_HEAD(&nn->ownerstr_hashtbl[i]);
-       for (i = 0; i < LOCKOWNER_INO_HASH_SIZE; i++)
-               INIT_LIST_HEAD(&nn->lockowner_ino_hashtbl[i]);
        for (i = 0; i < SESSION_HASH_SIZE; i++)
                INIT_LIST_HEAD(&nn->sessionid_hashtbl[i]);
        nn->conf_name_tree = RB_ROOT;
@@ -5071,8 +5261,6 @@ static int nfs4_state_create_net(struct net *net)
        return 0;
 
 err_sessionid:
-       kfree(nn->lockowner_ino_hashtbl);
-err_lockowner_ino:
        kfree(nn->ownerstr_hashtbl);
 err_ownerstr:
        kfree(nn->unconf_id_hashtbl);
@@ -5104,7 +5292,6 @@ nfs4_state_destroy_net(struct net *net)
        }
 
        kfree(nn->sessionid_hashtbl);
-       kfree(nn->lockowner_ino_hashtbl);
        kfree(nn->ownerstr_hashtbl);
        kfree(nn->unconf_id_hashtbl);
        kfree(nn->conf_id_hashtbl);