+static void
+gen_callback(struct nfs4_client *clp, struct nfsd4_setclientid *se, u32 scopeid)
+{
+ struct nfs4_cb_conn *cb = &clp->cl_cb_conn;
+ unsigned short expected_family;
+
+ /* Currently, we only support tcp and tcp6 for the callback channel */
+ if (se->se_callback_netid_len == 3 &&
+ !memcmp(se->se_callback_netid_val, "tcp", 3))
+ expected_family = AF_INET;
+ else if (se->se_callback_netid_len == 4 &&
+ !memcmp(se->se_callback_netid_val, "tcp6", 4))
+ expected_family = AF_INET6;
+ else
+ goto out_err;
+
+ cb->cb_addrlen = rpc_uaddr2sockaddr(se->se_callback_addr_val,
+ se->se_callback_addr_len,
+ (struct sockaddr *) &cb->cb_addr,
+ sizeof(cb->cb_addr));
+
+ if (!cb->cb_addrlen || cb->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;
+
+ cb->cb_minorversion = 0;
+ cb->cb_prog = se->se_callback_prog;
+ cb->cb_ident = se->se_callback_ident;
+ return;
+out_err:
+ cb->cb_addr.ss_family = AF_UNSPEC;
+ cb->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);
+
+ return;
+}
+
+/*
+ * Cache a reply. nfsd4_check_drc_limit() has bounded the cache size.
+ */
+void
+nfsd4_store_cache_entry(struct nfsd4_compoundres *resp)
+{
+ struct nfsd4_slot *slot = resp->cstate.slot;
+ unsigned int base;
+
+ dprintk("--> %s slot %p\n", __func__, slot);
+
+ slot->sl_opcnt = resp->opcnt;
+ slot->sl_status = resp->cstate.status;
+
+ if (nfsd4_not_cached(resp)) {
+ slot->sl_datalen = 0;
+ return;
+ }
+ slot->sl_datalen = (char *)resp->p - (char *)resp->cstate.datap;
+ base = (char *)resp->cstate.datap -
+ (char *)resp->xbuf->head[0].iov_base;
+ if (read_bytes_from_xdr_buf(resp->xbuf, base, slot->sl_data,
+ slot->sl_datalen))
+ WARN("%s: sessions DRC could not cache compound\n", __func__);
+ return;
+}
+
+/*
+ * Encode the replay sequence operation from the slot values.
+ * If cachethis is FALSE encode the uncached rep error on the next
+ * operation which sets resp->p and increments resp->opcnt for
+ * nfs4svc_encode_compoundres.
+ *
+ */
+static __be32
+nfsd4_enc_sequence_replay(struct nfsd4_compoundargs *args,
+ struct nfsd4_compoundres *resp)
+{
+ struct nfsd4_op *op;
+ struct nfsd4_slot *slot = resp->cstate.slot;
+
+ dprintk("--> %s resp->opcnt %d cachethis %u \n", __func__,
+ resp->opcnt, resp->cstate.slot->sl_cachethis);
+
+ /* Encode the replayed sequence operation */
+ op = &args->ops[resp->opcnt - 1];
+ nfsd4_encode_operation(resp, op);
+
+ /* Return nfserr_retry_uncached_rep in next operation. */
+ if (args->opcnt > 1 && slot->sl_cachethis == 0) {
+ op = &args->ops[resp->opcnt++];
+ op->status = nfserr_retry_uncached_rep;
+ nfsd4_encode_operation(resp, op);
+ }
+ return op->status;
+}
+
+/*
+ * The sequence operation is not cached because we can use the slot and
+ * session values.
+ */
+__be32
+nfsd4_replay_cache_entry(struct nfsd4_compoundres *resp,
+ struct nfsd4_sequence *seq)
+{
+ struct nfsd4_slot *slot = resp->cstate.slot;
+ __be32 status;
+
+ dprintk("--> %s slot %p\n", __func__, slot);
+
+ /* Either returns 0 or nfserr_retry_uncached */
+ status = nfsd4_enc_sequence_replay(resp->rqstp->rq_argp, resp);
+ if (status == nfserr_retry_uncached_rep)
+ return status;
+
+ /* The sequence operation has been encoded, cstate->datap set. */
+ memcpy(resp->cstate.datap, slot->sl_data, slot->sl_datalen);
+
+ resp->opcnt = slot->sl_opcnt;
+ resp->p = resp->cstate.datap + XDR_QUADLEN(slot->sl_datalen);
+ status = slot->sl_status;
+
+ return status;
+}
+
+/*
+ * Set the exchange_id flags returned by the server.
+ */
+static void
+nfsd4_set_ex_flags(struct nfs4_client *new, struct nfsd4_exchange_id *clid)
+{
+ /* pNFS is not supported */
+ new->cl_exchange_flags |= EXCHGID4_FLAG_USE_NON_PNFS;
+
+ /* Referrals are supported, Migration is not. */
+ new->cl_exchange_flags |= EXCHGID4_FLAG_SUPP_MOVED_REFER;
+
+ /* set the wire flags to return to client. */
+ clid->flags = new->cl_exchange_flags;
+}
+
+__be32
+nfsd4_exchange_id(struct svc_rqst *rqstp,
+ struct nfsd4_compound_state *cstate,
+ struct nfsd4_exchange_id *exid)
+{
+ struct nfs4_client *unconf, *conf, *new;
+ int status;
+ unsigned int strhashval;
+ char dname[HEXDIR_LEN];
+ char addr_str[INET6_ADDRSTRLEN];
+ nfs4_verifier verf = exid->verifier;
+ struct sockaddr *sa = svc_addr(rqstp);
+
+ rpc_ntop(sa, addr_str, sizeof(addr_str));
+ dprintk("%s rqstp=%p exid=%p clname.len=%u clname.data=%p "
+ "ip_addr=%s flags %x, spa_how %d\n",
+ __func__, rqstp, exid, exid->clname.len, exid->clname.data,
+ addr_str, exid->flags, exid->spa_how);
+
+ if (!check_name(exid->clname) || (exid->flags & ~EXCHGID4_FLAG_MASK_A))
+ return nfserr_inval;
+
+ /* Currently only support SP4_NONE */
+ switch (exid->spa_how) {
+ case SP4_NONE:
+ break;
+ case SP4_SSV:
+ return nfserr_encr_alg_unsupp;
+ default:
+ BUG(); /* checked by xdr code */
+ case SP4_MACH_CRED:
+ return nfserr_serverfault; /* no excuse :-/ */
+ }
+
+ status = nfs4_make_rec_clidname(dname, &exid->clname);
+
+ if (status)
+ goto error;
+
+ strhashval = clientstr_hashval(dname);
+
+ nfs4_lock_state();
+ status = nfs_ok;
+
+ conf = find_confirmed_client_by_str(dname, strhashval, true);
+ if (conf) {
+ if (!same_verf(&verf, &conf->cl_verifier)) {
+ /* 18.35.4 case 8 */
+ if (exid->flags & EXCHGID4_FLAG_UPD_CONFIRMED_REC_A) {
+ status = nfserr_not_same;
+ goto out;
+ }
+ /* Client reboot: destroy old state */
+ expire_client(conf);
+ goto out_new;
+ }
+ if (!same_creds(&conf->cl_cred, &rqstp->rq_cred)) {
+ /* 18.35.4 case 9 */
+ if (exid->flags & EXCHGID4_FLAG_UPD_CONFIRMED_REC_A) {
+ status = nfserr_perm;
+ goto out;
+ }
+ expire_client(conf);
+ goto out_new;
+ }
+ /*
+ * Set bit when the owner id and verifier map to an already
+ * confirmed client id (18.35.3).
+ */
+ exid->flags |= EXCHGID4_FLAG_CONFIRMED_R;
+
+ /*
+ * Falling into 18.35.4 case 2, possible router replay.
+ * Leave confirmed record intact and return same result.
+ */
+ copy_verf(conf, &verf);
+ new = conf;
+ goto out_copy;
+ }
+
+ /* 18.35.4 case 7 */
+ if (exid->flags & EXCHGID4_FLAG_UPD_CONFIRMED_REC_A) {
+ status = nfserr_noent;
+ goto out;
+ }
+
+ unconf = find_unconfirmed_client_by_str(dname, strhashval, true);
+ if (unconf) {
+ /*
+ * Possible retry or client restart. Per 18.35.4 case 4,
+ * a new unconfirmed record should be generated regardless
+ * of whether any properties have changed.
+ */
+ expire_client(unconf);
+ }
+
+out_new:
+ /* Normal case */
+ new = create_client(exid->clname, dname, rqstp, &verf);
+ if (new == NULL) {
+ status = nfserr_serverfault;
+ goto out;
+ }
+
+ gen_clid(new);
+ add_to_unconfirmed(new, strhashval);
+out_copy:
+ exid->clientid.cl_boot = new->cl_clientid.cl_boot;
+ exid->clientid.cl_id = new->cl_clientid.cl_id;
+
+ exid->seqid = 1;
+ nfsd4_set_ex_flags(new, exid);
+
+ dprintk("nfsd4_exchange_id seqid %d flags %x\n",
+ new->cl_cs_slot.sl_seqid, new->cl_exchange_flags);
+ status = nfs_ok;
+
+out:
+ nfs4_unlock_state();
+error:
+ dprintk("nfsd4_exchange_id returns %d\n", ntohl(status));
+ return status;
+}
+
+static int
+check_slot_seqid(u32 seqid, u32 slot_seqid, int slot_inuse)
+{
+ dprintk("%s enter. seqid %d slot_seqid %d\n", __func__, seqid,
+ slot_seqid);
+
+ /* The slot is in use, and no response has been sent. */
+ if (slot_inuse) {
+ if (seqid == slot_seqid)
+ return nfserr_jukebox;
+ else
+ return nfserr_seq_misordered;
+ }
+ /* Normal */
+ if (likely(seqid == slot_seqid + 1))
+ return nfs_ok;
+ /* Replay */
+ if (seqid == slot_seqid)
+ return nfserr_replay_cache;
+ /* Wraparound */
+ if (seqid == 1 && (slot_seqid + 1) == 0)
+ return nfs_ok;
+ /* Misordered replay or misordered new request */
+ return nfserr_seq_misordered;
+}
+
+/*
+ * Cache the create session result into the create session single DRC
+ * slot cache by saving the xdr structure. sl_seqid has been set.
+ * Do this for solo or embedded create session operations.
+ */
+static void
+nfsd4_cache_create_session(struct nfsd4_create_session *cr_ses,
+ struct nfsd4_clid_slot *slot, int nfserr)
+{
+ slot->sl_status = nfserr;
+ memcpy(&slot->sl_cr_ses, cr_ses, sizeof(*cr_ses));
+}
+
+static __be32
+nfsd4_replay_create_session(struct nfsd4_create_session *cr_ses,
+ struct nfsd4_clid_slot *slot)
+{
+ memcpy(cr_ses, &slot->sl_cr_ses, sizeof(*cr_ses));
+ return slot->sl_status;
+}
+
+__be32
+nfsd4_create_session(struct svc_rqst *rqstp,
+ struct nfsd4_compound_state *cstate,
+ struct nfsd4_create_session *cr_ses)
+{
+ struct sockaddr *sa = svc_addr(rqstp);
+ struct nfs4_client *conf, *unconf;
+ struct nfsd4_clid_slot *cs_slot = NULL;
+ int status = 0;
+
+ nfs4_lock_state();
+ unconf = find_unconfirmed_client(&cr_ses->clientid);
+ conf = find_confirmed_client(&cr_ses->clientid);
+
+ if (conf) {
+ cs_slot = &conf->cl_cs_slot;
+ status = check_slot_seqid(cr_ses->seqid, cs_slot->sl_seqid, 0);
+ if (status == nfserr_replay_cache) {
+ dprintk("Got a create_session replay! seqid= %d\n",
+ cs_slot->sl_seqid);
+ /* Return the cached reply status */
+ status = nfsd4_replay_create_session(cr_ses, cs_slot);
+ goto out;
+ } else if (cr_ses->seqid != cs_slot->sl_seqid + 1) {
+ status = nfserr_seq_misordered;
+ dprintk("Sequence misordered!\n");
+ dprintk("Expected seqid= %d but got seqid= %d\n",
+ 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)) {
+ status = nfserr_clid_inuse;
+ goto out;
+ }
+
+ cs_slot = &unconf->cl_cs_slot;
+ status = check_slot_seqid(cr_ses->seqid, cs_slot->sl_seqid, 0);
+ if (status) {
+ /* an unconfirmed replay returns misordered */
+ status = nfserr_seq_misordered;
+ goto out_cache;
+ }
+
+ cs_slot->sl_seqid++; /* from 0 to 1 */
+ move_to_confirmed(unconf);
+
+ if (cr_ses->flags & SESSION4_BACK_CHAN) {
+ unconf->cl_cb_xprt = rqstp->rq_xprt;
+ svc_xprt_get(unconf->cl_cb_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);
+ }
+ conf = unconf;
+ } else {
+ status = nfserr_stale_clientid;
+ goto out;
+ }
+
+ /*
+ * 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)
+ goto out;
+
+ memcpy(cr_ses->sessionid.data, conf->cl_sessionid.data,
+ NFS4_MAX_SESSIONID_LEN);
+ cr_ses->seqid = cs_slot->sl_seqid;