+ if (nlip1->nli_loc != nlip2->nli_loc)
+ return (nlip1->nli_loc - nlip2->nli_loc);
+ if (nlip1->nli_serv != nlip2->nli_serv)
+ return (nlip1->nli_serv - nlip2->nli_serv);
+ return (nlip1->nli_addr - nlip2->nli_addr);
+}
+
+/*
+ * Get the mntfromname (or path portion only) for a given location.
+ */
+void
+nfs_location_mntfromname(struct nfs_fs_locations *locs, struct nfs_location_index idx, char *s, int size, int pathonly)
+{
+ struct nfs_fs_location *fsl = locs->nl_locations[idx.nli_loc];
+ char *p;
+ int cnt, i;
+
+ p = s;
+ if (!pathonly) {
+ cnt = snprintf(p, size, "%s:", fsl->nl_servers[idx.nli_serv]->ns_name);
+ p += cnt;
+ size -= cnt;
+ }
+ if (fsl->nl_path.np_compcount == 0) {
+ /* mounting root export on server */
+ if (size > 0) {
+ *p++ = '/';
+ *p++ = '\0';
+ }
+ return;
+ }
+ /* append each server path component */
+ for (i=0; (size > 0) && (i < (int)fsl->nl_path.np_compcount); i++) {
+ cnt = snprintf(p, size, "/%s", fsl->nl_path.np_components[i]);
+ p += cnt;
+ size -= cnt;
+ }
+}
+
+/*
+ * NFS client connect socket upcall.
+ * (Used only during socket connect/search.)
+ */
+void
+nfs_connect_upcall(socket_t so, void *arg, __unused int waitflag)
+{
+ struct nfs_socket *nso = arg;
+ size_t rcvlen;
+ mbuf_t m;
+ int error = 0, recv = 1;
+
+ if (nso->nso_flags & NSO_CONNECTING) {
+ NFS_SOCK_DBG("nfs connect - socket %p upcall - connecting\n", nso);
+ wakeup(nso->nso_wake);
+ return;
+ }
+
+ lck_mtx_lock(&nso->nso_lock);
+ if ((nso->nso_flags & (NSO_UPCALL|NSO_DISCONNECTING|NSO_DEAD)) || !(nso->nso_flags & NSO_PINGING)) {
+ NFS_SOCK_DBG("nfs connect - socket %p upcall - nevermind\n", nso);
+ lck_mtx_unlock(&nso->nso_lock);
+ return;
+ }
+ NFS_SOCK_DBG("nfs connect - socket %p upcall\n", nso);
+ nso->nso_flags |= NSO_UPCALL;
+
+ /* loop while we make error-free progress */
+ while (!error && recv) {
+ /* make sure we're still interested in this socket */
+ if (nso->nso_flags & (NSO_DISCONNECTING|NSO_DEAD))
+ break;
+ lck_mtx_unlock(&nso->nso_lock);
+ m = NULL;
+ if (nso->nso_sotype == SOCK_STREAM) {
+ error = nfs_rpc_record_read(so, &nso->nso_rrs, MSG_DONTWAIT, &recv, &m);
+ } else {
+ rcvlen = 1000000;
+ error = sock_receivembuf(so, NULL, &m, MSG_DONTWAIT, &rcvlen);
+ recv = m ? 1 : 0;
+ }
+ lck_mtx_lock(&nso->nso_lock);
+ if (m) {
+ /* match response with request */
+ struct nfsm_chain nmrep;
+ uint32_t reply = 0, rxid = 0, verf_type, verf_len;
+ uint32_t reply_status, rejected_status, accepted_status;
+
+ nfsm_chain_dissect_init(error, &nmrep, m);
+ nfsm_chain_get_32(error, &nmrep, rxid);
+ nfsm_chain_get_32(error, &nmrep, reply);
+ if (!error && ((reply != RPC_REPLY) || (rxid != nso->nso_pingxid)))
+ error = EBADRPC;
+ nfsm_chain_get_32(error, &nmrep, reply_status);
+ if (!error && (reply_status == RPC_MSGDENIED)) {
+ nfsm_chain_get_32(error, &nmrep, rejected_status);
+ if (!error)
+ error = (rejected_status == RPC_MISMATCH) ? ERPCMISMATCH : EACCES;
+ }
+ nfsm_chain_get_32(error, &nmrep, verf_type); /* verifier flavor */
+ nfsm_chain_get_32(error, &nmrep, verf_len); /* verifier length */
+ nfsmout_if(error);
+ if (verf_len)
+ nfsm_chain_adv(error, &nmrep, nfsm_rndup(verf_len));
+ nfsm_chain_get_32(error, &nmrep, accepted_status);
+ nfsmout_if(error);
+ if ((accepted_status == RPC_PROGMISMATCH) && !nso->nso_version) {
+ uint32_t minvers, maxvers;
+ nfsm_chain_get_32(error, &nmrep, minvers);
+ nfsm_chain_get_32(error, &nmrep, maxvers);
+ nfsmout_if(error);
+ if (nso->nso_protocol == PMAPPROG) {
+ if ((minvers > RPCBVERS4) || (maxvers < PMAPVERS))
+ error = EPROGMISMATCH;
+ else if ((nso->nso_saddr->sa_family == AF_INET) &&
+ (PMAPVERS >= minvers) && (PMAPVERS <= maxvers))
+ nso->nso_version = PMAPVERS;
+ else if (nso->nso_saddr->sa_family == AF_INET6) {
+ if ((RPCBVERS4 >= minvers) && (RPCBVERS4 <= maxvers))
+ nso->nso_version = RPCBVERS4;
+ else if ((RPCBVERS3 >= minvers) && (RPCBVERS3 <= maxvers))
+ nso->nso_version = RPCBVERS3;
+ }
+ } else if (nso->nso_protocol == NFS_PROG) {
+ if ((minvers > NFS_VER4) || (maxvers < NFS_VER2))
+ error = EPROGMISMATCH;
+ else if ((NFS_VER3 >= minvers) && (NFS_VER3 <= maxvers))
+ nso->nso_version = NFS_VER3;
+ else if ((NFS_VER2 >= minvers) && (NFS_VER2 <= maxvers))
+ nso->nso_version = NFS_VER2;
+ else if ((NFS_VER4 >= minvers) && (NFS_VER4 <= maxvers))
+ nso->nso_version = NFS_VER4;
+ }
+ if (!error && nso->nso_version)
+ accepted_status = RPC_SUCCESS;
+ }
+ if (!error) {
+ switch (accepted_status) {
+ case RPC_SUCCESS:
+ error = 0;
+ break;
+ case RPC_PROGUNAVAIL:
+ error = EPROGUNAVAIL;
+ break;
+ case RPC_PROGMISMATCH:
+ error = EPROGMISMATCH;
+ break;
+ case RPC_PROCUNAVAIL:
+ error = EPROCUNAVAIL;
+ break;
+ case RPC_GARBAGE:
+ error = EBADRPC;
+ break;
+ case RPC_SYSTEM_ERR:
+ default:
+ error = EIO;
+ break;
+ }
+ }
+nfsmout:
+ nso->nso_flags &= ~NSO_PINGING;
+ if (error) {
+ nso->nso_error = error;
+ nso->nso_flags |= NSO_DEAD;
+ } else {
+ nso->nso_flags |= NSO_VERIFIED;
+ }
+ mbuf_freem(m);
+ /* wake up search thread */
+ wakeup(nso->nso_wake);
+ break;
+ }
+ }
+
+ nso->nso_flags &= ~NSO_UPCALL;
+ if ((error != EWOULDBLOCK) && (error || !recv)) {
+ /* problems with the socket... */
+ nso->nso_error = error ? error : EPIPE;
+ nso->nso_flags |= NSO_DEAD;
+ wakeup(nso->nso_wake);
+ }
+ if (nso->nso_flags & NSO_DISCONNECTING)
+ wakeup(&nso->nso_flags);
+ lck_mtx_unlock(&nso->nso_lock);
+}
+
+/*
+ * Create/initialize an nfs_socket structure.
+ */
+int
+nfs_socket_create(
+ __unused struct nfsmount *nmp,
+ struct sockaddr *sa,
+ int sotype,
+ in_port_t port,
+ uint32_t protocol,
+ uint32_t vers,
+ int resvport,
+ struct nfs_socket **nsop)
+{
+ struct nfs_socket *nso;
+ struct timeval now;
+ int error;
+#ifdef NFS_SOCKET_DEBUGGING
+ char naddr[MAX_IPv6_STR_LEN];
+ void *sinaddr;
+
+ if (sa->sa_family == AF_INET)
+ sinaddr = &((struct sockaddr_in*)sa)->sin_addr;
+ else
+ sinaddr = &((struct sockaddr_in6*)sa)->sin6_addr;
+ if (inet_ntop(sa->sa_family, sinaddr, naddr, sizeof(naddr)) != naddr)
+ strlcpy(naddr, "<unknown>", sizeof(naddr));
+#else
+ char naddr[1] = { 0 };
+#endif
+
+ *nsop = NULL;
+
+ /* Create the socket. */
+ MALLOC(nso, struct nfs_socket *, sizeof(struct nfs_socket), M_TEMP, M_WAITOK|M_ZERO);
+ if (nso)
+ MALLOC(nso->nso_saddr, struct sockaddr *, sa->sa_len, M_SONAME, M_WAITOK|M_ZERO);
+ if (!nso || !nso->nso_saddr) {
+ if (nso)
+ FREE(nso, M_TEMP);
+ return (ENOMEM);
+ }
+ lck_mtx_init(&nso->nso_lock, nfs_request_grp, LCK_ATTR_NULL);
+ nso->nso_sotype = sotype;
+ if (nso->nso_sotype == SOCK_STREAM)
+ nfs_rpc_record_state_init(&nso->nso_rrs);
+ microuptime(&now);
+ nso->nso_timestamp = now.tv_sec;
+ bcopy(sa, nso->nso_saddr, sa->sa_len);
+ if (sa->sa_family == AF_INET)
+ ((struct sockaddr_in*)nso->nso_saddr)->sin_port = htons(port);
+ else if (sa->sa_family == AF_INET6)
+ ((struct sockaddr_in6*)nso->nso_saddr)->sin6_port = htons(port);
+ nso->nso_protocol = protocol;
+ nso->nso_version = vers;
+
+ error = sock_socket(sa->sa_family, nso->nso_sotype, 0, NULL, NULL, &nso->nso_so);
+
+ /* Some servers require that the client port be a reserved port number. */
+ if (!error && resvport && ((sa->sa_family == AF_INET) || (sa->sa_family == AF_INET6))) {
+ struct sockaddr_storage ss;
+ int level = (sa->sa_family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
+ int optname = (sa->sa_family == AF_INET) ? IP_PORTRANGE : IPV6_PORTRANGE;
+ int portrange = IP_PORTRANGE_LOW;
+
+ error = sock_setsockopt(nso->nso_so, level, optname, &portrange, sizeof(portrange));
+ if (!error) { /* bind now to check for failure */
+ ss.ss_len = sa->sa_len;
+ ss.ss_family = sa->sa_family;
+ if (ss.ss_family == AF_INET) {
+ ((struct sockaddr_in*)&ss)->sin_addr.s_addr = INADDR_ANY;
+ ((struct sockaddr_in*)&ss)->sin_port = htons(0);
+ } else if (ss.ss_family == AF_INET6) {
+ ((struct sockaddr_in6*)&ss)->sin6_addr = in6addr_any;
+ ((struct sockaddr_in6*)&ss)->sin6_port = htons(0);
+ } else {
+ error = EINVAL;
+ }
+ if (!error)
+ error = sock_bind(nso->nso_so, (struct sockaddr*)&ss);
+ }
+ }
+
+ if (error) {
+ NFS_SOCK_DBG("nfs connect %s error %d creating socket %p %s type %d%s port %d prot %d %d\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname, error, nso, naddr, sotype,
+ resvport ? "r" : "", port, protocol, vers);
+ nfs_socket_destroy(nso);
+ } else {
+ NFS_SOCK_DBG("nfs connect %s created socket %p %s type %d%s port %d prot %d %d\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso, naddr,
+ sotype, resvport ? "r" : "", port, protocol, vers);
+ *nsop = nso;
+ }
+ return (error);
+}
+
+/*
+ * Destroy an nfs_socket structure.
+ */
+void
+nfs_socket_destroy(struct nfs_socket *nso)
+{
+ struct timespec ts = { 4, 0 };
+
+ lck_mtx_lock(&nso->nso_lock);
+ nso->nso_flags |= NSO_DISCONNECTING;
+ if (nso->nso_flags & NSO_UPCALL) /* give upcall a chance to complete */
+ msleep(&nso->nso_flags, &nso->nso_lock, PZERO-1, "nfswaitupcall", &ts);
+ lck_mtx_unlock(&nso->nso_lock);
+ sock_shutdown(nso->nso_so, SHUT_RDWR);
+ sock_close(nso->nso_so);
+ if (nso->nso_sotype == SOCK_STREAM)
+ nfs_rpc_record_state_cleanup(&nso->nso_rrs);
+ lck_mtx_destroy(&nso->nso_lock, nfs_request_grp);
+ if (nso->nso_saddr)
+ FREE(nso->nso_saddr, M_SONAME);
+ if (nso->nso_saddr2)
+ FREE(nso->nso_saddr2, M_SONAME);
+ NFS_SOCK_DBG("nfs connect - socket %p destroyed\n", nso);
+ FREE(nso, M_TEMP);
+}
+
+/*
+ * Set common socket options on an nfs_socket.
+ */
+void
+nfs_socket_options(struct nfsmount *nmp, struct nfs_socket *nso)
+{
+ /*
+ * Set socket send/receive timeouts
+ * - Receive timeout shouldn't matter because most receives are performed
+ * in the socket upcall non-blocking.
+ * - Send timeout should allow us to react to a blocked socket.
+ * Soft mounts will want to abort sooner.
+ */