Merge branch 'for-2.6.37' of git://linux-nfs.org/~bfields/linux
[~shefty/rdma-dev.git] / fs / nfsd / nfs4state.c
index a7292fcf7718e0d1471ddcfd2dab4e3957f17e18..9019e8ec9dc861a9e3b0ff27c53bc16ab2d42cb1 100644 (file)
@@ -207,7 +207,6 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_stateid *stp, struct svc_f
 {
        struct nfs4_delegation *dp;
        struct nfs4_file *fp = stp->st_file;
-       struct nfs4_cb_conn *cb = &stp->st_stateowner->so_client->cl_cb_conn;
 
        dprintk("NFSD alloc_init_deleg\n");
        /*
@@ -234,7 +233,6 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_stateid *stp, struct svc_f
        nfs4_file_get_access(fp, O_RDONLY);
        dp->dl_flock = NULL;
        dp->dl_type = type;
-       dp->dl_ident = cb->cb_ident;
        dp->dl_stateid.si_boot = boot_time;
        dp->dl_stateid.si_stateownerid = current_delegid++;
        dp->dl_stateid.si_fileid = 0;
@@ -535,171 +533,258 @@ gen_sessionid(struct nfsd4_session *ses)
  */
 #define NFSD_MIN_HDR_SEQ_SZ  (24 + 12 + 44)
 
+static void
+free_session_slots(struct nfsd4_session *ses)
+{
+       int i;
+
+       for (i = 0; i < ses->se_fchannel.maxreqs; i++)
+               kfree(ses->se_slots[i]);
+}
+
 /*
- * Give the client the number of ca_maxresponsesize_cached slots it
- * requests, of size bounded by NFSD_SLOT_CACHE_SIZE,
- * NFSD_MAX_MEM_PER_SESSION, and nfsd_drc_max_mem. Do not allow more
- * than NFSD_MAX_SLOTS_PER_SESSION.
- *
- * If we run out of reserved DRC memory we should (up to a point)
+ * We don't actually need to cache the rpc and session headers, so we
+ * can allocate a little less for each slot:
+ */
+static inline int slot_bytes(struct nfsd4_channel_attrs *ca)
+{
+       return ca->maxresp_cached - NFSD_MIN_HDR_SEQ_SZ;
+}
+
+static int nfsd4_sanitize_slot_size(u32 size)
+{
+       size -= NFSD_MIN_HDR_SEQ_SZ; /* We don't cache the rpc header */
+       size = min_t(u32, size, NFSD_SLOT_CACHE_SIZE);
+
+       return size;
+}
+
+/*
+ * XXX: If we run out of reserved DRC memory we could (up to a point)
  * re-negotiate active sessions and reduce their slot usage to make
  * rooom for new connections. For now we just fail the create session.
  */
-static int set_forechannel_drc_size(struct nfsd4_channel_attrs *fchan)
+static int nfsd4_get_drc_mem(int slotsize, u32 num)
 {
-       int mem, size = fchan->maxresp_cached;
+       int avail;
 
-       if (fchan->maxreqs < 1)
-               return nfserr_inval;
+       num = min_t(u32, num, NFSD_MAX_SLOTS_PER_SESSION);
 
-       if (size < NFSD_MIN_HDR_SEQ_SZ)
-               size = NFSD_MIN_HDR_SEQ_SZ;
-       size -= NFSD_MIN_HDR_SEQ_SZ;
-       if (size > NFSD_SLOT_CACHE_SIZE)
-               size = NFSD_SLOT_CACHE_SIZE;
-
-       /* bound the maxreqs by NFSD_MAX_MEM_PER_SESSION */
-       mem = fchan->maxreqs * size;
-       if (mem > NFSD_MAX_MEM_PER_SESSION) {
-               fchan->maxreqs = NFSD_MAX_MEM_PER_SESSION / size;
-               if (fchan->maxreqs > NFSD_MAX_SLOTS_PER_SESSION)
-                       fchan->maxreqs = NFSD_MAX_SLOTS_PER_SESSION;
-               mem = fchan->maxreqs * size;
-       }
+       spin_lock(&nfsd_drc_lock);
+       avail = min_t(int, NFSD_MAX_MEM_PER_SESSION,
+                       nfsd_drc_max_mem - nfsd_drc_mem_used);
+       num = min_t(int, num, avail / slotsize);
+       nfsd_drc_mem_used += num * slotsize;
+       spin_unlock(&nfsd_drc_lock);
 
+       return num;
+}
+
+static void nfsd4_put_drc_mem(int slotsize, int num)
+{
        spin_lock(&nfsd_drc_lock);
-       /* bound the total session drc memory ussage */
-       if (mem + nfsd_drc_mem_used > nfsd_drc_max_mem) {
-               fchan->maxreqs = (nfsd_drc_max_mem - nfsd_drc_mem_used) / size;
-               mem = fchan->maxreqs * size;
-       }
-       nfsd_drc_mem_used += mem;
+       nfsd_drc_mem_used -= slotsize * num;
        spin_unlock(&nfsd_drc_lock);
+}
 
-       if (fchan->maxreqs == 0)
-               return nfserr_jukebox;
+static struct nfsd4_session *alloc_session(int slotsize, int numslots)
+{
+       struct nfsd4_session *new;
+       int mem, i;
 
-       fchan->maxresp_cached = size + NFSD_MIN_HDR_SEQ_SZ;
-       return 0;
+       BUILD_BUG_ON(NFSD_MAX_SLOTS_PER_SESSION * sizeof(struct nfsd4_slot *)
+                       + sizeof(struct nfsd4_session) > PAGE_SIZE);
+       mem = numslots * sizeof(struct nfsd4_slot *);
+
+       new = kzalloc(sizeof(*new) + mem, GFP_KERNEL);
+       if (!new)
+               return NULL;
+       /* allocate each struct nfsd4_slot and data cache in one piece */
+       for (i = 0; i < numslots; i++) {
+               mem = sizeof(struct nfsd4_slot) + slotsize;
+               new->se_slots[i] = kzalloc(mem, GFP_KERNEL);
+               if (!new->se_slots[i])
+                       goto out_free;
+       }
+       return new;
+out_free:
+       while (i--)
+               kfree(new->se_slots[i]);
+       kfree(new);
+       return NULL;
 }
 
-/*
- * fchan holds the client values on input, and the server values on output
- * sv_max_mesg is the maximum payload plus one page for overhead.
- */
-static int init_forechannel_attrs(struct svc_rqst *rqstp,
-                                 struct nfsd4_channel_attrs *session_fchan,
-                                 struct nfsd4_channel_attrs *fchan)
+static void init_forechannel_attrs(struct nfsd4_channel_attrs *new, struct nfsd4_channel_attrs *req, int numslots, int slotsize)
 {
-       int status = 0;
-       __u32   maxcount = nfsd_serv->sv_max_mesg;
+       u32 maxrpc = nfsd_serv->sv_max_mesg;
 
-       /* headerpadsz set to zero in encode routine */
+       new->maxreqs = numslots;
+       new->maxresp_cached = slotsize + NFSD_MIN_HDR_SEQ_SZ;
+       new->maxreq_sz = min_t(u32, req->maxreq_sz, maxrpc);
+       new->maxresp_sz = min_t(u32, req->maxresp_sz, maxrpc);
+       new->maxops = min_t(u32, req->maxops, NFSD_MAX_OPS_PER_COMPOUND);
+}
 
-       /* Use the client's max request and max response size if possible */
-       if (fchan->maxreq_sz > maxcount)
-               fchan->maxreq_sz = maxcount;
-       session_fchan->maxreq_sz = fchan->maxreq_sz;
+static void free_conn(struct nfsd4_conn *c)
+{
+       svc_xprt_put(c->cn_xprt);
+       kfree(c);
+}
 
-       if (fchan->maxresp_sz > maxcount)
-               fchan->maxresp_sz = maxcount;
-       session_fchan->maxresp_sz = fchan->maxresp_sz;
+static void nfsd4_conn_lost(struct svc_xpt_user *u)
+{
+       struct nfsd4_conn *c = container_of(u, struct nfsd4_conn, cn_xpt_user);
+       struct nfs4_client *clp = c->cn_session->se_client;
 
-       /* Use the client's maxops if possible */
-       if (fchan->maxops > NFSD_MAX_OPS_PER_COMPOUND)
-               fchan->maxops = NFSD_MAX_OPS_PER_COMPOUND;
-       session_fchan->maxops = fchan->maxops;
+       spin_lock(&clp->cl_lock);
+       if (!list_empty(&c->cn_persession)) {
+               list_del(&c->cn_persession);
+               free_conn(c);
+       }
+       spin_unlock(&clp->cl_lock);
+}
 
-       /* FIXME: Error means no more DRC pages so the server should
-        * recover pages from existing sessions. For now fail session
-        * creation.
-        */
-       status = set_forechannel_drc_size(fchan);
+static struct nfsd4_conn *alloc_conn(struct svc_rqst *rqstp, u32 flags)
+{
+       struct nfsd4_conn *conn;
 
-       session_fchan->maxresp_cached = fchan->maxresp_cached;
-       session_fchan->maxreqs = fchan->maxreqs;
+       conn = kmalloc(sizeof(struct nfsd4_conn), GFP_KERNEL);
+       if (!conn)
+               return NULL;
+       svc_xprt_get(rqstp->rq_xprt);
+       conn->cn_xprt = rqstp->rq_xprt;
+       conn->cn_flags = flags;
+       INIT_LIST_HEAD(&conn->cn_xpt_user.list);
+       return conn;
+}
 
-       dprintk("%s status %d\n", __func__, status);
-       return status;
+static void __nfsd4_hash_conn(struct nfsd4_conn *conn, struct nfsd4_session *ses)
+{
+       conn->cn_session = ses;
+       list_add(&conn->cn_persession, &ses->se_conns);
 }
 
-static void
-free_session_slots(struct nfsd4_session *ses)
+static void nfsd4_hash_conn(struct nfsd4_conn *conn, struct nfsd4_session *ses)
 {
-       int i;
+       struct nfs4_client *clp = ses->se_client;
 
-       for (i = 0; i < ses->se_fchannel.maxreqs; i++)
-               kfree(ses->se_slots[i]);
+       spin_lock(&clp->cl_lock);
+       __nfsd4_hash_conn(conn, ses);
+       spin_unlock(&clp->cl_lock);
 }
 
-/*
- * We don't actually need to cache the rpc and session headers, so we
- * can allocate a little less for each slot:
- */
-static inline int slot_bytes(struct nfsd4_channel_attrs *ca)
+static void nfsd4_register_conn(struct nfsd4_conn *conn)
 {
-       return ca->maxresp_cached - NFSD_MIN_HDR_SEQ_SZ;
+       conn->cn_xpt_user.callback = nfsd4_conn_lost;
+       register_xpt_user(conn->cn_xprt, &conn->cn_xpt_user);
 }
 
-static int
-alloc_init_session(struct svc_rqst *rqstp, struct nfs4_client *clp,
-                  struct nfsd4_create_session *cses)
+static __be32 nfsd4_new_conn(struct svc_rqst *rqstp, struct nfsd4_session *ses)
 {
-       struct nfsd4_session *new, tmp;
-       struct nfsd4_slot *sp;
-       int idx, slotsize, cachesize, i;
-       int status;
+       struct nfsd4_conn *conn;
+       u32 flags = NFS4_CDFC4_FORE;
 
-       memset(&tmp, 0, sizeof(tmp));
+       if (ses->se_flags & SESSION4_BACK_CHAN)
+               flags |= NFS4_CDFC4_BACK;
+       conn = alloc_conn(rqstp, flags);
+       if (!conn)
+               return nfserr_jukebox;
+       nfsd4_hash_conn(conn, ses);
+       nfsd4_register_conn(conn);
+       return nfs_ok;
+}
 
-       /* FIXME: For now, we just accept the client back channel attributes. */
-       tmp.se_bchannel = cses->back_channel;
-       status = init_forechannel_attrs(rqstp, &tmp.se_fchannel,
-                                       &cses->fore_channel);
-       if (status)
-               goto out;
+static void nfsd4_del_conns(struct nfsd4_session *s)
+{
+       struct nfs4_client *clp = s->se_client;
+       struct nfsd4_conn *c;
 
-       BUILD_BUG_ON(NFSD_MAX_SLOTS_PER_SESSION * sizeof(struct nfsd4_slot)
-                    + sizeof(struct nfsd4_session) > PAGE_SIZE);
+       spin_lock(&clp->cl_lock);
+       while (!list_empty(&s->se_conns)) {
+               c = list_first_entry(&s->se_conns, struct nfsd4_conn, cn_persession);
+               list_del_init(&c->cn_persession);
+               spin_unlock(&clp->cl_lock);
 
-       status = nfserr_jukebox;
-       /* allocate struct nfsd4_session and slot table pointers in one piece */
-       slotsize = tmp.se_fchannel.maxreqs * sizeof(struct nfsd4_slot *);
-       new = kzalloc(sizeof(*new) + slotsize, GFP_KERNEL);
-       if (!new)
-               goto out;
+               unregister_xpt_user(c->cn_xprt, &c->cn_xpt_user);
+               free_conn(c);
 
-       memcpy(new, &tmp, sizeof(*new));
+               spin_lock(&clp->cl_lock);
+       }
+       spin_unlock(&clp->cl_lock);
+}
 
-       /* allocate each struct nfsd4_slot and data cache in one piece */
-       cachesize = slot_bytes(&new->se_fchannel);
-       for (i = 0; i < new->se_fchannel.maxreqs; i++) {
-               sp = kzalloc(sizeof(*sp) + cachesize, GFP_KERNEL);
-               if (!sp)
-                       goto out_free;
-               new->se_slots[i] = sp;
+void free_session(struct kref *kref)
+{
+       struct nfsd4_session *ses;
+       int mem;
+
+       ses = container_of(kref, struct nfsd4_session, se_ref);
+       nfsd4_del_conns(ses);
+       spin_lock(&nfsd_drc_lock);
+       mem = ses->se_fchannel.maxreqs * slot_bytes(&ses->se_fchannel);
+       nfsd_drc_mem_used -= mem;
+       spin_unlock(&nfsd_drc_lock);
+       free_session_slots(ses);
+       kfree(ses);
+}
+
+static struct nfsd4_session *alloc_init_session(struct svc_rqst *rqstp, struct nfs4_client *clp, struct nfsd4_create_session *cses)
+{
+       struct nfsd4_session *new;
+       struct nfsd4_channel_attrs *fchan = &cses->fore_channel;
+       int numslots, slotsize;
+       int status;
+       int idx;
+
+       /*
+        * Note decreasing slot size below client's request may
+        * make it difficult for client to function correctly, whereas
+        * decreasing the number of slots will (just?) affect
+        * performance.  When short on memory we therefore prefer to
+        * decrease number of slots instead of their size.
+        */
+       slotsize = nfsd4_sanitize_slot_size(fchan->maxresp_cached);
+       numslots = nfsd4_get_drc_mem(slotsize, fchan->maxreqs);
+
+       new = alloc_session(slotsize, numslots);
+       if (!new) {
+               nfsd4_put_drc_mem(slotsize, fchan->maxreqs);
+               return NULL;
        }
+       init_forechannel_attrs(&new->se_fchannel, fchan, numslots, slotsize);
 
        new->se_client = clp;
        gen_sessionid(new);
-       idx = hash_sessionid(&new->se_sessionid);
-       memcpy(clp->cl_sessionid.data, new->se_sessionid.data,
-              NFS4_MAX_SESSIONID_LEN);
 
+       INIT_LIST_HEAD(&new->se_conns);
+
+       new->se_cb_seq_nr = 1;
        new->se_flags = cses->flags;
+       new->se_cb_prog = cses->callback_prog;
        kref_init(&new->se_ref);
+       idx = hash_sessionid(&new->se_sessionid);
        spin_lock(&client_lock);
        list_add(&new->se_hash, &sessionid_hashtbl[idx]);
        list_add(&new->se_perclnt, &clp->cl_sessions);
        spin_unlock(&client_lock);
 
-       status = nfs_ok;
-out:
-       return status;
-out_free:
-       free_session_slots(new);
-       kfree(new);
-       goto out;
+       status = nfsd4_new_conn(rqstp, new);
+       /* whoops: benny points out, status is ignored! (err, or bogus) */
+       if (status) {
+               free_session(&new->se_ref);
+               return NULL;
+       }
+       if (!clp->cl_cb_session && (cses->flags & SESSION4_BACK_CHAN)) {
+               struct sockaddr *sa = svc_addr(rqstp);
+
+               clp->cl_cb_session = new;
+               clp->cl_cb_conn.cb_xprt = rqstp->rq_xprt;
+               svc_xprt_get(rqstp->rq_xprt);
+               rpc_copy_addr((struct sockaddr *)&clp->cl_cb_conn.cb_addr, sa);
+               clp->cl_cb_conn.cb_addrlen = svc_addr_len(sa);
+               nfsd4_probe_callback(clp);
+       }
+       return new;
 }
 
 /* caller must hold client_lock */
@@ -731,21 +816,6 @@ unhash_session(struct nfsd4_session *ses)
        list_del(&ses->se_perclnt);
 }
 
-void
-free_session(struct kref *kref)
-{
-       struct nfsd4_session *ses;
-       int mem;
-
-       ses = container_of(kref, struct nfsd4_session, se_ref);
-       spin_lock(&nfsd_drc_lock);
-       mem = ses->se_fchannel.maxreqs * slot_bytes(&ses->se_fchannel);
-       nfsd_drc_mem_used -= mem;
-       spin_unlock(&nfsd_drc_lock);
-       free_session_slots(ses);
-       kfree(ses);
-}
-
 /* must be called under the client_lock */
 static inline void
 renew_client_locked(struct nfs4_client *clp)
@@ -812,6 +882,13 @@ static struct nfs4_client *alloc_client(struct xdr_netobj name)
 static inline void
 free_client(struct nfs4_client *clp)
 {
+       while (!list_empty(&clp->cl_sessions)) {
+               struct nfsd4_session *ses;
+               ses = list_entry(clp->cl_sessions.next, struct nfsd4_session,
+                               se_perclnt);
+               list_del(&ses->se_perclnt);
+               nfsd4_put_session(ses);
+       }
        if (clp->cl_cred.cr_group_info)
                put_group_info(clp->cl_cred.cr_group_info);
        kfree(clp->cl_principal);
@@ -838,15 +915,12 @@ release_session_client(struct nfsd4_session *session)
 static inline void
 unhash_client_locked(struct nfs4_client *clp)
 {
+       struct nfsd4_session *ses;
+
        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);
-       }
+       list_for_each_entry(ses, &clp->cl_sessions, se_perclnt)
+               list_del_init(&ses->se_hash);
 }
 
 static void
@@ -875,7 +949,7 @@ expire_client(struct nfs4_client *clp)
                sop = list_entry(clp->cl_openowners.next, struct nfs4_stateowner, so_perclient);
                release_openowner(sop);
        }
-       nfsd4_set_callback_client(clp, NULL);
+       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);
@@ -960,6 +1034,8 @@ static struct nfs4_client *create_client(struct xdr_netobj name, char *recdir,
        if (clp == NULL)
                return NULL;
 
+       INIT_LIST_HEAD(&clp->cl_sessions);
+
        princ = svc_gss_principal(rqstp);
        if (princ) {
                clp->cl_principal = kstrdup(princ, GFP_KERNEL);
@@ -976,8 +1052,9 @@ static struct nfs4_client *create_client(struct xdr_netobj name, char *recdir,
        INIT_LIST_HEAD(&clp->cl_strhash);
        INIT_LIST_HEAD(&clp->cl_openowners);
        INIT_LIST_HEAD(&clp->cl_delegations);
-       INIT_LIST_HEAD(&clp->cl_sessions);
        INIT_LIST_HEAD(&clp->cl_lru);
+       spin_lock_init(&clp->cl_lock);
+       INIT_WORK(&clp->cl_cb_null.cb_work, nfsd4_do_callback_rpc);
        clp->cl_time = get_seconds();
        clear_bit(0, &clp->cl_cb_slot_busy);
        rpc_init_wait_queue(&clp->cl_cb_waitq, "Backchannel slot table");
@@ -986,7 +1063,7 @@ static struct nfs4_client *create_client(struct xdr_netobj name, char *recdir,
        clp->cl_flavor = rqstp->rq_flavor;
        copy_cred(&clp->cl_cred, &rqstp->rq_cred);
        gen_confirm(clp);
-
+       clp->cl_cb_session = NULL;
        return clp;
 }
 
@@ -1098,7 +1175,7 @@ find_unconfirmed_client_by_str(const char *dname, unsigned int hashval,
 static void
 gen_callback(struct nfs4_client *clp, struct nfsd4_setclientid *se, u32 scopeid)
 {
-       struct nfs4_cb_conn *cb = &clp->cl_cb_conn;
+       struct nfs4_cb_conn *conn = &clp->cl_cb_conn;
        unsigned short expected_family;
 
        /* Currently, we only support tcp and tcp6 for the callback channel */
@@ -1111,24 +1188,23 @@ gen_callback(struct nfs4_client *clp, struct nfsd4_setclientid *se, u32 scopeid)
        else
                goto out_err;
 
-       cb->cb_addrlen = rpc_uaddr2sockaddr(se->se_callback_addr_val,
+       conn->cb_addrlen = rpc_uaddr2sockaddr(se->se_callback_addr_val,
                                            se->se_callback_addr_len,
-                                           (struct sockaddr *) &cb->cb_addr,
-                                           sizeof(cb->cb_addr));
+                                           (struct sockaddr *)&conn->cb_addr,
+                                           sizeof(conn->cb_addr));
 
-       if (!cb->cb_addrlen || cb->cb_addr.ss_family != expected_family)
+       if (!conn->cb_addrlen || conn->cb_addr.ss_family != expected_family)
                goto out_err;
 
-       if (cb->cb_addr.ss_family == AF_INET6)
-               ((struct sockaddr_in6 *) &cb->cb_addr)->sin6_scope_id = scopeid;
+       if (conn->cb_addr.ss_family == AF_INET6)
+               ((struct sockaddr_in6 *)&conn->cb_addr)->sin6_scope_id = scopeid;
 
-       cb->cb_minorversion = 0;
-       cb->cb_prog = se->se_callback_prog;
-       cb->cb_ident = se->se_callback_ident;
+       conn->cb_prog = se->se_callback_prog;
+       conn->cb_ident = se->se_callback_ident;
        return;
 out_err:
-       cb->cb_addr.ss_family = AF_UNSPEC;
-       cb->cb_addrlen = 0;
+       conn->cb_addr.ss_family = AF_UNSPEC;
+       conn->cb_addrlen = 0;
        dprintk(KERN_INFO "NFSD: this client (clientid %08x/%08x) "
                "will not receive delegations\n",
                clp->cl_clientid.cl_boot, clp->cl_clientid.cl_id);
@@ -1415,7 +1491,9 @@ nfsd4_create_session(struct svc_rqst *rqstp,
 {
        struct sockaddr *sa = svc_addr(rqstp);
        struct nfs4_client *conf, *unconf;
+       struct nfsd4_session *new;
        struct nfsd4_clid_slot *cs_slot = NULL;
+       bool confirm_me = false;
        int status = 0;
 
        nfs4_lock_state();
@@ -1438,7 +1516,6 @@ nfsd4_create_session(struct svc_rqst *rqstp,
                                cs_slot->sl_seqid, cr_ses->seqid);
                        goto out;
                }
-               cs_slot->sl_seqid++;
        } else if (unconf) {
                if (!same_creds(&unconf->cl_cred, &rqstp->rq_cred) ||
                    !rpc_cmp_addr(sa, (struct sockaddr *) &unconf->cl_addr)) {
@@ -1451,48 +1528,41 @@ nfsd4_create_session(struct svc_rqst *rqstp,
                if (status) {
                        /* an unconfirmed replay returns misordered */
                        status = nfserr_seq_misordered;
-                       goto out_cache;
+                       goto out;
                }
 
-               cs_slot->sl_seqid++; /* from 0 to 1 */
-               move_to_confirmed(unconf);
-
-               if (cr_ses->flags & SESSION4_BACK_CHAN) {
-                       unconf->cl_cb_conn.cb_xprt = rqstp->rq_xprt;
-                       svc_xprt_get(rqstp->rq_xprt);
-                       rpc_copy_addr(
-                               (struct sockaddr *)&unconf->cl_cb_conn.cb_addr,
-                               sa);
-                       unconf->cl_cb_conn.cb_addrlen = svc_addr_len(sa);
-                       unconf->cl_cb_conn.cb_minorversion =
-                               cstate->minorversion;
-                       unconf->cl_cb_conn.cb_prog = cr_ses->callback_prog;
-                       unconf->cl_cb_seq_nr = 1;
-                       nfsd4_probe_callback(unconf, &unconf->cl_cb_conn);
-               }
+               confirm_me = true;
                conf = unconf;
        } else {
                status = nfserr_stale_clientid;
                goto out;
        }
 
+       /*
+        * XXX: we should probably set this at creation time, and check
+        * for consistent minorversion use throughout:
+        */
+       conf->cl_minorversion = 1;
        /*
         * We do not support RDMA or persistent sessions
         */
        cr_ses->flags &= ~SESSION4_PERSIST;
        cr_ses->flags &= ~SESSION4_RDMA;
 
-       status = alloc_init_session(rqstp, conf, cr_ses);
-       if (status)
+       status = nfserr_jukebox;
+       new = alloc_init_session(rqstp, conf, cr_ses);
+       if (!new)
                goto out;
-
-       memcpy(cr_ses->sessionid.data, conf->cl_sessionid.data,
+       status = nfs_ok;
+       memcpy(cr_ses->sessionid.data, new->se_sessionid.data,
               NFS4_MAX_SESSIONID_LEN);
+       cs_slot->sl_seqid++;
        cr_ses->seqid = cs_slot->sl_seqid;
 
-out_cache:
        /* cache solo and embedded create sessions under the state lock */
        nfsd4_cache_create_session(cr_ses, cs_slot, status);
+       if (confirm_me)
+               move_to_confirmed(conf);
 out:
        nfs4_unlock_state();
        dprintk("%s returns %d\n", __func__, ntohl(status));
@@ -1546,8 +1616,11 @@ nfsd4_destroy_session(struct svc_rqst *r,
 
        nfs4_lock_state();
        /* wait for callbacks */
-       nfsd4_set_callback_client(ses->se_client, NULL);
+       nfsd4_shutdown_callback(ses->se_client);
        nfs4_unlock_state();
+
+       nfsd4_del_conns(ses);
+
        nfsd4_put_session(ses);
        status = nfs_ok;
 out:
@@ -1555,6 +1628,36 @@ out:
        return status;
 }
 
+static struct nfsd4_conn *__nfsd4_find_conn(struct svc_xprt *xpt, struct nfsd4_session *s)
+{
+       struct nfsd4_conn *c;
+
+       list_for_each_entry(c, &s->se_conns, cn_persession) {
+               if (c->cn_xprt == xpt) {
+                       return c;
+               }
+       }
+       return NULL;
+}
+
+static void nfsd4_sequence_check_conn(struct nfsd4_conn *new, struct nfsd4_session *ses)
+{
+       struct nfs4_client *clp = ses->se_client;
+       struct nfsd4_conn *c;
+
+       spin_lock(&clp->cl_lock);
+       c = __nfsd4_find_conn(new->cn_xprt, ses);
+       if (c) {
+               spin_unlock(&clp->cl_lock);
+               free_conn(new);
+               return;
+       }
+       __nfsd4_hash_conn(new, ses);
+       spin_unlock(&clp->cl_lock);
+       nfsd4_register_conn(new);
+       return;
+}
+
 __be32
 nfsd4_sequence(struct svc_rqst *rqstp,
               struct nfsd4_compound_state *cstate,
@@ -1563,11 +1666,20 @@ nfsd4_sequence(struct svc_rqst *rqstp,
        struct nfsd4_compoundres *resp = rqstp->rq_resp;
        struct nfsd4_session *session;
        struct nfsd4_slot *slot;
+       struct nfsd4_conn *conn;
        int status;
 
        if (resp->opcnt != 1)
                return nfserr_sequence_pos;
 
+       /*
+        * Will be either used or freed by nfsd4_sequence_check_conn
+        * below.
+        */
+       conn = alloc_conn(rqstp, NFS4_CDFC4_FORE);
+       if (!conn)
+               return nfserr_jukebox;
+
        spin_lock(&client_lock);
        status = nfserr_badsession;
        session = find_in_sessionid_hashtbl(&seq->sessionid);
@@ -1599,6 +1711,9 @@ nfsd4_sequence(struct svc_rqst *rqstp,
        if (status)
                goto out;
 
+       nfsd4_sequence_check_conn(conn, session);
+       conn = NULL;
+
        /* Success! bump slot seqid */
        slot->sl_inuse = true;
        slot->sl_seqid = seq->seqid;
@@ -1613,6 +1728,7 @@ out:
                nfsd4_get_session(cstate->session);
                atomic_inc(&session->se_client->cl_refcount);
        }
+       kfree(conn);
        spin_unlock(&client_lock);
        dprintk("%s: return %d\n", __func__, ntohl(status));
        return status;
@@ -1747,6 +1863,11 @@ nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
                        goto out;
                gen_clid(new);
        }
+       /*
+        * XXX: we should probably set this at creation time, and check
+        * for consistent minorversion use throughout:
+        */
+       new->cl_minorversion = 0;
        gen_callback(new, setclid, rpc_get_scope_id(sa));
        add_to_unconfirmed(new, strhashval);
        setclid->se_clientid.cl_boot = new->cl_clientid.cl_boot;
@@ -1807,7 +1928,8 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,
                        status = nfserr_clid_inuse;
                else {
                        atomic_set(&conf->cl_cb_set, 0);
-                       nfsd4_probe_callback(conf, &unconf->cl_cb_conn);
+                       nfsd4_change_callback(conf, &unconf->cl_cb_conn);
+                       nfsd4_probe_callback(conf);
                        expire_client(unconf);
                        status = nfs_ok;
 
@@ -1841,7 +1963,7 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,
                        }
                        move_to_confirmed(unconf);
                        conf = unconf;
-                       nfsd4_probe_callback(conf, &conf->cl_cb_conn);
+                       nfsd4_probe_callback(conf);
                        status = nfs_ok;
                }
        } else if ((!conf || (conf && !same_verf(&conf->cl_confirm, &confirm)))
@@ -2944,7 +3066,11 @@ nfs4_preprocess_stateid_op(struct nfsd4_compound_state *cstate,
        if (STALE_STATEID(stateid)) 
                goto out;
 
-       status = nfserr_bad_stateid;
+       /*
+        * We assume that any stateid that has the current boot time,
+        * but that we can't find, is expired:
+        */
+       status = nfserr_expired;
        if (is_delegation_stateid(stateid)) {
                dp = find_delegation_stateid(ino, stateid);
                if (!dp)
@@ -2964,6 +3090,7 @@ nfs4_preprocess_stateid_op(struct nfsd4_compound_state *cstate,
                stp = find_stateid(stateid, flags);
                if (!stp)
                        goto out;
+               status = nfserr_bad_stateid;
                if (nfs4_check_fh(current_fh, stp))
                        goto out;
                if (!stp->st_stateowner->so_confirmed)
@@ -3038,8 +3165,9 @@ nfs4_preprocess_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid,
                 * a replayed close:
                 */
                sop = search_close_lru(stateid->si_stateownerid, flags);
+               /* It's not stale; let's assume it's expired: */
                if (sop == NULL)
-                       return nfserr_bad_stateid;
+                       return nfserr_expired;
                *sopp = sop;
                goto check_replay;
        }
@@ -3304,6 +3432,7 @@ nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
        status = nfserr_bad_stateid;
        if (!is_delegation_stateid(stateid))
                goto out;
+       status = nfserr_expired;
        dp = find_delegation_stateid(inode, stateid);
        if (!dp)
                goto out;