nfsd4: mark_client_expired
[safe/jmp/linux-2.6] / fs / nfsd / nfs4state.c
index 4300d9f..98aa7e8 100644 (file)
@@ -34,6 +34,7 @@
 
 #include <linux/file.h>
 #include <linux/smp_lock.h>
+#include <linux/slab.h>
 #include <linux/namei.h>
 #include <linux/swap.h>
 #include <linux/sunrpc/svcauth_gss.h>
@@ -249,6 +250,9 @@ unhash_delegation(struct nfs4_delegation *dp)
  * SETCLIENTID state 
  */
 
+/* client_lock protects the client lru list and session hash table */
+static DEFINE_SPINLOCK(client_lock);
+
 /* Hash tables for nfs4_clientid state */
 #define CLIENT_HASH_BITS                 4
 #define CLIENT_HASH_SIZE                (1 << CLIENT_HASH_BITS)
@@ -367,7 +371,6 @@ static void release_openowner(struct nfs4_stateowner *sop)
        nfs4_put_stateowner(sop);
 }
 
-static DEFINE_SPINLOCK(sessionid_lock);
 #define SESSION_HASH_SIZE      512
 static struct list_head sessionid_hashtbl[SESSION_HASH_SIZE];
 
@@ -565,10 +568,10 @@ alloc_init_session(struct svc_rqst *rqstp, struct nfs4_client *clp,
 
        new->se_flags = cses->flags;
        kref_init(&new->se_ref);
-       spin_lock(&sessionid_lock);
+       spin_lock(&client_lock);
        list_add(&new->se_hash, &sessionid_hashtbl[idx]);
        list_add(&new->se_perclnt, &clp->cl_sessions);
-       spin_unlock(&sessionid_lock);
+       spin_unlock(&client_lock);
 
        status = nfs_ok;
 out:
@@ -579,7 +582,7 @@ out_free:
        goto out;
 }
 
-/* caller must hold sessionid_lock */
+/* caller must hold client_lock */
 static struct nfsd4_session *
 find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid)
 {
@@ -602,7 +605,7 @@ find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid)
        return NULL;
 }
 
-/* caller must hold sessionid_lock */
+/* caller must hold client_lock */
 static void
 unhash_session(struct nfsd4_session *ses)
 {
@@ -610,15 +613,6 @@ unhash_session(struct nfsd4_session *ses)
        list_del(&ses->se_perclnt);
 }
 
-static void
-release_session(struct nfsd4_session *ses)
-{
-       spin_lock(&sessionid_lock);
-       unhash_session(ses);
-       spin_unlock(&sessionid_lock);
-       nfsd4_put_session(ses);
-}
-
 void
 free_session(struct kref *kref)
 {
@@ -634,9 +628,18 @@ free_session(struct kref *kref)
        kfree(ses);
 }
 
+/* must be called under the client_lock */
 static inline void
-renew_client(struct nfs4_client *clp)
+renew_client_locked(struct nfs4_client *clp)
 {
+       if (is_client_expired(clp)) {
+               dprintk("%s: client (clientid %08x/%08x) already expired\n",
+                       __func__,
+                       clp->cl_clientid.cl_boot,
+                       clp->cl_clientid.cl_id);
+               return;
+       }
+
        /*
        * Move client to the end to the LRU list.
        */
@@ -647,6 +650,14 @@ renew_client(struct nfs4_client *clp)
        clp->cl_time = get_seconds();
 }
 
+static inline void
+renew_client(struct nfs4_client *clp)
+{
+       spin_lock(&client_lock);
+       renew_client_locked(clp);
+       spin_unlock(&client_lock);
+}
+
 /* SETCLIENTID and SETCLIENTID_CONFIRM Helper functions */
 static int
 STALE_CLIENTID(clientid_t *clid)
@@ -690,6 +701,21 @@ free_client(struct nfs4_client *clp)
        kfree(clp);
 }
 
+/* must be called under the client_lock */
+static inline void
+unhash_client_locked(struct nfs4_client *clp)
+{
+       mark_client_expired(clp);
+       list_del(&clp->cl_lru);
+       while (!list_empty(&clp->cl_sessions)) {
+               struct nfsd4_session  *ses;
+               ses = list_entry(clp->cl_sessions.next, struct nfsd4_session,
+                                se_perclnt);
+               unhash_session(ses);
+               nfsd4_put_session(ses);
+       }
+}
+
 static void
 expire_client(struct nfs4_client *clp)
 {
@@ -712,23 +738,20 @@ expire_client(struct nfs4_client *clp)
                list_del_init(&dp->dl_recall_lru);
                unhash_delegation(dp);
        }
-       list_del(&clp->cl_idhash);
-       list_del(&clp->cl_strhash);
-       list_del(&clp->cl_lru);
        while (!list_empty(&clp->cl_openowners)) {
                sop = list_entry(clp->cl_openowners.next, struct nfs4_stateowner, so_perclient);
                release_openowner(sop);
        }
-       while (!list_empty(&clp->cl_sessions)) {
-               struct nfsd4_session  *ses;
-               ses = list_entry(clp->cl_sessions.next, struct nfsd4_session,
-                                se_perclnt);
-               release_session(ses);
-       }
        nfsd4_set_callback_client(clp, NULL);
        if (clp->cl_cb_conn.cb_xprt)
                svc_xprt_put(clp->cl_cb_conn.cb_xprt);
-       free_client(clp);
+       list_del(&clp->cl_idhash);
+       list_del(&clp->cl_strhash);
+       spin_lock(&client_lock);
+       unhash_client_locked(clp);
+       if (atomic_read(&clp->cl_refcount) == 0)
+               free_client(clp);
+       spin_unlock(&client_lock);
 }
 
 static void copy_verf(struct nfs4_client *target, nfs4_verifier *source)
@@ -814,6 +837,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name, char *recdir,
        }
 
        memcpy(clp->cl_recdir, recdir, HEXDIR_LEN);
+       atomic_set(&clp->cl_refcount, 0);
        atomic_set(&clp->cl_cb_set, 0);
        INIT_LIST_HEAD(&clp->cl_idhash);
        INIT_LIST_HEAD(&clp->cl_strhash);
@@ -821,6 +845,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name, char *recdir,
        INIT_LIST_HEAD(&clp->cl_delegations);
        INIT_LIST_HEAD(&clp->cl_sessions);
        INIT_LIST_HEAD(&clp->cl_lru);
+       clp->cl_time = get_seconds();
        clear_bit(0, &clp->cl_cb_slot_busy);
        rpc_init_wait_queue(&clp->cl_cb_waitq, "Backchannel slot table");
        copy_verf(clp, verf);
@@ -851,8 +876,7 @@ add_to_unconfirmed(struct nfs4_client *clp, unsigned int strhashval)
        list_add(&clp->cl_strhash, &unconf_str_hashtbl[strhashval]);
        idhashval = clientid_hashval(clp->cl_clientid.cl_id);
        list_add(&clp->cl_idhash, &unconf_id_hashtbl[idhashval]);
-       list_add_tail(&clp->cl_lru, &client_lru);
-       clp->cl_time = get_seconds();
+       renew_client(clp);
 }
 
 static void
@@ -862,10 +886,9 @@ move_to_confirmed(struct nfs4_client *clp)
        unsigned int strhashval;
 
        dprintk("NFSD: move_to_confirm nfs4_client %p\n", clp);
-       list_del_init(&clp->cl_strhash);
        list_move(&clp->cl_idhash, &conf_id_hashtbl[idhashval]);
        strhashval = clientstr_hashval(clp->cl_recdir);
-       list_add(&clp->cl_strhash, &conf_str_hashtbl[strhashval]);
+       list_move(&clp->cl_strhash, &conf_str_hashtbl[strhashval]);
        renew_client(clp);
 }
 
@@ -1343,6 +1366,21 @@ out:
        return status;
 }
 
+static bool nfsd4_last_compound_op(struct svc_rqst *rqstp)
+{
+       struct nfsd4_compoundres *resp = rqstp->rq_resp;
+       struct nfsd4_compoundargs *argp = rqstp->rq_argp;
+
+       return argp->opcnt == resp->opcnt;
+}
+
+static bool nfsd4_compound_in_session(struct nfsd4_session *session, struct nfs4_sessionid *sid)
+{
+       if (!session)
+               return 0;
+       return !memcmp(sid, &session->se_sessionid, sizeof(*sid));
+}
+
 __be32
 nfsd4_destroy_session(struct svc_rqst *r,
                      struct nfsd4_compound_state *cstate,
@@ -1358,16 +1396,20 @@ nfsd4_destroy_session(struct svc_rqst *r,
         * - Do we need to clear any callback info from previous session?
         */
 
+       if (nfsd4_compound_in_session(cstate->session, &sessionid->sessionid)) {
+               if (!nfsd4_last_compound_op(r))
+                       return nfserr_not_only_op;
+       }
        dump_sessionid(__func__, &sessionid->sessionid);
-       spin_lock(&sessionid_lock);
+       spin_lock(&client_lock);
        ses = find_in_sessionid_hashtbl(&sessionid->sessionid);
        if (!ses) {
-               spin_unlock(&sessionid_lock);
+               spin_unlock(&client_lock);
                goto out;
        }
 
        unhash_session(ses);
-       spin_unlock(&sessionid_lock);
+       spin_unlock(&client_lock);
 
        /* wait for callbacks */
        nfsd4_set_callback_client(ses->se_client, NULL);
@@ -1391,7 +1433,7 @@ nfsd4_sequence(struct svc_rqst *rqstp,
        if (resp->opcnt != 1)
                return nfserr_sequence_pos;
 
-       spin_lock(&sessionid_lock);
+       spin_lock(&client_lock);
        status = nfserr_badsession;
        session = find_in_sessionid_hashtbl(&seq->sessionid);
        if (!session)
@@ -1430,18 +1472,14 @@ nfsd4_sequence(struct svc_rqst *rqstp,
        cstate->slot = slot;
        cstate->session = session;
 
-       /* Hold a session reference until done processing the compound:
-        * nfsd4_put_session called only if the cstate slot is set.
-        */
-       nfsd4_get_session(session);
 out:
-       spin_unlock(&sessionid_lock);
-       /* Renew the clientid on success and on replay */
+       /* Hold a session reference until done processing the compound. */
        if (cstate->session) {
-               nfs4_lock_state();
-               renew_client(session->se_client);
-               nfs4_unlock_state();
+               nfsd4_get_session(cstate->session);
+               /* Renew the clientid on success and on replay */
+               renew_client_locked(session->se_client);
        }
+       spin_unlock(&client_lock);
        dprintk("%s: return %d\n", __func__, ntohl(status));
        return status;
 }
@@ -2550,6 +2588,8 @@ nfs4_laundromat(void)
        dprintk("NFSD: laundromat service - starting\n");
        if (locks_in_grace())
                nfsd4_end_grace();
+       INIT_LIST_HEAD(&reaplist);
+       spin_lock(&client_lock);
        list_for_each_safe(pos, next, &client_lru) {
                clp = list_entry(pos, struct nfs4_client, cl_lru);
                if (time_after((unsigned long)clp->cl_time, (unsigned long)cutoff)) {
@@ -2558,12 +2598,16 @@ nfs4_laundromat(void)
                                clientid_val = t;
                        break;
                }
+               list_move(&clp->cl_lru, &reaplist);
+       }
+       spin_unlock(&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);
                nfsd4_remove_clid_dir(clp);
                expire_client(clp);
        }
-       INIT_LIST_HEAD(&reaplist);
        spin_lock(&recall_lock);
        list_for_each_safe(pos, next, &del_recall_lru) {
                dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);