summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/fs/nfs/nfs4x_srv.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/fs/nfs/nfs4x_srv.c')
-rw-r--r--usr/src/uts/common/fs/nfs/nfs4x_srv.c1250
1 files changed, 1250 insertions, 0 deletions
diff --git a/usr/src/uts/common/fs/nfs/nfs4x_srv.c b/usr/src/uts/common/fs/nfs/nfs4x_srv.c
new file mode 100644
index 0000000000..854a8e1abe
--- /dev/null
+++ b/usr/src/uts/common/fs/nfs/nfs4x_srv.c
@@ -0,0 +1,1250 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2017 RackTop Systems.
+ */
+
+#include <rpc/types.h>
+#include <rpc/auth.h>
+#include <rpc/rpcsec_gss.h>
+#include <sys/sdt.h>
+#include <sys/disp.h>
+#include <nfs/nfs.h>
+#include <nfs/nfs4.h>
+#include <sys/systeminfo.h>
+
+/* Helpers */
+
+/* Principal handling routines */
+/* returns 0 if no match; or 1 for a match */
+int
+rfs4_cmp_cred_set(cred_set_t *p, struct compound_state *cs)
+{
+ int rc = 0;
+ rpc_gss_principal_t recp; /* cached clnt princ */
+ rpc_gss_principal_t ibrp; /* inbound req princ */
+
+
+ if (p->cp_cr == NULL)
+ return (rc); /* nothing to compare with */
+
+ if (p->cp_aflavor != cs->req->rq_cred.oa_flavor)
+ return (rc);
+
+ if (p->cp_secmod != cs->nfsflavor)
+ return (rc);
+
+ if (crcmp(p->cp_cr, cs->basecr))
+ return (rc);
+
+ switch (p->cp_aflavor) {
+ case AUTH_DES:
+ rc = (strcmp(p->cp_princ, cs->principal) == 0);
+ break;
+
+ case RPCSEC_GSS:
+ recp = (rpc_gss_principal_t)p->cp_princ;
+ ibrp = (rpc_gss_principal_t)cs->principal;
+
+ if (recp->len != ibrp->len)
+ break;
+ rc = (bcmp(recp->name, ibrp->name, ibrp->len) == 0);
+ break;
+
+ case AUTH_SYS:
+ case AUTH_NONE:
+ default:
+ rc = 1;
+ break;
+ }
+ return (rc);
+}
+
+static rpc_gss_principal_t
+rfs4_dup_princ(rpc_gss_principal_t ppl)
+{
+ rpc_gss_principal_t pdup;
+ int len;
+
+ if (ppl == NULL)
+ return (NULL);
+
+ len = sizeof (int) + ppl->len;
+ pdup = (rpc_gss_principal_t)kmem_alloc(len, KM_SLEEP);
+ bcopy(ppl, pdup, len);
+ return (pdup);
+}
+
+void
+rfs4_set_cred_set(cred_set_t *p, struct compound_state *cs)
+{
+ ASSERT(p->cp_cr == NULL);
+
+ p->cp_cr = crdup(cs->basecr);
+ p->cp_aflavor = cs->req->rq_cred.oa_flavor;
+ p->cp_secmod = cs->nfsflavor; /* secmod != flavor for RPCSEC_GSS */
+
+ /*
+ * Set principal as per security flavor
+ */
+ switch (p->cp_aflavor) {
+ case AUTH_DES:
+ p->cp_princ = strdup(cs->principal);
+ break;
+
+ case RPCSEC_GSS:
+ p->cp_princ =
+ (caddr_t)rfs4_dup_princ((rpc_gss_principal_t)cs->principal);
+ break;
+
+ case AUTH_SYS:
+ case AUTH_NONE:
+ default:
+ break;
+ }
+}
+
+void
+rfs4_free_cred_set(cred_set_t *p)
+{
+ rpc_gss_principal_t ppl;
+
+ if (p->cp_cr == NULL)
+ return;
+
+ switch (p->cp_aflavor) {
+ case AUTH_DES:
+ kmem_free(p->cp_princ, strlen(p->cp_princ) + 1);
+ break;
+
+ case RPCSEC_GSS:
+ ppl = (rpc_gss_principal_t)p->cp_princ;
+ kmem_free(ppl, ppl->len + sizeof (int));
+ break;
+ }
+
+ crfree(p->cp_cr);
+ p->cp_cr = NULL;
+}
+
+/* principal end */
+
+bool_t
+nfs_clid4_cmp(nfs_client_id4 *s1, nfs_client_id4 *s2)
+{
+ if (s1->verifier != s2->verifier)
+ return (FALSE);
+ if (s1->id_len != s2->id_len)
+ return (FALSE);
+ if (bcmp(s1->id_val, s2->id_val, s2->id_len))
+ return (FALSE);
+ return (TRUE);
+}
+
+/*
+ * Rudimentary server implementation (XXX - for now)
+ */
+void
+rfs4x_get_server_impl_id(EXCHANGE_ID4resok *resp)
+{
+ char *sol_impl = "illumos NFSv4.1 Server Implementation";
+ char *sol_idom = "nfsv41.ietf.org";
+ void *p;
+ uint_t len = 0;
+ nfs_impl_id4 *nip;
+
+ resp->eir_server_impl_id.eir_server_impl_id_len = 1;
+ nip = kmem_zalloc(sizeof (nfs_impl_id4), KM_SLEEP);
+ resp->eir_server_impl_id.eir_server_impl_id_val = nip;
+
+ /* Domain */
+ nip->nii_domain.utf8string_len = len = strlen(sol_idom);
+ p = kmem_zalloc(len * sizeof (char), KM_SLEEP);
+ nip->nii_domain.utf8string_val = p;
+ bcopy(sol_idom, p, len);
+
+ /* Implementation */
+ nip->nii_name.utf8string_len = len = strlen(sol_impl);
+ p = kmem_zalloc(len * sizeof (char), KM_SLEEP);
+ nip->nii_name.utf8string_val = p;
+ bcopy(sol_impl, p, len);
+
+ /* Time is zero for now */
+}
+
+/*
+ * Compute the "use bits", i.e. the flags specifying the permissible
+ * regular, MDS, and data server ops for the returned clientid.
+ *
+ * The minorversion1 specification allows a server implementor two
+ * alternatives: allow PNFS_MDS and PNFS_DS on the same clientid, or
+ * force the client to create separate clientids to distinguish
+ * MDS versus DS operations.
+ *
+ * Our design distinguishes operations based upon filehandle, and thus
+ * there is no reason to force the client to create separate clientids.
+ * Thus, we give the client as much as possible, while keeping the result
+ * within the allowed combinations as specified in the specification.
+ *
+ * Our constraints are: use a subset of the client's request, unless
+ * the client requested nothing, in which case we may return any
+ * legal combination; and, the combination of NON_PNFS and PNFS_MDS
+ * may not both be set in the results. These constraints are reflected
+ * in the ASSERT()s at the end.
+ */
+
+static uint32_t
+compute_use_pnfs_flags(uint32_t request)
+{
+ uint32_t rc;
+
+ /* Start with the client's initial request */
+ rc = request & EXCHGID4_FLAG_MASK_PNFS;
+
+ /* If the client requested nothing, return the most permissive. */
+ if (rc == 0) {
+ rc = EXCHGID4_FLAG_USE_NON_PNFS;
+ goto done;
+ }
+
+ /* Don't permit the illegal combination of MDS and NON_PNFS */
+ if ((rc &
+ (EXCHGID4_FLAG_USE_NON_PNFS | EXCHGID4_FLAG_USE_PNFS_MDS)) ==
+ (EXCHGID4_FLAG_USE_NON_PNFS | EXCHGID4_FLAG_USE_PNFS_MDS))
+ rc &= ~EXCHGID4_FLAG_USE_PNFS_MDS;
+done:
+ ASSERT(((request & EXCHGID4_FLAG_MASK_PNFS) == 0) ||
+ ((rc & ~(request & EXCHGID4_FLAG_MASK_PNFS)) == 0));
+ ASSERT((rc & (EXCHGID4_FLAG_USE_NON_PNFS | EXCHGID4_FLAG_USE_PNFS_MDS))
+ != (EXCHGID4_FLAG_USE_NON_PNFS | EXCHGID4_FLAG_USE_PNFS_MDS));
+ ASSERT(rc != 0);
+
+ return (rc);
+}
+
+static void
+rfs4x_set_trunkinfo(EXCHANGE_ID4resok *rok)
+{
+ const char *nodename = uts_nodename();
+ unsigned int nd_len = strlen(nodename);
+ unsigned int hw_len = strlen(hw_serial);
+ unsigned id_len = nd_len + 1 + hw_len;
+ char *s = kmem_alloc(id_len, KM_SLEEP);
+ server_owner4 *so = &rok->eir_server_owner;
+ struct eir_server_scope *ss = &rok->eir_server_scope;
+
+ (void) memcpy(s, nodename, nd_len);
+ s[nd_len] = ' ';
+ (void) memcpy(s + nd_len + 1, hw_serial, hw_len);
+
+ so->so_major_id.so_major_id_len = id_len;
+ so->so_major_id.so_major_id_val = s;
+
+ ss->eir_server_scope_len = id_len;
+ ss->eir_server_scope_val = kmem_alloc(id_len, KM_SLEEP);
+ (void) memcpy(ss->eir_server_scope_val, s, id_len);
+
+ rok->eir_server_owner.so_minor_id = 0;
+}
+
+static bool_t
+client_has_state_locked(rfs4_client_t *cp)
+{
+ if (list_head(&cp->rc_sessions) != NULL ||
+ list_head(&cp->rc_openownerlist) != NULL)
+ return (TRUE);
+ else
+ return (FALSE);
+}
+
+/* OPERATIONS */
+
+/* EXCHANGE_ID */
+void
+rfs4x_op_exchange_id(nfs_argop4 *argop, nfs_resop4 *resop,
+ struct svc_req *req, compound_state_t *cs)
+{
+ EXCHANGE_ID4args *args = &argop->nfs_argop4_u.opexchange_id;
+ EXCHANGE_ID4res *resp = &resop->nfs_resop4_u.opexchange_id;
+ EXCHANGE_ID4resok *rok = &resp->EXCHANGE_ID4res_u.eir_resok4;
+ rfs4_client_t *cp, *conf;
+ bool_t update, create;
+ client_owner4 *cop;
+ nfs_client_id4 cid; /* cip */
+ nfsstat4 status = NFS4_OK;
+ nfs4_srv_t *nsrv4;
+
+ DTRACE_NFSV4_2(op__exchange__id__start,
+ struct compound_state *, cs,
+ EXCHANGE_ID4args *, args);
+
+ /*
+ * EXCHANGE_ID's may be preceded by SEQUENCE
+ *
+ * Check that eia_flags only has "valid" spec bits
+ * and that no 'eir_flag' ONLY bits are specified.
+ */
+ if (args->eia_flags & ~EXID4_FLAG_MASK) {
+ status = NFS4ERR_INVAL;
+ goto err;
+ }
+
+ update = (args->eia_flags & EXCHGID4_FLAG_UPD_CONFIRMED_REC_A);
+ cop = &args->eia_clientowner;
+ conf = NULL;
+
+ cid.verifier = cop->co_verifier;
+ cid.id_len = cop->co_ownerid.co_ownerid_len;
+ cid.id_val = cop->co_ownerid.co_ownerid_val;
+ cid.cl_addr = (struct sockaddr *)svc_getrpccaller(req->rq_xprt)->buf;
+
+ /*
+ * Refer to Section 18.35.4
+ */
+again:
+ create = TRUE;
+ cp = rfs4_findclient(&cid, &create, conf);
+ ASSERT(cp != NULL);
+
+ if (conf) {
+ rfs4_dbe_lock(cp->rc_dbe);
+ if (cp->rc_cp_confirmed == NULL)
+ cp->rc_cp_confirmed = conf;
+ else
+ rfs4_client_rele(conf);
+ rfs4_dbe_unlock(cp->rc_dbe);
+ conf = NULL;
+ }
+
+ if (create) {
+ /* Record just created */
+ if (!update) {
+ /* case 1 - utok */
+ rfs4_set_cred_set(&cp->rc_cr_set, cs);
+
+ rok->eir_clientid = cp->rc_clientid;
+ rok->eir_sequenceid = cp->rc_contrived.xi_sid;
+ goto out;
+ } else {
+ /* no record and trying to update */
+ status = NFS4ERR_NOENT;
+ goto err_out;
+ }
+ }
+
+ /* Record exists */
+
+ /* expired clients should be ignored and released */
+ if (rfs4_lease_expired(cp)) {
+ rfs4_client_close(cp);
+ update = FALSE;
+ goto again;
+ }
+
+ if (cp->rc_need_confirm) {
+ /* UNCONFIRMED */
+ if (!update) {
+ /* case 4 - utok */
+ rfs4_client_close(cp);
+
+ ASSERT(!update);
+ goto again;
+ } else {
+ /* case 7 - utok */
+ status = NFS4ERR_NOENT;
+ goto err_out;
+ }
+ }
+
+ /* record exists and confirmed */
+ if (!update) {
+ if (!rfs4_cmp_cred_set(&cp->rc_cr_set, cs)) {
+ /* case 3 */
+ /* lease is checked above */
+ rfs4_dbe_lock(cp->rc_dbe);
+ if (!client_has_state_locked(cp)) {
+ rfs4_dbe_unlock(cp->rc_dbe);
+
+ rfs4_client_close(cp);
+ ASSERT(!update);
+ goto again;
+ }
+ rfs4_dbe_unlock(cp->rc_dbe);
+
+ /*
+ * clid_in_use. old_client_ret has unexpired
+ * lease with state.
+ */
+ status = NFS4ERR_CLID_INUSE;
+ goto err_out;
+ } else if (cp->rc_nfs_client.verifier != cid.verifier) {
+ /* case 5: Client Restart */
+ /*
+ * Skip confirmed client record to allow confirmed
+ * and unconfirmed state at the same time. The number
+ * of states can collapse to one once the server
+ * receives an applicable CREATE_SESSION or EXCHANGE_ID.
+ */
+ ASSERT(conf == NULL);
+ conf = cp;
+ ASSERT(!update);
+ goto again;
+
+ } else if (nfs_clid4_cmp(&cp->rc_nfs_client, &cid)) {
+ /* case 2 - utok */
+ rok->eir_clientid = cp->rc_clientid;
+ rok->eir_sequenceid = cp->rc_contrived.xi_sid;
+ /* trickle down to "out" */
+
+ } else {
+ /* something is really wacky in srv state */
+ status = NFS4ERR_SERVERFAULT;
+ goto err_out;
+ }
+
+ } else { /* UPDATE */
+ if (cp->rc_nfs_client.verifier != cid.verifier) {
+ /* 18.35.4 case 8 */
+ status = NFS4ERR_NOT_SAME;
+ goto err_out;
+ }
+ if (!rfs4_cmp_cred_set(&cp->rc_cr_set, cs)) {
+ /* 18.35.4 case 9 */
+ status = NFS4ERR_PERM;
+ goto err_out;
+ }
+
+ /* case 6 - utok */
+ rok->eir_clientid = cp->rc_clientid;
+ rok->eir_sequenceid = cp->rc_contrived.xi_sid;
+ /* trickle down to "out" */
+ }
+out:
+ rok->eir_flags = 0;
+ if (resp->eir_status == NFS4_OK && !cp->rc_need_confirm)
+ rok->eir_flags |= EXCHGID4_FLAG_CONFIRMED_R;
+
+ /*
+ * State Protection Mojo
+ */
+ cp->rc_state_prot.sp_type = args->eia_state_protect.spa_how;
+ switch (cp->rc_state_prot.sp_type) {
+ case SP4_NONE:
+ break;
+
+ case SP4_MACH_CRED:
+ /* XXX - Some of Karen's secret sauce here... */
+ break;
+
+ case SP4_SSV:
+ /* XXX - ... and here */
+#define ssv_args eia_state_protect.state_protect4_a_u.spa_ssv_parms
+ if (args->ssv_args.ssp_ops.spo_must_allow & OP_EXCHANGE_ID) {
+ /*
+ * if the client ID was created specifying SP4_SSV
+ * state protection and EXCHANGE_ID as the one of
+ * the operations in spo_must_allow, then server MUST
+ * authorize EXCHANGE_IDs with the SSV principal in
+ * addition to the principal that created the client
+ * ID.
+ */
+ /* EMPTY */;
+ }
+ break;
+ }
+
+ /*
+ * Referrals supports
+ */
+ if (args->eia_flags & EXCHGID4_FLAG_SUPP_MOVED_REFER) {
+ rok->eir_flags |= EXCHGID4_FLAG_SUPP_MOVED_REFER;
+ }
+
+ /*
+ * Migration/Replication not (yet) supported
+ */
+ if (args->eia_flags & EXCHGID4_FLAG_SUPP_MOVED_MIGR)
+ rok->eir_flags &= ~EXCHGID4_FLAG_SUPP_MOVED_MIGR;
+
+ /*
+ * TODO: Add the appropriate "use_pnfs" flags.
+ */
+ rok->eir_flags |= compute_use_pnfs_flags(args->eia_flags);
+
+ /* force no state protection for now */
+ rok->eir_state_protect.spr_how = SP4_NONE;
+
+ /* Implementation specific mojo */
+ if (args->eia_client_impl_id.eia_client_impl_id_len != 0) {
+ /* EMPTY */;
+ }
+
+ nsrv4 = nfs4_get_srv();
+
+ /* XXX - jw - best guess */
+ rfs4_ss_clid(nsrv4, cp);
+
+ /* Server's implementation */
+ rfs4x_get_server_impl_id(rok);
+
+ /* compute trunking capabilities */
+ bzero(&rok->eir_server_scope, sizeof (rok->eir_server_scope));
+ bzero(&rok->eir_server_owner, sizeof (server_owner4));
+
+ /* Add trunk handling */
+ rfs4x_set_trunkinfo(rok);
+
+ /*
+ * XXX - jw - best guess
+ * Check to see if client can perform reclaims
+ */
+ rfs4_ss_chkclid(nsrv4, cp);
+
+err_out:
+ rfs4_client_rele(cp);
+err:
+ *cs->statusp = resp->eir_status = status;
+
+ DTRACE_NFSV4_2(op__exchange__id__done,
+ struct compound_state *, cs,
+ EXCHANGE_ID4res *, resp);
+}
+
+void
+rfs4x_exchange_id_free(nfs_resop4 *resop)
+{
+ EXCHANGE_ID4res *resp = &resop->nfs_resop4_u.opexchange_id;
+ EXCHANGE_ID4resok *rok = &resp->EXCHANGE_ID4res_u.eir_resok4;
+ struct server_owner4 *sop = &rok->eir_server_owner;
+ nfs_impl_id4 *nip;
+ int len = 0;
+
+ /* Server Owner: major */
+ if ((len = sop->so_major_id.so_major_id_len) != 0)
+ kmem_free(sop->so_major_id.so_major_id_val, len);
+
+ if ((nip = rok->eir_server_impl_id.eir_server_impl_id_val) != NULL) {
+ /* Immplementation */
+ len = nip->nii_name.utf8string_len;
+ kmem_free(nip->nii_name.utf8string_val, len * sizeof (char));
+
+ /* Domain */
+ len = nip->nii_domain.utf8string_len;
+ kmem_free(nip->nii_domain.utf8string_val, len * sizeof (char));
+
+ /* Server Impl */
+ kmem_free(nip, sizeof (nfs_impl_id4));
+ }
+}
+
+void
+rfs4x_op_create_session(nfs_argop4 *argop, nfs_resop4 *resop,
+ struct svc_req *req, compound_state_t *cs)
+{
+ CREATE_SESSION4args *args = &argop->nfs_argop4_u.opcreate_session;
+ CREATE_SESSION4res *resp = &resop->nfs_resop4_u.opcreate_session;
+ CREATE_SESSION4resok *rok = &resp->CREATE_SESSION4res_u.csr_resok4;
+ CREATE_SESSION4resok *crp;
+ rfs4_client_t *cp;
+ rfs4_session_t *sp;
+ session41_create_t sca;
+ sequenceid4 stseq;
+ sequenceid4 agseq;
+ nfsstat4 status = NFS4_OK;
+
+ DTRACE_NFSV4_2(op__create__session__start,
+ struct compound_state *, cs,
+ CREATE_SESSION4args*, args);
+
+ /*
+ * A CREATE_SESSION request can be prefixed by OP_SEQUENCE.
+ * In this case, the newly created session has no relation
+ * to the sessid used for the OP_SEQUENCE.
+ */
+
+ /*
+ * Find the clientid
+ */
+ cp = rfs4_findclient_by_id(args->csa_clientid, TRUE);
+ if (cp == NULL) {
+ status = NFS4ERR_STALE_CLIENTID;
+ goto out;
+ }
+
+ /*
+ * Make sure the lease is still valid.
+ */
+ if (rfs4_lease_expired(cp)) {
+ rfs4_client_close(cp);
+ status = NFS4ERR_STALE_CLIENTID;
+ goto out;
+ }
+
+ /*
+ * Sequenceid processing (handling replay's, etc)
+ */
+ agseq = args->csa_sequence;
+ stseq = cp->rc_contrived.xi_sid;
+ if (stseq == agseq + 1) {
+ /*
+ * If the previous sequenceid, then must be a replay of a
+ * previous CREATE_SESSION; return the cached result.
+ */
+ crp = (CREATE_SESSION4resok *)&cp->rc_contrived.cs_res;
+ status = cp->rc_contrived.cs_status;
+ rok->csr_sequence = agseq;
+ bcopy(crp->csr_sessionid, rok->csr_sessionid,
+ sizeof (sessionid4));
+ rok->csr_flags = crp->csr_flags;
+ rok->csr_fore_chan_attrs = crp->csr_fore_chan_attrs;
+ rok->csr_back_chan_attrs = crp->csr_back_chan_attrs;
+
+ rfs4_update_lease(cp);
+ rfs4_client_rele(cp);
+ goto out;
+ }
+
+ if (stseq != agseq) {
+ /*
+ * No way to differentiate MISORD_NEWREQ vs. MISORD_REPLAY,
+ * so anything else, we simply treat as SEQ_MISORDERED.
+ */
+ status = NFS4ERR_SEQ_MISORDERED;
+ rfs4_client_rele(cp);
+ goto out;
+ }
+
+ /*
+ * Clientid confirmation
+ */
+ if (cp->rc_need_confirm) {
+ if (rfs4_cmp_cred_set(&cp->rc_cr_set, cs)) {
+ cp->rc_need_confirm = FALSE;
+ if (cp->rc_cp_confirmed != NULL) {
+ rfs4_client_close(cp->rc_cp_confirmed);
+ cp->rc_cp_confirmed = NULL;
+ }
+ } else {
+ status = NFS4ERR_CLID_INUSE;
+ rfs4_client_rele(cp);
+ goto out;
+ }
+ }
+
+ /*
+ * Session creation
+ */
+ sca.cs_error = 0;
+ sca.cs_req = req;
+ sca.cs_client = cp;
+ sca.cs_aotw = *args;
+ sp = rfs4x_createsession(&sca);
+
+ if (sca.cs_error) {
+ status = sca.cs_error;
+ rfs4_client_rele(cp);
+ if (sp != NULL)
+ rfs4x_session_rele(sp);
+ goto out;
+ }
+
+ if (sp == NULL) {
+ status = NFS4ERR_SERVERFAULT;
+ rfs4_client_rele(cp);
+ goto out;
+ }
+
+ /*
+ * Need to store the result in the rfs4_client_t's contrived
+ * result slot and then respond from there. This way, when the
+ * csa_sequence == contrived.cc_sid, we can return the latest
+ * cached result. (see replay: above)
+ */
+ crp = (CREATE_SESSION4resok *)&cp->rc_contrived.cs_res;
+ cp->rc_contrived.cs_status = NFS4_OK;
+ rok->csr_sequence = crp->csr_sequence = cp->rc_contrived.xi_sid;
+ bcopy(sp->sn_sessid, rok->csr_sessionid, sizeof (sessionid4));
+ bcopy(sp->sn_sessid, crp->csr_sessionid, sizeof (sessionid4));
+ rok->csr_flags = crp->csr_flags = sp->sn_csflags;
+
+ cp->rc_contrived.xi_sid++;
+
+ /*
+ * XXX: struct assignment of channel4_attrs is broken because
+ * ca_rdma_ird is specified as a single element array. A struct
+ * assignment will copy the ca_rdma_ird array ptr to multiple args/
+ * res structs, and the ptr will be free'd multiple times.
+ * Without RDMA, ca_rdma_ird is a zero element array so its ptr
+ * is NULL (which is why this doesn't cause problems right now).
+ *
+ * Struct assignment is convenient, and it would be best to enable
+ * it by creating an in-kernel channel4_attrs struct which didn't
+ * contain the single element array, but just contained the inbound
+ * receive queue depth. Let the XDR encode/decode funcs convert
+ * from the in-kernel form to the OTW form.
+ */
+ rok->csr_fore_chan_attrs =
+ crp->csr_fore_chan_attrs = sp->sn_fore->cn_attrs;
+ rok->csr_back_chan_attrs = crp->csr_back_chan_attrs =
+ args->csa_back_chan_attrs;
+
+ rfs4_update_lease(cp);
+
+ /*
+ * References from the session to the client are
+ * accounted for while session is being created.
+ */
+ rfs4_client_rele(cp);
+ rfs4x_session_rele(sp);
+out:
+ *cs->statusp = resp->csr_status = status;
+
+ DTRACE_NFSV4_2(op__create__session__done,
+ struct compound_state *, cs,
+ CREATE_SESSION4res *, resp);
+}
+
+/* ARGSUSED */
+void
+rfs4x_op_destroy_session(nfs_argop4 *argop, nfs_resop4 *resop,
+ struct svc_req *req, compound_state_t *cs)
+{
+ DESTROY_SESSION4args *args = &argop->nfs_argop4_u.opdestroy_session;
+ DESTROY_SESSION4res *resp = &resop->nfs_resop4_u.opdestroy_session;
+ rfs4_session_t *sp;
+ rfs4_client_t *cp;
+ nfsstat4 status = NFS4_OK;
+ int addref = 0; /* additional reference */
+
+ DTRACE_NFSV4_2(op__destroy__session__start,
+ struct compound_state *, cs,
+ DESTROY_SESSION4args *, args);
+
+ /* section 18.37.3 rfc5661 */
+ if (rfs4_has_session(cs)) {
+ /* compound with a sequence */
+ if (bcmp(args->dsa_sessionid, cs->sp->sn_sessid,
+ sizeof (sessionid4)) == 0) {
+ /*
+ * Same session.
+ * must be the final operation in the COMPOUND request
+ */
+ if ((cs->op_pos + 1) != cs->op_len) {
+ status = NFS4ERR_NOT_ONLY_OP;
+ goto out;
+ }
+ addref++;
+ } else {
+ /* Not the same session */
+ /*
+ * TODO: need to verify that both sessions share
+ * the connection
+ */
+ DTRACE_PROBE(nfss41__i__destroy_encap_session);
+
+ }
+ }
+
+ sp = rfs4x_findsession_by_id(args->dsa_sessionid);
+ if (sp == NULL) {
+ status = NFS4ERR_BADSESSION;
+ goto out;
+ }
+ /*
+ * verify cred that was used to create the session matches and is in
+ * concordance w/the state protection type used.
+ */
+ cp = sp->sn_clnt;
+ switch (cp->rc_state_prot.sp_type) {
+ case SP4_MACH_CRED:
+ cmn_err(CE_NOTE, "op_destroy_session: SP4_MACH_CRED");
+ if (!rfs4_cmp_cred_set(&cp->rc_cr_set, cs)) {
+ status = NFS4ERR_PERM;
+ rfs4x_session_rele(sp);
+ goto out;
+ }
+ break;
+
+ case SP4_SSV:
+ /*
+ * XXX - Need some of Karen's secret ssv sauce
+ * here. For now, just allow the destroy.
+ */
+ cmn_err(CE_NOTE, "op_destroy_session: SP4_SSV");
+ break;
+
+ case SP4_NONE:
+ break;
+
+ default:
+ break;
+ }
+
+ status = rfs4x_destroysession(sp, 2 + addref);
+ rfs4x_session_rele(sp);
+out:
+ *cs->statusp = resp->dsr_status = status;
+
+ DTRACE_NFSV4_2(op__destroy__session__done,
+ struct compound_state *, cs,
+ DESTROY_SESSION4res *, resp);
+}
+
+/*
+ * The thread will traverse the entire list pinging the connections
+ * that need it and refreshing any stale/dead connections.
+ */
+static void
+ping_cb_null_thr(rfs4_session_t *sp)
+{
+ /* TODO: Need implementation */
+ cmn_err(CE_NOTE, "nfs4x: ping_cb_null thread");
+ rfs4x_session_rele(sp);
+ thread_exit();
+}
+
+/*
+ * Find session and validate sequence args.
+ * If this function successfully completes the compound state
+ * will contain a session pointer.
+ */
+static nfsstat4
+rfs4x_find_session(SEQUENCE4args *sargs, struct compound_state *cs)
+{
+ rfs4_session_t *sp;
+ slotid4 slot;
+
+ ASSERT(sargs != NULL);
+
+ if ((sp = rfs4x_findsession_by_id(sargs->sa_sessionid)) == NULL)
+ return (NFS4ERR_BADSESSION);
+
+ slot = sargs->sa_slotid;
+ if (slot < 0 || slot >= sp->sn_fore->cn_attrs.ca_maxrequests) {
+ rfs4x_session_rele(sp);
+ return (NFS4ERR_BADSLOT);
+ }
+ cs->sp = sp;
+ cs->cachethis = sargs->sa_cachethis;
+
+ return (NFS4_OK);
+}
+
+/* called under held lock */
+static nfsstat4
+check_slot_seqid(rfs4_slot_t *slot, sequenceid4 seqid)
+{
+ nfsstat4 status = NFS4ERR_SEQ_MISORDERED;
+
+ if (slot->se_flags & RFS4_SLOT_INUSE) {
+ /*
+ * There are three cases:
+ * 1. Duplicated requests for currently performing
+ * duplicated request.
+ * 2. New request for currently performing duplicated
+ * request.
+ * 3. Request with bad seqid for non finished performing
+ * request (due to a little window between 'prep'
+ * stage and actual renew se_seqid).
+ * In all cases tell a client to retry request later.
+ */
+ if (slot->se_seqid == seqid || slot->se_seqid + 1 == seqid) {
+ status = NFS4ERR_DELAY;
+ }
+ } else {
+ if (seqid == slot->se_seqid + 1)
+ status = NFS4_OK;
+ else if (seqid == slot->se_seqid)
+ status = nfserr_replay_cache;
+ }
+ return (status);
+}
+
+/*
+ * Prep stage for SEQUENCE operation.
+ *
+ * Main purpose to call this:
+ * - check on cached replay
+ * - Set cs.sp and cs.slot
+ */
+int
+rfs4x_sequence_prep(COMPOUND4args *args, COMPOUND4res *resp,
+ compound_state_t *cs)
+{
+ SEQUENCE4args *sargs;
+ nfsstat4 status;
+ rfs4_slot_t *slot;
+
+ if (args->array_len == 0 || args->array[0].argop != OP_SEQUENCE)
+ return (NFS4_OK);
+
+ sargs = &args->array[0].nfs_argop4_u.opsequence;
+
+ status = rfs4x_find_session(sargs, cs);
+ if (status != NFS4_OK)
+ return (status);
+
+ /* have reference to session */
+ slot = &cs->sp->sn_slots[sargs->sa_slotid];
+
+ mutex_enter(&slot->se_lock);
+ status = check_slot_seqid(slot, sargs->sa_sequenceid);
+ if (status == nfserr_replay_cache) {
+ if (slot->se_flags & RFS4_SLOT_CACHED) {
+ slot->se_flags |= RFS4_SLOT_INUSE;
+ cs->slot = slot;
+ *resp = slot->se_buf;
+ } else {
+ status = NFS4ERR_SEQ_MISORDERED;
+ }
+ } else if (status == NFS4_OK) {
+ slot->se_flags |= RFS4_SLOT_INUSE;
+ cs->slot = slot;
+ }
+ mutex_exit(&slot->se_lock);
+
+ return (status);
+}
+
+/*
+ * Do cleanup things
+ * 1. cache reply
+ * 2. release slot
+ */
+void
+rfs4x_sequence_done(COMPOUND4res *resp, compound_state_t *cs)
+{
+ rfs4_slot_t *slot = cs->slot;
+ rfs4_session_t *sp = cs->sp;
+ int add = 0;
+
+ ASSERT(slot != NULL);
+ ASSERT(sp != NULL);
+
+ mutex_enter(&slot->se_lock);
+ slot->se_flags &= ~RFS4_SLOT_INUSE;
+
+ if (*cs->statusp != nfserr_replay_cache) {
+ if (slot->se_flags & RFS4_SLOT_CACHED) {
+ rfs4_compound_free(&slot->se_buf);
+ slot->se_flags &= ~RFS4_SLOT_CACHED;
+ add = -1;
+ }
+
+ if (*cs->statusp == NFS4_OK && cs->cachethis) {
+ slot->se_flags |= RFS4_SLOT_CACHED;
+ slot->se_buf = *resp; /* cache a reply */
+ add += 1;
+ } else {
+ rfs4_compound_free(resp);
+ }
+ }
+ mutex_exit(&slot->se_lock);
+
+ if (add != 0)
+ atomic_add_32(&sp->sn_rcached, add);
+}
+
+/*
+ * Process the SEQUENCE operation. The session pointer has already been
+ * cached in the compound state, so we just dereference
+ */
+/*ARGSUSED*/
+void
+rfs4x_op_sequence(nfs_argop4 *argop, nfs_resop4 *resop,
+ struct svc_req *req, compound_state_t *cs)
+{
+ SEQUENCE4args *args = &argop->nfs_argop4_u.opsequence;
+ SEQUENCE4res *resp = &resop->nfs_resop4_u.opsequence;
+ SEQUENCE4resok *rok = &resp->SEQUENCE4res_u.sr_resok4;
+ rfs4_session_t *sp = cs->sp;
+ rfs4_slot_t *slot = cs->slot;
+ nfsstat4 status = NFS4_OK;
+ uint32_t cbstat = 0x0;
+
+ DTRACE_NFSV4_2(op__sequence__start,
+ struct compound_state *, cs,
+ SEQUENCE4args *, args);
+
+ ASSERT(sp != NULL && slot != NULL);
+
+ if (cs->op_pos != 0) {
+ status = NFS4ERR_SEQUENCE_POS;
+ goto out;
+ }
+
+ if (rfs4_lease_expired(sp->sn_clnt)) {
+ status = NFS4ERR_BADSESSION;
+ goto out;
+ }
+
+ /*
+ * If the back channel has been established...
+ * . if the channel has _not_ been marked as failed _AND_
+ * there are connections that have pings outstanding,
+ * we go ahead and fire the thread to traverse all of
+ * the session's conns, issuing CB_NULL's to those that
+ * need a ping.
+ * . if the channel is _not_ OK (ie. failed), then notify
+ * client that there is currently a problem with the CB
+ * path.
+ */
+ rfs4_dbe_lock(sp->sn_dbe);
+ if (SN_CB_CHAN_EST(sp)) {
+ if (SN_CB_CHAN_OK(sp)) {
+ if (sp->sn_bc.pngcnt > 0 && !sp->sn_bc.pnginprog) {
+ kthread_t *t;
+
+ rfs4x_session_hold(sp);
+ t = thread_create(NULL, 0, ping_cb_null_thr,
+ sp, 0, &p0, TS_RUN, minclsyspri);
+ if (!t)
+ rfs4x_session_rele(sp);
+ }
+ } else {
+ cbstat |= SEQ4_STATUS_CB_PATH_DOWN;
+ }
+ }
+ cs->client = sp->sn_clnt;
+
+ DTRACE_PROBE1(compound_clid, clientid4, cs->client->rc_clientid);
+
+ ASSERT(args->sa_sequenceid == slot->se_seqid + 1);
+
+ /*
+ * New request.
+ */
+ mutex_enter(&slot->se_lock);
+ slot->se_seqid = args->sa_sequenceid;
+ mutex_exit(&slot->se_lock);
+
+ /*
+ * Update access time and lease
+ */
+ cs->slotno = args->sa_slotid;
+ sp->sn_laccess = nfs_sys_uptime();
+ rfs4_update_lease(cs->client);
+
+ /*
+ * Let's keep it simple for now
+ */
+ bcopy(sp->sn_sessid, rok->sr_sessionid, sizeof (sessionid4));
+ rok->sr_sequenceid = slot->se_seqid;
+ rok->sr_slotid = args->sa_slotid;
+ rok->sr_highest_slotid =
+ sp->sn_fore->cn_attrs.ca_maxrequests - 1;
+ rok->sr_target_highest_slotid =
+ sp->sn_fore->cn_attrs.ca_maxrequests - 1;
+ rok->sr_status_flags |= cbstat;
+ rfs4_dbe_unlock(sp->sn_dbe);
+out:
+ *cs->statusp = resp->sr_status = status;
+ DTRACE_NFSV4_2(op__sequence__done,
+ struct compound_state *, cs,
+ SEQUENCE4res *, resp);
+}
+
+/* ARGSUSED */
+void
+rfs4x_op_reclaim_complete(nfs_argop4 *argop, nfs_resop4 *resop,
+ struct svc_req *req, compound_state_t *cs)
+{
+ RECLAIM_COMPLETE4args *args = &argop->nfs_argop4_u.opreclaim_complete;
+ RECLAIM_COMPLETE4res *resp = &resop->nfs_resop4_u.opreclaim_complete;
+ rfs4_client_t *cp;
+ nfsstat4 status = NFS4_OK;
+
+ DTRACE_NFSV4_2(op__reclaim__complete__start,
+ struct compound_state *, cs,
+ RECLAIM_COMPLETE4args *, args);
+
+ cp = cs->client;
+ rfs4_dbe_lock(cp->rc_dbe);
+ if (args->rca_one_fs) {
+ /* do what? we don't track this */
+ goto out;
+ }
+
+ if (cp->rc_reclaim_completed) {
+ status = NFS4ERR_COMPLETE_ALREADY;
+ goto out;
+ }
+
+ if (cp->rc_can_reclaim) {
+ ASSERT(rfs4_servinst(cp)->nreclaim > 0);
+ atomic_add_32(&(rfs4_servinst(cp))->nreclaim, -1);
+ }
+
+ cp->rc_reclaim_completed = 1;
+out:
+ rfs4_dbe_unlock(cp->rc_dbe);
+
+ *cs->statusp = resp->rcr_status = status;
+ DTRACE_NFSV4_2(op__reclaim__complete__done,
+ struct compound_state *, cs,
+ RECLAIM_COMPLETE4res *, resp);
+}
+
+/* ARGSUSED */
+void
+rfs4x_op_destroy_clientid(nfs_argop4 *argop, nfs_resop4 *resop,
+ struct svc_req *req, compound_state_t *cs)
+{
+ DESTROY_CLIENTID4args *args = &argop->nfs_argop4_u.opdestroy_clientid;
+ DESTROY_CLIENTID4res *resp = &resop->nfs_resop4_u.opdestroy_clientid;
+ rfs4_client_t *cp;
+ nfsstat4 status = NFS4_OK;
+
+ DTRACE_NFSV4_2(op__destroy__clientid__start,
+ struct compound_state *, cs,
+ DESTROY_CLIENTID4args *, args);
+
+ cp = rfs4_findclient_by_id(args->dca_clientid, TRUE);
+ if (cp == NULL) {
+ status = NFS4ERR_STALE_CLIENTID;
+ goto end;
+ }
+
+ rfs4_dbe_lock(cp->rc_dbe);
+ if (client_has_state_locked(cp))
+ status = NFS4ERR_CLIENTID_BUSY;
+ else
+ cp->rc_destroying = TRUE;
+ rfs4_dbe_unlock(cp->rc_dbe);
+
+ if (status == NFS4_OK)
+ rfs4_client_close(cp);
+ else
+ rfs4_client_rele(cp);
+end:
+ *cs->statusp = resp->dcr_status = status;
+
+ DTRACE_NFSV4_2(op__destroy__clientid__done,
+ struct compound_state *, cs,
+ DESTROY_CLIENTID4res *, resp);
+}
+
+/*ARGSUSED*/
+void
+rfs4x_op_bind_conn_to_session(nfs_argop4 *argop, nfs_resop4 *resop,
+ struct svc_req *req, compound_state_t *cs)
+{
+ BIND_CONN_TO_SESSION4args *args =
+ &argop->nfs_argop4_u.opbind_conn_to_session;
+ BIND_CONN_TO_SESSION4res *resp =
+ &resop->nfs_resop4_u.opbind_conn_to_session;
+ BIND_CONN_TO_SESSION4resok *rok =
+ &resp->BIND_CONN_TO_SESSION4res_u.bctsr_resok4;
+ rfs4_session_t *sp;
+ nfsstat4 status = NFS4_OK;
+
+ DTRACE_NFSV4_2(op__bind__conn__to__session__start,
+ struct compound_state *, cs,
+ BIND_CONN_TO_SESSION4args *, args);
+
+ if (cs->op_pos != 0) {
+ status = NFS4ERR_NOT_ONLY_OP;
+ goto end;
+ }
+
+ sp = rfs4x_findsession_by_id(args->bctsa_sessid);
+ if (sp == NULL) {
+ status = NFS4ERR_BADSESSION;
+ goto end;
+ }
+
+ rfs4_update_lease(sp->sn_clnt); /* no need lock protection */
+
+ rfs4_dbe_lock(sp->sn_dbe);
+ sp->sn_laccess = nfs_sys_uptime();
+ rfs4_dbe_unlock(sp->sn_dbe);
+
+ rok->bctsr_use_conn_in_rdma_mode = FALSE;
+
+ switch (args->bctsa_dir) {
+ case CDFC4_FORE:
+ case CDFC4_FORE_OR_BOTH:
+ /* always map to Fore */
+ rok->bctsr_dir = CDFS4_FORE;
+ break;
+
+ case CDFC4_BACK:
+ case CDFC4_BACK_OR_BOTH:
+ /* TODO: always map to Back */
+ rok->bctsr_dir = CDFS4_FORE;
+ break;
+ default:
+ break;
+ }
+
+ bcopy(sp->sn_sessid, rok->bctsr_sessid, sizeof (sessionid4));
+ rfs4x_session_rele(sp);
+end:
+ *cs->statusp = resp->bctsr_status = status;
+
+ DTRACE_NFSV4_2(op__bind__conn__to__session__done,
+ struct compound_state *, cs,
+ BIND_CONN_TO_SESSION4res *, resp);
+}
+
+/* ARGSUSED */
+void
+rfs4x_op_secinfo_noname(nfs_argop4 *argop, nfs_resop4 *resop,
+ struct svc_req *req, compound_state_t *cs)
+{
+ SECINFO_NO_NAME4res *resp = &resop->nfs_resop4_u.opsecinfo_no_name;
+ nfsstat4 status;
+ bool_t dotdot;
+
+ DTRACE_NFSV4_1(op__secinfo__no__name__start,
+ struct compound_state *, cs);
+
+ if (cs->vp == NULL) {
+ status = NFS4ERR_NOFILEHANDLE;
+ goto out;
+ }
+
+ if (cs->vp->v_type != VDIR) {
+ status = NFS4ERR_NOTDIR;
+ goto out;
+ }
+
+ dotdot =
+ (argop->nfs_argop4_u.opsecinfo_no_name == SECINFO_STYLE4_PARENT);
+
+ status = do_rfs4_op_secinfo(cs, dotdot ? ".." : ".", resp);
+
+ /* Cleanup FH as described at 18.45.3 and 2.6.3.1.1.8 */
+ if (status == NFS4_OK) {
+ VN_RELE(cs->vp);
+ cs->vp = NULL;
+ }
+out:
+ *cs->statusp = resp->status = status;
+
+ DTRACE_NFSV4_2(op__secinfo__no__name__done,
+ struct compound_state *, cs,
+ SECINFO_NO_NAME4res *, resp);
+}