nfsd4: fix destroy_session race
authorJ. Bruce Fields <bfields@redhat.com>
Thu, 14 Mar 2013 23:55:33 +0000 (19:55 -0400)
committerJ. Bruce Fields <bfields@redhat.com>
Wed, 3 Apr 2013 15:48:38 +0000 (11:48 -0400)
destroy_session uses the session and client without continuously holding
any reference or locks.

Put the whole thing under the state lock for now.

Signed-off-by: J. Bruce Fields <bfields@redhat.com>
fs/nfsd/nfs4state.c

index c89bb3c40a0b705408a05b68d67b81aeaa757f3e..8cc668dc4997fb9cf22da80ef369fba7cb897adc 100644 (file)
@@ -1926,41 +1926,35 @@ nfsd4_destroy_session(struct svc_rqst *r,
                      struct nfsd4_destroy_session *sessionid)
 {
        struct nfsd4_session *ses;
-       __be32 status = nfserr_badsession;
+       __be32 status;
        struct nfsd_net *nn = net_generic(SVC_NET(r), nfsd_net_id);
 
-       /* Notes:
-        * - The confirmed nfs4_client->cl_sessionid holds destroyed sessinid
-        * - Should we return nfserr_back_chan_busy if waiting for
-        *   callbacks on to-be-destroyed session?
-        * - Do we need to clear any callback info from previous session?
-        */
-
+       nfs4_lock_state();
+       status = nfserr_not_only_op;
        if (nfsd4_compound_in_session(cstate->session, &sessionid->sessionid)) {
                if (!nfsd4_last_compound_op(r))
-                       return nfserr_not_only_op;
+                       goto out;
        }
        dump_sessionid(__func__, &sessionid->sessionid);
        spin_lock(&nn->client_lock);
        ses = find_in_sessionid_hashtbl(&sessionid->sessionid, SVC_NET(r));
-       if (!ses) {
-               spin_unlock(&nn->client_lock);
-               goto out;
-       }
+       status = nfserr_badsession;
+       if (!ses)
+               goto out_client_lock;
 
        unhash_session(ses);
        spin_unlock(&nn->client_lock);
 
-       nfs4_lock_state();
        nfsd4_probe_callback_sync(ses->se_client);
-       nfs4_unlock_state();
 
        spin_lock(&nn->client_lock);
        nfsd4_del_conns(ses);
        nfsd4_put_session_locked(ses);
-       spin_unlock(&nn->client_lock);
        status = nfs_ok;
+out_client_lock:
+       spin_unlock(&nn->client_lock);
 out:
+       nfs4_unlock_state();
        return status;
 }