nfsd: Move create_client() call outside the lock
[pandora-kernel.git] / fs / nfsd / nfs4state.c
index 6d26d26..4b42cb9 100644 (file)
@@ -239,6 +239,44 @@ static void nfsd4_put_session(struct nfsd4_session *ses)
        spin_unlock(&nn->client_lock);
 }
 
+static int
+same_owner_str(struct nfs4_stateowner *sop, struct xdr_netobj *owner)
+{
+       return (sop->so_owner.len == owner->len) &&
+               0 == memcmp(sop->so_owner.data, owner->data, owner->len);
+}
+
+static struct nfs4_openowner *
+find_openstateowner_str_locked(unsigned int hashval, struct nfsd4_open *open,
+                       struct nfs4_client *clp)
+{
+       struct nfs4_stateowner *so;
+
+       lockdep_assert_held(&clp->cl_lock);
+
+       list_for_each_entry(so, &clp->cl_ownerstr_hashtbl[hashval],
+                           so_strhash) {
+               if (!so->so_is_open_owner)
+                       continue;
+               if (same_owner_str(so, &open->op_owner)) {
+                       atomic_inc(&so->so_count);
+                       return openowner(so);
+               }
+       }
+       return NULL;
+}
+
+static struct nfs4_openowner *
+find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open,
+                       struct nfs4_client *clp)
+{
+       struct nfs4_openowner *oo;
+
+       spin_lock(&clp->cl_lock);
+       oo = find_openstateowner_str_locked(hashval, open, clp);
+       spin_unlock(&clp->cl_lock);
+       return oo;
+}
 
 static inline u32
 opaque_hashval(const void *ptr, int nbytes)
@@ -361,12 +399,11 @@ unsigned long max_delegations;
 #define OWNER_HASH_SIZE             (1 << OWNER_HASH_BITS)
 #define OWNER_HASH_MASK             (OWNER_HASH_SIZE - 1)
 
-static unsigned int ownerstr_hashval(u32 clientid, struct xdr_netobj *ownername)
+static unsigned int ownerstr_hashval(struct xdr_netobj *ownername)
 {
        unsigned int ret;
 
        ret = opaque_hashval(ownername->data, ownername->len);
-       ret += clientid;
        return ret & OWNER_HASH_MASK;
 }
 
@@ -900,14 +937,19 @@ release_all_access(struct nfs4_ol_stateid *stp)
 
 static void nfs4_put_stateowner(struct nfs4_stateowner *sop)
 {
-       if (!atomic_dec_and_test(&sop->so_count))
+       struct nfs4_client *clp = sop->so_client;
+
+       might_lock(&clp->cl_lock);
+
+       if (!atomic_dec_and_lock(&sop->so_count, &clp->cl_lock))
                return;
        sop->so_ops->so_unhash(sop);
+       spin_unlock(&clp->cl_lock);
        kfree(sop->so_owner.data);
        sop->so_ops->so_free(sop);
 }
 
-static void unhash_generic_stateid(struct nfs4_ol_stateid *stp)
+static void unhash_ol_stateid(struct nfs4_ol_stateid *stp)
 {
        struct nfs4_file *fp = stp->st_stid.sc_file;
 
@@ -941,72 +983,139 @@ static void nfs4_free_lock_stateid(struct nfs4_stid *stid)
        nfs4_free_ol_stateid(stid);
 }
 
+/*
+ * Put the persistent reference to an already unhashed generic stateid, while
+ * holding the cl_lock. If it's the last reference, then put it onto the
+ * reaplist for later destruction.
+ */
+static void put_ol_stateid_locked(struct nfs4_ol_stateid *stp,
+                                      struct list_head *reaplist)
+{
+       struct nfs4_stid *s = &stp->st_stid;
+       struct nfs4_client *clp = s->sc_client;
+
+       lockdep_assert_held(&clp->cl_lock);
+
+       WARN_ON_ONCE(!list_empty(&stp->st_locks));
+
+       if (!atomic_dec_and_test(&s->sc_count)) {
+               wake_up_all(&close_wq);
+               return;
+       }
+
+       idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id);
+       list_add(&stp->st_locks, reaplist);
+}
+
+static void unhash_lock_stateid(struct nfs4_ol_stateid *stp)
+{
+       struct nfs4_openowner *oo = openowner(stp->st_openstp->st_stateowner);
+
+       lockdep_assert_held(&oo->oo_owner.so_client->cl_lock);
+
+       list_del_init(&stp->st_locks);
+       unhash_ol_stateid(stp);
+       unhash_stid(&stp->st_stid);
+}
+
 static void release_lock_stateid(struct nfs4_ol_stateid *stp)
 {
        struct nfs4_openowner *oo = openowner(stp->st_openstp->st_stateowner);
 
        spin_lock(&oo->oo_owner.so_client->cl_lock);
-       list_del(&stp->st_locks);
-       unhash_generic_stateid(stp);
-       unhash_stid(&stp->st_stid);
+       unhash_lock_stateid(stp);
        spin_unlock(&oo->oo_owner.so_client->cl_lock);
        nfs4_put_stid(&stp->st_stid);
 }
 
-static void unhash_lockowner(struct nfs4_lockowner *lo)
+static void unhash_lockowner_locked(struct nfs4_lockowner *lo)
 {
+       struct nfs4_client *clp = lo->lo_owner.so_client;
+
+       lockdep_assert_held(&clp->cl_lock);
+
        list_del_init(&lo->lo_owner.so_strhash);
 }
 
-static void release_lockowner_stateids(struct nfs4_lockowner *lo)
+/*
+ * Free a list of generic stateids that were collected earlier after being
+ * fully unhashed.
+ */
+static void
+free_ol_stateid_reaplist(struct list_head *reaplist)
 {
        struct nfs4_ol_stateid *stp;
 
-       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);
+       might_sleep();
+
+       while (!list_empty(reaplist)) {
+               stp = list_first_entry(reaplist, struct nfs4_ol_stateid,
+                                      st_locks);
+               list_del(&stp->st_locks);
+               stp->st_stid.sc_free(&stp->st_stid);
        }
 }
 
 static void release_lockowner(struct nfs4_lockowner *lo)
 {
-       unhash_lockowner(lo);
-       release_lockowner_stateids(lo);
+       struct nfs4_client *clp = lo->lo_owner.so_client;
+       struct nfs4_ol_stateid *stp;
+       struct list_head reaplist;
+
+       INIT_LIST_HEAD(&reaplist);
+
+       spin_lock(&clp->cl_lock);
+       unhash_lockowner_locked(lo);
+       while (!list_empty(&lo->lo_owner.so_stateids)) {
+               stp = list_first_entry(&lo->lo_owner.so_stateids,
+                               struct nfs4_ol_stateid, st_perstateowner);
+               unhash_lock_stateid(stp);
+               put_ol_stateid_locked(stp, &reaplist);
+       }
+       spin_unlock(&clp->cl_lock);
+       free_ol_stateid_reaplist(&reaplist);
        nfs4_put_stateowner(&lo->lo_owner);
 }
 
-static void release_open_stateid_locks(struct nfs4_ol_stateid *open_stp)
-       __releases(&open_stp->st_stateowner->so_client->cl_lock)
-       __acquires(&open_stp->st_stateowner->so_client->cl_lock)
+static void release_open_stateid_locks(struct nfs4_ol_stateid *open_stp,
+                                      struct list_head *reaplist)
 {
        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);
-               spin_unlock(&open_stp->st_stateowner->so_client->cl_lock);
-               release_lock_stateid(stp);
-               spin_lock(&open_stp->st_stateowner->so_client->cl_lock);
+               unhash_lock_stateid(stp);
+               put_ol_stateid_locked(stp, reaplist);
        }
 }
 
-static void unhash_open_stateid(struct nfs4_ol_stateid *stp)
+static void unhash_open_stateid(struct nfs4_ol_stateid *stp,
+                               struct list_head *reaplist)
 {
-       spin_lock(&stp->st_stateowner->so_client->cl_lock);
-       unhash_generic_stateid(stp);
-       release_open_stateid_locks(stp);
-       spin_unlock(&stp->st_stateowner->so_client->cl_lock);
+       lockdep_assert_held(&stp->st_stid.sc_client->cl_lock);
+
+       unhash_ol_stateid(stp);
+       release_open_stateid_locks(stp, reaplist);
 }
 
 static void release_open_stateid(struct nfs4_ol_stateid *stp)
 {
-       unhash_open_stateid(stp);
-       nfs4_put_stid(&stp->st_stid);
+       LIST_HEAD(reaplist);
+
+       spin_lock(&stp->st_stid.sc_client->cl_lock);
+       unhash_open_stateid(stp, &reaplist);
+       put_ol_stateid_locked(stp, &reaplist);
+       spin_unlock(&stp->st_stid.sc_client->cl_lock);
+       free_ol_stateid_reaplist(&reaplist);
 }
 
-static void unhash_openowner(struct nfs4_openowner *oo)
+static void unhash_openowner_locked(struct nfs4_openowner *oo)
 {
+       struct nfs4_client *clp = oo->oo_owner.so_client;
+
+       lockdep_assert_held(&clp->cl_lock);
+
        list_del_init(&oo->oo_owner.so_strhash);
        list_del_init(&oo->oo_perclient);
 }
@@ -1022,21 +1131,24 @@ static void release_last_closed_stateid(struct nfs4_openowner *oo)
        }
 }
 
-static void release_openowner_stateids(struct nfs4_openowner *oo)
+static void release_openowner(struct nfs4_openowner *oo)
 {
        struct nfs4_ol_stateid *stp;
+       struct nfs4_client *clp = oo->oo_owner.so_client;
+       struct list_head reaplist;
 
+       INIT_LIST_HEAD(&reaplist);
+
+       spin_lock(&clp->cl_lock);
+       unhash_openowner_locked(oo);
        while (!list_empty(&oo->oo_owner.so_stateids)) {
                stp = list_first_entry(&oo->oo_owner.so_stateids,
                                struct nfs4_ol_stateid, st_perstateowner);
-               release_open_stateid(stp);
+               unhash_open_stateid(stp, &reaplist);
+               put_ol_stateid_locked(stp, &reaplist);
        }
-}
-
-static void release_openowner(struct nfs4_openowner *oo)
-{
-       unhash_openowner(oo);
-       release_openowner_stateids(oo);
+       spin_unlock(&clp->cl_lock);
+       free_ol_stateid_reaplist(&reaplist);
        release_last_closed_stateid(oo);
        nfs4_put_stateowner(&oo->oo_owner);
 }
@@ -1304,9 +1416,6 @@ static void __free_session(struct nfsd4_session *ses)
 
 static void free_session(struct nfsd4_session *ses)
 {
-       struct nfsd_net *nn = net_generic(ses->se_client->net, nfsd_net_id);
-
-       lockdep_assert_held(&nn->client_lock);
        nfsd4_del_conns(ses);
        nfsd4_put_drc_mem(&ses->se_fchannel);
        __free_session(ses);
@@ -1418,15 +1527,20 @@ STALE_CLIENTID(clientid_t *clid, struct nfsd_net *nn)
 static struct nfs4_client *alloc_client(struct xdr_netobj name)
 {
        struct nfs4_client *clp;
+       int i;
 
        clp = kzalloc(sizeof(struct nfs4_client), GFP_KERNEL);
        if (clp == NULL)
                return NULL;
        clp->cl_name.data = kmemdup(name.data, name.len, GFP_KERNEL);
-       if (clp->cl_name.data == NULL) {
-               kfree(clp);
-               return NULL;
-       }
+       if (clp->cl_name.data == NULL)
+               goto err_no_name;
+       clp->cl_ownerstr_hashtbl = kmalloc(sizeof(struct list_head) *
+                       OWNER_HASH_SIZE, GFP_KERNEL);
+       if (!clp->cl_ownerstr_hashtbl)
+               goto err_no_hashtbl;
+       for (i = 0; i < OWNER_HASH_SIZE; i++)
+               INIT_LIST_HEAD(&clp->cl_ownerstr_hashtbl[i]);
        clp->cl_name.len = name.len;
        INIT_LIST_HEAD(&clp->cl_sessions);
        idr_init(&clp->cl_stateids);
@@ -1441,14 +1555,16 @@ static struct nfs4_client *alloc_client(struct xdr_netobj name)
        spin_lock_init(&clp->cl_lock);
        rpc_init_wait_queue(&clp->cl_cb_waitq, "Backchannel slot table");
        return clp;
+err_no_hashtbl:
+       kfree(clp->cl_name.data);
+err_no_name:
+       kfree(clp);
+       return NULL;
 }
 
 static void
 free_client(struct nfs4_client *clp)
 {
-       struct nfsd_net __maybe_unused *nn = net_generic(clp->net, nfsd_net_id);
-
-       lockdep_assert_held(&nn->client_lock);
        while (!list_empty(&clp->cl_sessions)) {
                struct nfsd4_session *ses;
                ses = list_entry(clp->cl_sessions.next, struct nfsd4_session,
@@ -1459,18 +1575,30 @@ free_client(struct nfs4_client *clp)
        }
        rpc_destroy_wait_queue(&clp->cl_cb_waitq);
        free_svc_cred(&clp->cl_cred);
+       kfree(clp->cl_ownerstr_hashtbl);
        kfree(clp->cl_name.data);
        idr_destroy(&clp->cl_stateids);
        kfree(clp);
 }
 
 /* must be called under the client_lock */
-static inline void
+static void
 unhash_client_locked(struct nfs4_client *clp)
 {
+       struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
        struct nfsd4_session *ses;
 
-       list_del(&clp->cl_lru);
+       /* Mark the client as expired! */
+       clp->cl_time = 0;
+       /* Make it invisible */
+       if (!list_empty(&clp->cl_idhash)) {
+               list_del_init(&clp->cl_idhash);
+               if (test_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags))
+                       rb_erase(&clp->cl_namenode, &nn->conf_name_tree);
+               else
+                       rb_erase(&clp->cl_namenode, &nn->unconf_name_tree);
+       }
+       list_del_init(&clp->cl_lru);
        spin_lock(&clp->cl_lock);
        list_for_each_entry(ses, &clp->cl_sessions, se_perclnt)
                list_del_init(&ses->se_hash);
@@ -1478,12 +1606,21 @@ unhash_client_locked(struct nfs4_client *clp)
 }
 
 static void
-destroy_client(struct nfs4_client *clp)
+unhash_client(struct nfs4_client *clp)
+{
+       struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
+
+       spin_lock(&nn->client_lock);
+       unhash_client_locked(clp);
+       spin_unlock(&nn->client_lock);
+}
+
+static void
+__destroy_client(struct nfs4_client *clp)
 {
        struct nfs4_openowner *oo;
        struct nfs4_delegation *dp;
        struct list_head reaplist;
-       struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
 
        INIT_LIST_HEAD(&reaplist);
        spin_lock(&state_lock);
@@ -1511,22 +1648,21 @@ destroy_client(struct nfs4_client *clp)
        nfsd4_shutdown_callback(clp);
        if (clp->cl_cb_conn.cb_xprt)
                svc_xprt_put(clp->cl_cb_conn.cb_xprt);
-       list_del(&clp->cl_idhash);
-       if (test_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags))
-               rb_erase(&clp->cl_namenode, &nn->conf_name_tree);
-       else
-               rb_erase(&clp->cl_namenode, &nn->unconf_name_tree);
-       spin_lock(&nn->client_lock);
-       unhash_client_locked(clp);
-       WARN_ON_ONCE(atomic_read(&clp->cl_refcount));
        free_client(clp);
-       spin_unlock(&nn->client_lock);
+}
+
+static void
+destroy_client(struct nfs4_client *clp)
+{
+       unhash_client(clp);
+       __destroy_client(clp);
 }
 
 static void expire_client(struct nfs4_client *clp)
 {
+       unhash_client(clp);
        nfsd4_client_record_remove(clp);
-       destroy_client(clp);
+       __destroy_client(clp);
 }
 
 static void copy_verf(struct nfs4_client *target, nfs4_verifier *source)
@@ -1716,7 +1852,6 @@ static struct nfs4_client *create_client(struct xdr_netobj name,
        struct sockaddr *sa = svc_addr(rqstp);
        int ret;
        struct net *net = SVC_NET(rqstp);
-       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
        clp = alloc_client(name);
        if (clp == NULL)
@@ -1724,9 +1859,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name,
 
        ret = copy_cred(&clp->cl_cred, &rqstp->rq_cred);
        if (ret) {
-               spin_lock(&nn->client_lock);
                free_client(clp);
-               spin_unlock(&nn->client_lock);
                return NULL;
        }
        INIT_WORK(&clp->cl_cb_null.cb_work, nfsd4_run_cb_null);
@@ -2048,6 +2181,10 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,
                return nfserr_encr_alg_unsupp;
        }
 
+       new = create_client(exid->clname, rqstp, &verf);
+       if (new == NULL)
+               return nfserr_jukebox;
+
        /* Cases below refer to rfc 5661 section 18.35.4: */
        nfs4_lock_state();
        conf = find_confirmed_client_by_name(&exid->clname, nn);
@@ -2074,7 +2211,6 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,
                        }
                        /* case 6 */
                        exid->flags |= EXCHGID4_FLAG_CONFIRMED_R;
-                       new = conf;
                        goto out_copy;
                }
                if (!creds_match) { /* case 3 */
@@ -2087,7 +2223,6 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,
                }
                if (verfs_match) { /* case 2 */
                        conf->cl_exchange_flags |= EXCHGID4_FLAG_CONFIRMED_R;
-                       new = conf;
                        goto out_copy;
                }
                /* case 5, client reboot */
@@ -2105,29 +2240,28 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,
 
        /* case 1 (normal case) */
 out_new:
-       new = create_client(exid->clname, rqstp, &verf);
-       if (new == NULL) {
-               status = nfserr_jukebox;
-               goto out;
-       }
        new->cl_minorversion = cstate->minorversion;
        new->cl_mach_cred = (exid->spa_how == SP4_MACH_CRED);
 
        gen_clid(new, nn);
        add_to_unconfirmed(new);
+       conf = new;
+       new = NULL;
 out_copy:
-       exid->clientid.cl_boot = new->cl_clientid.cl_boot;
-       exid->clientid.cl_id = new->cl_clientid.cl_id;
+       exid->clientid.cl_boot = conf->cl_clientid.cl_boot;
+       exid->clientid.cl_id = conf->cl_clientid.cl_id;
 
-       exid->seqid = new->cl_cs_slot.sl_seqid + 1;
-       nfsd4_set_ex_flags(new, exid);
+       exid->seqid = conf->cl_cs_slot.sl_seqid + 1;
+       nfsd4_set_ex_flags(conf, exid);
 
        dprintk("nfsd4_exchange_id seqid %d flags %x\n",
-               new->cl_cs_slot.sl_seqid, new->cl_exchange_flags);
+               conf->cl_cs_slot.sl_seqid, conf->cl_exchange_flags);
        status = nfs_ok;
 
 out:
        nfs4_unlock_state();
+       if (new)
+               free_client(new);
        return status;
 }
 
@@ -2770,6 +2904,9 @@ nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        __be32                  status;
        struct nfsd_net         *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
 
+       new = create_client(clname, rqstp, &clverifier);
+       if (new == NULL)
+               return nfserr_jukebox;
        /* Cases below refer to rfc 3530 section 14.2.33: */
        nfs4_lock_state();
        conf = find_confirmed_client_by_name(&clname, nn);
@@ -2790,10 +2927,6 @@ nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        unconf = find_unconfirmed_client_by_name(&clname, nn);
        if (unconf)
                expire_client(unconf);
-       status = nfserr_jukebox;
-       new = create_client(clname, rqstp, &clverifier);
-       if (new == NULL)
-               goto out;
        if (conf && same_verf(&conf->cl_verifier, &clverifier))
                /* case 1: probable callback update */
                copy_clid(new, conf);
@@ -2805,9 +2938,12 @@ nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        setclid->se_clientid.cl_boot = new->cl_clientid.cl_boot;
        setclid->se_clientid.cl_id = new->cl_clientid.cl_id;
        memcpy(setclid->se_confirm.data, new->cl_confirm.data, sizeof(setclid->se_confirm.data));
+       new = NULL;
        status = nfs_ok;
 out:
        nfs4_unlock_state();
+       if (new)
+               free_client(new);
        return status;
 }
 
@@ -2995,17 +3131,16 @@ static inline void *alloc_stateowner(struct kmem_cache *slab, struct xdr_netobj
 
 static void hash_openowner(struct nfs4_openowner *oo, struct nfs4_client *clp, unsigned int strhashval)
 {
-       struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);
+       lockdep_assert_held(&clp->cl_lock);
 
-       list_add(&oo->oo_owner.so_strhash, &nn->ownerstr_hashtbl[strhashval]);
+       list_add(&oo->oo_owner.so_strhash,
+                &clp->cl_ownerstr_hashtbl[strhashval]);
        list_add(&oo->oo_perclient, &clp->cl_openowners);
 }
 
 static void nfs4_unhash_openowner(struct nfs4_stateowner *so)
 {
-       struct nfs4_openowner *oo = openowner(so);
-
-       unhash_openowner(oo);
+       unhash_openowner_locked(openowner(so));
 }
 
 static void nfs4_free_openowner(struct nfs4_stateowner *so)
@@ -3025,7 +3160,7 @@ 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;
+       struct nfs4_openowner *oo, *ret;
 
        oo = alloc_stateowner(openowner_slab, &open->op_owner, clp);
        if (!oo)
@@ -3039,7 +3174,14 @@ alloc_init_open_stateowner(unsigned int strhashval, struct nfsd4_open *open,
        oo->oo_time = 0;
        oo->oo_last_closed_stid = NULL;
        INIT_LIST_HEAD(&oo->oo_close_lru);
-       hash_openowner(oo, clp, strhashval);
+       spin_lock(&clp->cl_lock);
+       ret = find_openstateowner_str_locked(strhashval, open, clp);
+       if (ret == NULL) {
+               hash_openowner(oo, clp, strhashval);
+               ret = oo;
+       } else
+               nfs4_free_openowner(&oo->oo_owner);
+       spin_unlock(&clp->cl_lock);
        return oo;
 }
 
@@ -3100,39 +3242,6 @@ move_to_close_lru(struct nfs4_ol_stateid *s, struct net *net)
        oo->oo_time = get_seconds();
 }
 
-static int
-same_owner_str(struct nfs4_stateowner *sop, struct xdr_netobj *owner,
-                                                       clientid_t *clid)
-{
-       return (sop->so_owner.len == owner->len) &&
-               0 == memcmp(sop->so_owner.data, owner->data, owner->len) &&
-               (sop->so_client->cl_clientid.cl_id == clid->cl_id);
-}
-
-static struct nfs4_openowner *
-find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open,
-                       bool sessions, struct nfsd_net *nn)
-{
-       struct nfs4_stateowner *so;
-       struct nfs4_openowner *oo;
-       struct nfs4_client *clp;
-
-       list_for_each_entry(so, &nn->ownerstr_hashtbl[hashval], so_strhash) {
-               if (!so->so_is_open_owner)
-                       continue;
-               if (same_owner_str(so, &open->op_owner, &open->op_clientid)) {
-                       oo = openowner(so);
-                       clp = oo->oo_owner.so_client;
-                       if ((bool)clp->cl_minorversion != sessions)
-                               return NULL;
-                       renew_client(oo->oo_owner.so_client);
-                       atomic_inc(&oo->oo_owner.so_count);
-                       return oo;
-               }
-       }
-       return NULL;
-}
-
 /* search file_hashtbl[] for file */
 static struct nfs4_file *
 find_file_locked(struct knfsd_fh *fh)
@@ -3354,8 +3463,8 @@ nfsd4_process_open1(struct nfsd4_compound_state *cstate,
                return status;
        clp = cstate->clp;
 
-       strhashval = ownerstr_hashval(clientid->cl_id, &open->op_owner);
-       oo = find_openstateowner_str(strhashval, open, cstate->minorversion, nn);
+       strhashval = ownerstr_hashval(&open->op_owner);
+       oo = find_openstateowner_str(strhashval, open, clp);
        open->op_openowner = oo;
        if (!oo) {
                goto new_owner;
@@ -4006,13 +4115,15 @@ nfs4_laundromat(struct nfsd_net *nn)
                                clp->cl_clientid.cl_id);
                        continue;
                }
-               list_move(&clp->cl_lru, &reaplist);
+               unhash_client_locked(clp);
+               list_add(&clp->cl_lru, &reaplist);
        }
        spin_unlock(&nn->client_lock);
        list_for_each_safe(pos, next, &reaplist) {
                clp = list_entry(pos, struct nfs4_client, cl_lru);
                dprintk("NFSD: purging unused client (clientid %08x)\n",
                        clp->cl_clientid.cl_id);
+               list_del_init(&clp->cl_lru);
                expire_client(clp);
        }
        spin_lock(&state_lock);
@@ -4328,17 +4439,6 @@ unlock_state:
        return status;
 }
 
-static __be32
-nfsd4_free_lock_stateid(struct nfs4_ol_stateid *stp)
-{
-       struct nfs4_lockowner *lo = lockowner(stp->st_stateowner);
-
-       if (check_for_locks(stp->st_stid.sc_file, lo))
-               return nfserr_locks_held;
-       release_lock_stateid(stp);
-       return nfs_ok;
-}
-
 /*
  * Test if the stateid is valid
  */
@@ -4365,6 +4465,7 @@ nfsd4_free_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        stateid_t *stateid = &free_stateid->fr_stateid;
        struct nfs4_stid *s;
        struct nfs4_delegation *dp;
+       struct nfs4_ol_stateid *stp;
        struct nfs4_client *cl = cstate->session->se_client;
        __be32 ret = nfserr_bad_stateid;
 
@@ -4387,8 +4488,15 @@ nfsd4_free_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                ret = check_stateid_generation(stateid, &s->sc_stateid, 1);
                if (ret)
                        break;
+               stp = openlockstateid(s);
+               ret = nfserr_locks_held;
+               if (check_for_locks(stp->st_stid.sc_file,
+                                   lockowner(stp->st_stateowner)))
+                       break;
+               unhash_lock_stateid(stp);
                spin_unlock(&cl->cl_lock);
-               ret = nfsd4_free_lock_stateid(openlockstateid(s));
+               nfs4_put_stid(s);
+               ret = nfs_ok;
                goto out;
        case NFS4_REVOKED_DELEG_STID:
                dp = delegstateid(s);
@@ -4607,14 +4715,21 @@ out:
 static void nfsd4_close_open_stateid(struct nfs4_ol_stateid *s)
 {
        struct nfs4_client *clp = s->st_stid.sc_client;
+       LIST_HEAD(reaplist);
 
        s->st_stid.sc_type = NFS4_CLOSED_STID;
-       unhash_open_stateid(s);
+       spin_lock(&clp->cl_lock);
+       unhash_open_stateid(s, &reaplist);
 
-       if (clp->cl_minorversion)
-               nfs4_put_stid(&s->st_stid);
-       else
+       if (clp->cl_minorversion) {
+               put_ol_stateid_locked(s, &reaplist);
+               spin_unlock(&clp->cl_lock);
+               free_ol_stateid_reaplist(&reaplist);
+       } else {
+               spin_unlock(&clp->cl_lock);
+               free_ol_stateid_reaplist(&reaplist);
                move_to_close_lru(s, clp->net);
+       }
 }
 
 /*
@@ -4759,16 +4874,17 @@ nevermind:
 }
 
 static struct nfs4_lockowner *
-find_lockowner_str(clientid_t *clid, struct xdr_netobj *owner,
-               struct nfsd_net *nn)
+find_lockowner_str_locked(clientid_t *clid, struct xdr_netobj *owner,
+               struct nfs4_client *clp)
 {
-       unsigned int strhashval = ownerstr_hashval(clid->cl_id, owner);
+       unsigned int strhashval = ownerstr_hashval(owner);
        struct nfs4_stateowner *so;
 
-       list_for_each_entry(so, &nn->ownerstr_hashtbl[strhashval], so_strhash) {
+       list_for_each_entry(so, &clp->cl_ownerstr_hashtbl[strhashval],
+                           so_strhash) {
                if (so->so_is_open_owner)
                        continue;
-               if (!same_owner_str(so, owner, clid))
+               if (!same_owner_str(so, owner))
                        continue;
                atomic_inc(&so->so_count);
                return lockowner(so);
@@ -4776,9 +4892,21 @@ find_lockowner_str(clientid_t *clid, struct xdr_netobj *owner,
        return NULL;
 }
 
+static struct nfs4_lockowner *
+find_lockowner_str(clientid_t *clid, struct xdr_netobj *owner,
+               struct nfs4_client *clp)
+{
+       struct nfs4_lockowner *lo;
+
+       spin_lock(&clp->cl_lock);
+       lo = find_lockowner_str_locked(clid, owner, clp);
+       spin_unlock(&clp->cl_lock);
+       return lo;
+}
+
 static void nfs4_unhash_lockowner(struct nfs4_stateowner *sop)
 {
-       unhash_lockowner(lockowner(sop));
+       unhash_lockowner_locked(lockowner(sop));
 }
 
 static void nfs4_free_lockowner(struct nfs4_stateowner *sop)
@@ -4801,9 +4929,11 @@ static const struct nfs4_stateowner_operations lockowner_ops = {
  * 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);
+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, *ret;
 
        lo = alloc_stateowner(lockowner_slab, &lock->lk_new_owner, clp);
        if (!lo)
@@ -4812,7 +4942,16 @@ alloc_init_lock_stateowner(unsigned int strhashval, struct nfs4_client *clp, str
        lo->lo_owner.so_is_open_owner = 0;
        lo->lo_owner.so_seqid = lock->lk_new_lock_seqid;
        lo->lo_owner.so_ops = &lockowner_ops;
-       list_add(&lo->lo_owner.so_strhash, &nn->ownerstr_hashtbl[strhashval]);
+       spin_lock(&clp->cl_lock);
+       ret = find_lockowner_str_locked(&clp->cl_clientid,
+                       &lock->lk_new_owner, clp);
+       if (ret == NULL) {
+               list_add(&lo->lo_owner.so_strhash,
+                        &clp->cl_ownerstr_hashtbl[strhashval]);
+               ret = lo;
+       } else
+               nfs4_free_lockowner(&lo->lo_owner);
+       spin_unlock(&clp->cl_lock);
        return lo;
 }
 
@@ -4924,12 +5063,10 @@ lookup_or_create_lock_state(struct nfsd4_compound_state *cstate,
        struct inode *inode = cstate->current_fh.fh_dentry->d_inode;
        struct nfs4_lockowner *lo;
        unsigned int strhashval;
-       struct nfsd_net *nn = net_generic(cl->net, nfsd_net_id);
 
-       lo = find_lockowner_str(&cl->cl_clientid, &lock->v.new.owner, nn);
+       lo = find_lockowner_str(&cl->cl_clientid, &lock->v.new.owner, cl);
        if (!lo) {
-               strhashval = ownerstr_hashval(cl->cl_clientid.cl_id,
-                               &lock->v.new.owner);
+               strhashval = ownerstr_hashval(&lock->v.new.owner);
                lo = alloc_init_lock_stateowner(strhashval, cl, ost, lock);
                if (lo == NULL)
                        return nfserr_jukebox;
@@ -5207,7 +5344,8 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                goto out;
        }
 
-       lo = find_lockowner_str(&lockt->lt_clientid, &lockt->lt_owner, nn);
+       lo = find_lockowner_str(&lockt->lt_clientid, &lockt->lt_owner,
+                               cstate->clp);
        if (lo)
                file_lock->fl_owner = (fl_owner_t)lo;
        file_lock->fl_pid = current->tgid;
@@ -5346,13 +5484,14 @@ nfsd4_release_lockowner(struct svc_rqst *rqstp,
                        struct nfsd4_release_lockowner *rlockowner)
 {
        clientid_t *clid = &rlockowner->rl_clientid;
-       struct nfs4_stateowner *sop = NULL, *tmp;
-       struct nfs4_lockowner *lo;
+       struct nfs4_stateowner *sop;
+       struct nfs4_lockowner *lo = NULL;
        struct nfs4_ol_stateid *stp;
        struct xdr_netobj *owner = &rlockowner->rl_owner;
-       unsigned int hashval = ownerstr_hashval(clid->cl_id, owner);
+       unsigned int hashval = ownerstr_hashval(owner);
        __be32 status;
        struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
+       struct nfs4_client *clp;
 
        dprintk("nfsd4_release_lockowner clientid: (%08x/%08x):\n",
                clid->cl_boot, clid->cl_id);
@@ -5363,36 +5502,31 @@ nfsd4_release_lockowner(struct svc_rqst *rqstp,
        if (status)
                goto out;
 
-       status = nfserr_locks_held;
-
+       clp = cstate->clp;
        /* 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(tmp, owner, clid)) {
-                       sop = tmp;
-                       atomic_inc(&sop->so_count);
-                       break;
-               }
-       }
+       spin_lock(&clp->cl_lock);
+       list_for_each_entry(sop, &clp->cl_ownerstr_hashtbl[hashval],
+                           so_strhash) {
 
-       /* No matching owner found, maybe a replay? Just declare victory... */
-       if (!sop) {
-               status = nfs_ok;
-               goto out;
-       }
+               if (sop->so_is_open_owner || !same_owner_str(sop, owner))
+                       continue;
 
-       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_stid.sc_file, lo)) {
-                       nfs4_put_stateowner(sop);
-                       goto out;
+               /* see if there are still any locks associated with it */
+               lo = lockowner(sop);
+               list_for_each_entry(stp, &sop->so_stateids, st_perstateowner) {
+                       if (check_for_locks(stp->st_stid.sc_file, lo)) {
+                               status = nfserr_locks_held;
+                               spin_unlock(&clp->cl_lock);
+                               goto out;
+                       }
                }
-       }
 
-       status = nfs_ok;
-       release_lockowner(lo);
+               atomic_inc(&sop->so_count);
+               break;
+       }
+       spin_unlock(&clp->cl_lock);
+       if (lo)
+               release_lockowner(lo);
 out:
        nfs4_unlock_state();
        return status;
@@ -5734,10 +5868,6 @@ static int nfs4_state_create_net(struct net *net)
                        CLIENT_HASH_SIZE, GFP_KERNEL);
        if (!nn->unconf_id_hashtbl)
                goto err_unconf_id;
-       nn->ownerstr_hashtbl = kmalloc(sizeof(struct list_head) *
-                       OWNER_HASH_SIZE, GFP_KERNEL);
-       if (!nn->ownerstr_hashtbl)
-               goto err_ownerstr;
        nn->sessionid_hashtbl = kmalloc(sizeof(struct list_head) *
                        SESSION_HASH_SIZE, GFP_KERNEL);
        if (!nn->sessionid_hashtbl)
@@ -5747,8 +5877,6 @@ static int nfs4_state_create_net(struct net *net)
                INIT_LIST_HEAD(&nn->conf_id_hashtbl[i]);
                INIT_LIST_HEAD(&nn->unconf_id_hashtbl[i]);
        }
-       for (i = 0; i < OWNER_HASH_SIZE; i++)
-               INIT_LIST_HEAD(&nn->ownerstr_hashtbl[i]);
        for (i = 0; i < SESSION_HASH_SIZE; i++)
                INIT_LIST_HEAD(&nn->sessionid_hashtbl[i]);
        nn->conf_name_tree = RB_ROOT;
@@ -5764,8 +5892,6 @@ static int nfs4_state_create_net(struct net *net)
        return 0;
 
 err_sessionid:
-       kfree(nn->ownerstr_hashtbl);
-err_ownerstr:
        kfree(nn->unconf_id_hashtbl);
 err_unconf_id:
        kfree(nn->conf_id_hashtbl);
@@ -5795,7 +5921,6 @@ nfs4_state_destroy_net(struct net *net)
        }
 
        kfree(nn->sessionid_hashtbl);
-       kfree(nn->ownerstr_hashtbl);
        kfree(nn->unconf_id_hashtbl);
        kfree(nn->conf_id_hashtbl);
        put_net(net);