+ nso->nso_error = error;
+ nso->nso_flags |= NSO_DEAD;
+ return (0);
+ }
+
+ return (1);
+}
+
+/*
+ * nfs_connect_search_socket_found: Take the found socket of the socket search list and assign it to the searched socket.
+ * Set the nfs socket protocol and version if needed.
+ */
+void
+nfs_connect_search_socket_found(struct nfsmount *nmp, struct nfs_socket_search *nss, struct nfs_socket *nso)
+{
+ NFS_SOCK_DBG("nfs connect %s socket %p verified\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso);
+ if (!nso->nso_version) {
+ /* If the version isn't set, the default must have worked. */
+ if (nso->nso_protocol == PMAPPROG)
+ nso->nso_version = (nso->nso_saddr->sa_family == AF_INET) ? PMAPVERS : RPCBVERS4;
+ if (nso->nso_protocol == NFS_PROG)
+ nso->nso_version = PVER2MAJOR(nmp->nm_max_vers);
+ }
+ TAILQ_REMOVE(&nss->nss_socklist, nso, nso_link);
+ nss->nss_sockcnt--;
+ nss->nss_sock = nso;
+}
+
+/*
+ * nfs_connect_search_socket_reap: For each socket in the search list mark any timed out socket as dead and remove from
+ * the list. Dead socket are then destroyed.
+ */
+void
+nfs_connect_search_socket_reap(struct nfsmount *nmp __unused, struct nfs_socket_search *nss, struct timeval *now)
+{
+ struct nfs_socket *nso, *nsonext;
+
+ TAILQ_FOREACH_SAFE(nso, &nss->nss_socklist, nso_link, nsonext) {
+ lck_mtx_lock(&nso->nso_lock);
+ if (now->tv_sec >= (nso->nso_timestamp + nss->nss_timeo)) {
+ /* took too long */
+ NFS_SOCK_DBG("nfs connect %s socket %p timed out\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso);
+ nso->nso_error = ETIMEDOUT;
+ nso->nso_flags |= NSO_DEAD;
+ }
+ if (!(nso->nso_flags & NSO_DEAD)) {
+ lck_mtx_unlock(&nso->nso_lock);
+ continue;
+ }
+ lck_mtx_unlock(&nso->nso_lock);
+ NFS_SOCK_DBG("nfs connect %s reaping socket %p %d\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso, nso->nso_error);
+ nfs_socket_search_update_error(nss, nso->nso_error);
+ TAILQ_REMOVE(&nss->nss_socklist, nso, nso_link);
+ nss->nss_sockcnt--;
+ nfs_socket_destroy(nso);
+ /* If there are more sockets to try, force the starting of another socket */
+ if (nss->nss_addrcnt > 0)
+ nss->nss_last = -2;
+ }
+}
+
+/*
+ * nfs_connect_search_check: Check on the status of search and wait for replies if needed.
+ */
+int
+nfs_connect_search_check(struct nfsmount *nmp, struct nfs_socket_search *nss, struct timeval *now)
+{
+ int error;
+
+ /* log a warning if connect is taking a while */
+ if (((now->tv_sec - nss->nss_timestamp) >= 8) && ((nss->nss_flags & (NSS_VERBOSE|NSS_WARNED)) == NSS_VERBOSE)) {
+ printf("nfs_connect: socket connect taking a while for %s\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname);
+ nss->nss_flags |= NSS_WARNED;
+ }
+ if (nmp->nm_sockflags & NMSOCK_UNMOUNT)
+ return (EINTR);
+ if ((error = nfs_sigintr(nmp, NULL, current_thread(), 0)))
+ return (error);
+
+ /* If we were succesfull at sending a ping, wait up to a second for a reply */
+ if (nss->nss_last >= 0)
+ tsleep(nss, PSOCK, "nfs_connect_search_wait", hz);
+
+ return (0);
+}
+
+
+/*
+ * Continue the socket search until we have something to report.
+ */
+int
+nfs_connect_search_loop(struct nfsmount *nmp, struct nfs_socket_search *nss)
+{
+ struct nfs_socket *nso;
+ struct timeval now;
+ int error;
+ int verbose = (nss->nss_flags & NSS_VERBOSE);
+
+loop:
+ microuptime(&now);
+ NFS_SOCK_DBG("nfs connect %s search %ld\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, now.tv_sec);
+
+ /* add a new socket to the socket list if needed and available */
+ error = nfs_connect_search_new_socket(nmp, nss, &now);
+ if (error) {
+ NFS_SOCK_DBG("nfs connect returned %d\n", error);
+ return (error);
+ }
+
+ /* check each active socket on the list and try to push it along */
+ TAILQ_FOREACH(nso, &nss->nss_socklist, nso_link) {
+ lck_mtx_lock(&nso->nso_lock);
+
+ /* If not connected connect it */
+ if (!(nso->nso_flags & NSO_CONNECTED)) {
+ if (!nfs_connect_search_socket_connect(nmp, nso, verbose)) {
+ lck_mtx_unlock(&nso->nso_lock);
+ continue;
+ }
+ }
+
+ /* If the socket hasn't been verified or in a ping, ping it. We also handle UDP retransmits */
+ if (!(nso->nso_flags & (NSO_PINGING|NSO_VERIFIED)) ||
+ ((nso->nso_sotype == SOCK_DGRAM) && (now.tv_sec >= nso->nso_reqtimestamp+2))) {
+ if (!nfs_connect_search_ping(nmp, nso, &now)) {
+ lck_mtx_unlock(&nso->nso_lock);
+ continue;
+ }
+ }
+
+ /* Has the socket been verified by the up call routine? */
+ if (nso->nso_flags & NSO_VERIFIED) {
+ /* WOOHOO!! This socket looks good! */
+ nfs_connect_search_socket_found(nmp, nss, nso);
+ lck_mtx_unlock(&nso->nso_lock);
+ break;
+ }
+ lck_mtx_unlock(&nso->nso_lock);
+ }
+
+ /* Check for timed out sockets and mark as dead and then remove all dead sockets. */
+ nfs_connect_search_socket_reap(nmp, nss, &now);
+
+ /*
+ * Keep looping if we haven't found a socket yet and we have more
+ * sockets to (continue to) try.
+ */
+ error = 0;
+ if (!nss->nss_sock && (!TAILQ_EMPTY(&nss->nss_socklist) || nss->nss_addrcnt)) {
+ error = nfs_connect_search_check(nmp, nss, &now);
+ if (!error)
+ goto loop;
+ }
+
+ NFS_SOCK_DBG("nfs connect %s returning %d\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, error);
+ return (error);
+}
+
+/*
+ * Initialize a new NFS connection.
+ *
+ * Search for a location to connect a socket to and initialize the connection.
+ *
+ * An NFS mount may have multiple locations/servers/addresses available.
+ * We attempt to connect to each one asynchronously and will start
+ * several sockets in parallel if other locations are slow to answer.
+ * We'll use the first NFS socket we can successfully set up.
+ *
+ * The search may involve contacting the portmapper service first.
+ *
+ * A mount's initial connection may require negotiating some parameters such
+ * as socket type and NFS version.
+ */
+
+int
+nfs_connect(struct nfsmount *nmp, int verbose, int timeo)
+{
+ struct nfs_socket_search nss;
+ struct nfs_socket *nso, *nsonfs;
+ struct sockaddr_storage ss;
+ struct sockaddr *saddr, *oldsaddr;
+ sock_upcall upcall;
+ struct timeval now, start;
+ int error, savederror, nfsvers;
+ int tryv4 = 1;
+ uint8_t sotype = nmp->nm_sotype ? nmp->nm_sotype : SOCK_STREAM;
+ fhandle_t *fh = NULL;
+ char *path = NULL;
+ in_port_t port;
+ int addrtotal = 0;
+
+ /* paranoia... check that we have at least one address in the locations */
+ uint32_t loc, serv;
+ for (loc=0; loc < nmp->nm_locations.nl_numlocs; loc++) {
+ for (serv=0; serv < nmp->nm_locations.nl_locations[loc]->nl_servcount; serv++) {
+ addrtotal += nmp->nm_locations.nl_locations[loc]->nl_servers[serv]->ns_addrcount;
+ if (nmp->nm_locations.nl_locations[loc]->nl_servers[serv]->ns_addrcount == 0)
+ NFS_SOCK_DBG("nfs connect %s search, server %s has no addresses\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname,
+ nmp->nm_locations.nl_locations[loc]->nl_servers[serv]->ns_name);
+ }
+ }
+
+ if (addrtotal == 0) {
+ NFS_SOCK_DBG("nfs connect %s search failed, no addresses\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname);
+ return (EINVAL);
+ } else
+ NFS_SOCK_DBG("nfs connect %s has %d addresses\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname, addrtotal);
+
+ lck_mtx_lock(&nmp->nm_lock);
+ nmp->nm_sockflags |= NMSOCK_CONNECTING;
+ nmp->nm_nss = &nss;
+ lck_mtx_unlock(&nmp->nm_lock);
+ microuptime(&start);
+ savederror = error = 0;
+
+tryagain:
+ /* initialize socket search state */
+ bzero(&nss, sizeof(nss));
+ nss.nss_addrcnt = addrtotal;
+ nss.nss_error = savederror;
+ TAILQ_INIT(&nss.nss_socklist);
+ nss.nss_sotype = sotype;
+ nss.nss_startloc = nmp->nm_locations.nl_current;
+ nss.nss_timestamp = start.tv_sec;
+ nss.nss_timeo = timeo;
+ if (verbose)
+ nss.nss_flags |= NSS_VERBOSE;
+
+ /* First time connecting, we may need to negotiate some things */
+ if (!(nmp->nm_sockflags & NMSOCK_HASCONNECTED)) {
+ if (!nmp->nm_vers) {
+ /* No NFS version specified... */
+ if (!nmp->nm_nfsport || (!NM_OMATTR_GIVEN(nmp, FH) && !nmp->nm_mountport)) {
+ if (PVER2MAJOR(nmp->nm_max_vers) >= NFS_VER4 && tryv4) {
+ nss.nss_port = NFS_PORT;
+ nss.nss_protocol = NFS_PROG;
+ nss.nss_version = 4;
+ nss.nss_flags |= NSS_FALLBACK2PMAP;
+ } else {
+ /* ...connect to portmapper first if we (may) need any ports. */
+ nss.nss_port = PMAPPORT;
+ nss.nss_protocol = PMAPPROG;
+ nss.nss_version = 0;
+ }
+ } else {
+ /* ...connect to NFS port first. */
+ nss.nss_port = nmp->nm_nfsport;
+ nss.nss_protocol = NFS_PROG;
+ nss.nss_version = 0;
+ }
+ } else if (nmp->nm_vers >= NFS_VER4) {
+ if (tryv4) {
+ /* For NFSv4, we use the given (or default) port. */
+ nss.nss_port = nmp->nm_nfsport ? nmp->nm_nfsport : NFS_PORT;
+ nss.nss_protocol = NFS_PROG;
+ nss.nss_version = 4;
+ /*
+ * set NSS_FALLBACK2PMAP here to pick up any non standard port
+ * if no port is specified on the mount;
+ * Note nm_vers is set so we will only try NFS_VER4.
+ */
+ if (!nmp->nm_nfsport)
+ nss.nss_flags |= NSS_FALLBACK2PMAP;
+ } else {
+ nss.nss_port = PMAPPORT;
+ nss.nss_protocol = PMAPPROG;
+ nss.nss_version = 0;
+ }
+ } else {
+ /* For NFSv3/v2... */
+ if (!nmp->nm_nfsport || (!NM_OMATTR_GIVEN(nmp, FH) && !nmp->nm_mountport)) {
+ /* ...connect to portmapper first if we need any ports. */
+ nss.nss_port = PMAPPORT;
+ nss.nss_protocol = PMAPPROG;
+ nss.nss_version = 0;
+ } else {
+ /* ...connect to NFS port first. */
+ nss.nss_port = nmp->nm_nfsport;
+ nss.nss_protocol = NFS_PROG;
+ nss.nss_version = nmp->nm_vers;
+ }
+ }
+ NFS_SOCK_DBG("nfs connect first %s, so type %d port %d prot %d %d\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname, nss.nss_sotype, nss.nss_port,
+ nss.nss_protocol, nss.nss_version);
+ } else {
+ /* we've connected before, just connect to NFS port */
+ if (!nmp->nm_nfsport) {
+ /* need to ask portmapper which port that would be */
+ nss.nss_port = PMAPPORT;
+ nss.nss_protocol = PMAPPROG;
+ nss.nss_version = 0;
+ } else {
+ nss.nss_port = nmp->nm_nfsport;
+ nss.nss_protocol = NFS_PROG;
+ nss.nss_version = nmp->nm_vers;
+ }
+ NFS_SOCK_DBG("nfs connect %s, so type %d port %d prot %d %d\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname, nss.nss_sotype, nss.nss_port,
+ nss.nss_protocol, nss.nss_version);
+ }
+
+ /* Set next location to first valid location. */
+ /* If start location is invalid, find next location. */
+ nss.nss_nextloc = nss.nss_startloc;
+ if ((nss.nss_nextloc.nli_serv >= nmp->nm_locations.nl_locations[nss.nss_nextloc.nli_loc]->nl_servcount) ||
+ (nss.nss_nextloc.nli_addr >= nmp->nm_locations.nl_locations[nss.nss_nextloc.nli_loc]->nl_servers[nss.nss_nextloc.nli_serv]->ns_addrcount)) {
+ nfs_location_next(&nmp->nm_locations, &nss.nss_nextloc);
+ if (!nfs_location_index_cmp(&nss.nss_nextloc, &nss.nss_startloc)) {
+ NFS_SOCK_DBG("nfs connect %s search failed, couldn't find a valid location index\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname);
+ return (ENOENT);
+ }
+ }
+ nss.nss_last = -1;
+
+keepsearching:
+
+ error = nfs_connect_search_loop(nmp, &nss);
+ if (error || !nss.nss_sock) {
+ /* search failed */
+ nfs_socket_search_cleanup(&nss);
+ if (nss.nss_flags & NSS_FALLBACK2PMAP) {
+ tryv4 = 0;
+ NFS_SOCK_DBG("nfs connect %s TCP failed for V4 %d %d, trying PORTMAP\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname, error, nss.nss_error);
+ goto tryagain;
+ }
+
+ if (!error && (nss.nss_sotype == SOCK_STREAM) && !nmp->nm_sotype && (nmp->nm_vers < NFS_VER4)) {
+ /* Try using UDP */
+ sotype = SOCK_DGRAM;
+ savederror = nss.nss_error;
+ NFS_SOCK_DBG("nfs connect %s TCP failed %d %d, trying UDP\n",
+ vfs_statfs(nmp->nm_mountp)->f_mntfromname, error, nss.nss_error);
+ goto tryagain;
+ }
+ if (!error)
+ error = nss.nss_error ? nss.nss_error : ETIMEDOUT;
+ lck_mtx_lock(&nmp->nm_lock);
+ nmp->nm_sockflags &= ~NMSOCK_CONNECTING;
+ nmp->nm_nss = NULL;