+ if (n < 0)
+ n = 0;
+ n = (n * 10) + (c - '0');
+ if (n > 255) {
+ n = -1;
+ break;
+ }
+ }
+ *lenp = len;
+ *addrp = p;
+ return n;
+}
+
+/* parse and set the setclientid ipv4 callback address */
+static int
+parse_ipv4(unsigned int addr_len, char *addr_val, unsigned int *cbaddrp, unsigned short *cbportp)
+{
+ int temp = 0;
+ u32 cbaddr = 0;
+ u16 cbport = 0;
+ u32 addrlen = addr_len;
+ char *addr = addr_val;
+ int i, shift;
+
+ /* ipaddress */
+ shift = 24;
+ for(i = 4; i > 0 ; i--) {
+ if ((temp = parse_octet(&addrlen, &addr)) < 0) {
+ return 0;
+ }
+ cbaddr |= (temp << shift);
+ if (shift > 0)
+ shift -= 8;
+ }
+ *cbaddrp = cbaddr;
+
+ /* port */
+ shift = 8;
+ for(i = 2; i > 0 ; i--) {
+ if ((temp = parse_octet(&addrlen, &addr)) < 0) {
+ return 0;
+ }
+ cbport |= (temp << shift);
+ if (shift > 0)
+ shift -= 8;
+ }
+ *cbportp = cbport;
+ return 1;
+}
+
+static void
+gen_callback(struct nfs4_client *clp, struct nfsd4_setclientid *se)
+{
+ struct nfs4_callback *cb = &clp->cl_callback;
+
+ /* Currently, we only support tcp for the callback channel */
+ if ((se->se_callback_netid_len != 3) || memcmp((char *)se->se_callback_netid_val, "tcp", 3))
+ goto out_err;
+
+ if ( !(parse_ipv4(se->se_callback_addr_len, se->se_callback_addr_val,
+ &cb->cb_addr, &cb->cb_port)))
+ goto out_err;
+ cb->cb_prog = se->se_callback_prog;
+ cb->cb_ident = se->se_callback_ident;
+ return;
+out_err:
+ 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;
+}
+
+void
+nfsd4_set_statp(struct svc_rqst *rqstp, __be32 *statp)
+{
+ struct nfsd4_compoundres *resp = rqstp->rq_resp;
+
+ resp->cstate.statp = statp;
+}
+
+/*
+ * Dereference the result pages.
+ */
+static void
+nfsd4_release_respages(struct page **respages, short resused)
+{
+ int i;
+
+ dprintk("--> %s\n", __func__);
+ for (i = 0; i < resused; i++) {
+ if (!respages[i])
+ continue;
+ put_page(respages[i]);
+ respages[i] = NULL;
+ }
+}
+
+static void
+nfsd4_copy_pages(struct page **topages, struct page **frompages, short count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ topages[i] = frompages[i];
+ if (!topages[i])
+ continue;
+ get_page(topages[i]);
+ }
+}
+
+/*
+ * Cache the reply pages up to NFSD_PAGES_PER_SLOT + 1, clearing the previous
+ * pages. We add a page to NFSD_PAGES_PER_SLOT for the case where the total
+ * length of the XDR response is less than se_fmaxresp_cached
+ * (NFSD_PAGES_PER_SLOT * PAGE_SIZE) but the xdr_buf pages is used for a
+ * of the reply (e.g. readdir).
+ *
+ * Store the base and length of the rq_req.head[0] page
+ * of the NFSv4.1 data, just past the rpc header.
+ */
+void
+nfsd4_store_cache_entry(struct nfsd4_compoundres *resp)
+{
+ struct nfsd4_cache_entry *entry = &resp->cstate.slot->sl_cache_entry;
+ struct svc_rqst *rqstp = resp->rqstp;
+ struct nfsd4_compoundargs *args = rqstp->rq_argp;
+ struct nfsd4_op *op = &args->ops[resp->opcnt];
+ struct kvec *resv = &rqstp->rq_res.head[0];
+
+ dprintk("--> %s entry %p\n", __func__, entry);
+
+ /* Don't cache a failed OP_SEQUENCE. */
+ if (resp->opcnt == 1 && op->opnum == OP_SEQUENCE && resp->cstate.status)
+ return;
+
+ nfsd4_release_respages(entry->ce_respages, entry->ce_resused);
+ entry->ce_opcnt = resp->opcnt;
+ entry->ce_status = resp->cstate.status;
+
+ /*
+ * Don't need a page to cache just the sequence operation - the slot
+ * does this for us!
+ */
+
+ if (nfsd4_not_cached(resp)) {
+ entry->ce_resused = 0;
+ entry->ce_rpchdrlen = 0;
+ dprintk("%s Just cache SEQUENCE. ce_cachethis %d\n", __func__,
+ resp->cstate.slot->sl_cache_entry.ce_cachethis);
+ return;
+ }
+ entry->ce_resused = rqstp->rq_resused;
+ if (entry->ce_resused > NFSD_PAGES_PER_SLOT + 1)
+ entry->ce_resused = NFSD_PAGES_PER_SLOT + 1;
+ nfsd4_copy_pages(entry->ce_respages, rqstp->rq_respages,
+ entry->ce_resused);
+ entry->ce_datav.iov_base = resp->cstate.statp;
+ entry->ce_datav.iov_len = resv->iov_len - ((char *)resp->cstate.statp -
+ (char *)page_address(rqstp->rq_respages[0]));
+ /* Current request rpc header length*/
+ entry->ce_rpchdrlen = (char *)resp->cstate.statp -
+ (char *)page_address(rqstp->rq_respages[0]);
+}
+
+/*
+ * We keep the rpc header, but take the nfs reply from the replycache.
+ */
+static int
+nfsd41_copy_replay_data(struct nfsd4_compoundres *resp,
+ struct nfsd4_cache_entry *entry)
+{
+ struct svc_rqst *rqstp = resp->rqstp;
+ struct kvec *resv = &resp->rqstp->rq_res.head[0];
+ int len;
+
+ /* Current request rpc header length*/
+ len = (char *)resp->cstate.statp -
+ (char *)page_address(rqstp->rq_respages[0]);
+ if (entry->ce_datav.iov_len + len > PAGE_SIZE) {
+ dprintk("%s v41 cached reply too large (%Zd).\n", __func__,
+ entry->ce_datav.iov_len);
+ return 0;
+ }
+ /* copy the cached reply nfsd data past the current rpc header */
+ memcpy((char *)resv->iov_base + len, entry->ce_datav.iov_base,
+ entry->ce_datav.iov_len);
+ resv->iov_len = len + entry->ce_datav.iov_len;
+ return 1;
+}
+
+/*
+ * Keep the first page of the replay. Copy the NFSv4.1 data from the first
+ * cached page. Replace any futher replay pages from the cache.
+ */
+__be32
+nfsd4_replay_cache_entry(struct nfsd4_compoundres *resp,
+ struct nfsd4_sequence *seq)
+{
+ struct nfsd4_cache_entry *entry = &resp->cstate.slot->sl_cache_entry;
+ __be32 status;
+
+ dprintk("--> %s entry %p\n", __func__, entry);
+
+ /*
+ * If this is just the sequence operation, we did not keep
+ * a page in the cache entry because we can just use the
+ * slot info stored in struct nfsd4_sequence that was checked
+ * against the slot in nfsd4_sequence().
+ *
+ * This occurs when seq->cachethis is FALSE, or when the client
+ * session inactivity timer fires and a solo sequence operation
+ * is sent (lease renewal).
+ */
+ if (seq && nfsd4_not_cached(resp)) {
+ seq->maxslots = resp->cstate.session->se_fnumslots;
+ return nfs_ok;
+ }
+
+ if (!nfsd41_copy_replay_data(resp, entry)) {
+ /*
+ * Not enough room to use the replay rpc header, send the
+ * cached header. Release all the allocated result pages.
+ */
+ svc_free_res_pages(resp->rqstp);
+ nfsd4_copy_pages(resp->rqstp->rq_respages, entry->ce_respages,
+ entry->ce_resused);
+ } else {
+ /* Release all but the first allocated result page */
+
+ resp->rqstp->rq_resused--;
+ svc_free_res_pages(resp->rqstp);
+
+ nfsd4_copy_pages(&resp->rqstp->rq_respages[1],
+ &entry->ce_respages[1],
+ entry->ce_resused - 1);
+ }
+
+ resp->rqstp->rq_resused = entry->ce_resused;
+ resp->opcnt = entry->ce_opcnt;
+ resp->cstate.iovlen = entry->ce_datav.iov_len + entry->ce_rpchdrlen;
+ status = entry->ce_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];
+ nfs4_verifier verf = exid->verifier;
+ u32 ip_addr = svc_addr_in(rqstp)->sin_addr.s_addr;
+
+ dprintk("%s rqstp=%p exid=%p clname.len=%u clname.data=%p "
+ " ip_addr=%u flags %x, spa_how %d\n",
+ __func__, rqstp, exid, exid->clname.len, exid->clname.data,
+ ip_addr, 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;
+ }
+ if (ip_addr != conf->cl_addr &&
+ !(exid->flags & EXCHGID4_FLAG_UPD_CONFIRMED_REC_A)) {
+ /* Client collision. 18.35.4 case 3 */
+ status = nfserr_clid_inuse;
+ goto out;
+ }
+ /*
+ * 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;
+ } else {
+ /* 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);
+ if (new == NULL) {
+ status = nfserr_resource;
+ goto out;
+ }
+
+ copy_verf(new, &verf);
+ copy_cred(&new->cl_cred, &rqstp->rq_cred);
+ new->cl_addr = ip_addr;
+ gen_clid(new);
+ gen_confirm(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;
+
+ new->cl_slot.sl_seqid = 0;
+ exid->seqid = 1;
+ nfsd4_set_ex_flags(new, exid);
+
+ dprintk("nfsd4_exchange_id seqid %d flags %x\n",
+ new->cl_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, struct nfsd4_slot *slot)
+{
+ dprintk("%s enter. seqid %d slot->sl_seqid %d\n", __func__, seqid,
+ slot->sl_seqid);
+
+ /* The slot is in use, and no response has been sent. */
+ if (slot->sl_inuse) {
+ if (seqid == slot->sl_seqid)
+ return nfserr_jukebox;
+ else
+ return nfserr_seq_misordered;
+ }
+ /* Normal */
+ if (likely(seqid == slot->sl_seqid + 1))
+ return nfs_ok;
+ /* Replay */
+ if (seqid == slot->sl_seqid)
+ return nfserr_replay_cache;
+ /* Wraparound */
+ if (seqid == 1 && (slot->sl_seqid + 1) == 0)
+ return nfs_ok;
+ /* Misordered replay or misordered new request */
+ return nfserr_seq_misordered;
+}
+
+__be32
+nfsd4_create_session(struct svc_rqst *rqstp,
+ struct nfsd4_compound_state *cstate,
+ struct nfsd4_create_session *cr_ses)
+{
+ u32 ip_addr = svc_addr_in(rqstp)->sin_addr.s_addr;
+ struct nfsd4_compoundres *resp = rqstp->rq_resp;
+ struct nfs4_client *conf, *unconf;
+ struct nfsd4_slot *slot = NULL;
+ int status = 0;
+
+ nfs4_lock_state();
+ unconf = find_unconfirmed_client(&cr_ses->clientid);
+ conf = find_confirmed_client(&cr_ses->clientid);
+
+ if (conf) {
+ slot = &conf->cl_slot;
+ status = check_slot_seqid(cr_ses->seqid, slot);
+ if (status == nfserr_replay_cache) {
+ dprintk("Got a create_session replay! seqid= %d\n",
+ slot->sl_seqid);
+ cstate->slot = slot;
+ cstate->status = status;
+ /* Return the cached reply status */
+ status = nfsd4_replay_cache_entry(resp, NULL);
+ goto out;
+ } else if (cr_ses->seqid != conf->cl_slot.sl_seqid + 1) {
+ status = nfserr_seq_misordered;
+ dprintk("Sequence misordered!\n");
+ dprintk("Expected seqid= %d but got seqid= %d\n",
+ slot->sl_seqid, cr_ses->seqid);
+ goto out;
+ }
+ conf->cl_slot.sl_seqid++;
+ } else if (unconf) {
+ if (!same_creds(&unconf->cl_cred, &rqstp->rq_cred) ||
+ (ip_addr != unconf->cl_addr)) {
+ status = nfserr_clid_inuse;
+ goto out;