X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/6d2010ae8f7a6078e10b361c6962983bab233e0f..15129b1c8dbb3650c63b70adb1cad9af601c6c17:/bsd/nfs/nfs_socket.c diff --git a/bsd/nfs/nfs_socket.c b/bsd/nfs/nfs_socket.c index 71b6e5c44..c163bfa04 100644 --- a/bsd/nfs/nfs_socket.c +++ b/bsd/nfs/nfs_socket.c @@ -107,6 +107,8 @@ #include #include +#define NFS_SOCK_DBG(...) NFS_DBG(NFS_FAC_SOCK, 7, ## __VA_ARGS__) + /* XXX */ boolean_t current_thread_aborted(void); kern_return_t thread_terminate(thread_t); @@ -145,6 +147,12 @@ nfs_sockaddr_cmp(struct sockaddr *sa1, struct sockaddr *sa2) #if NFSCLIENT +int nfs_connect_search_new_socket(struct nfsmount *, struct nfs_socket_search *, struct timeval *); +int nfs_connect_search_socket_connect(struct nfsmount *, struct nfs_socket *, int); +int nfs_connect_search_ping(struct nfsmount *, struct nfs_socket *, struct timeval *); +void nfs_connect_search_socket_found(struct nfsmount *, struct nfs_socket_search *, struct nfs_socket *); +void nfs_connect_search_socket_reap(struct nfsmount *, struct nfs_socket_search *, struct timeval *); +int nfs_connect_search_check(struct nfsmount *, struct nfs_socket_search *, struct timeval *); int nfs_reconnect(struct nfsmount *); int nfs_connect_setup(struct nfsmount *); void nfs_mount_sock_thread(void *, wait_result_t); @@ -157,12 +165,9 @@ void nfs_reqbusy(struct nfsreq *); struct nfsreq *nfs_reqnext(struct nfsreq *); int nfs_wait_reply(struct nfsreq *); void nfs_softterm(struct nfsreq *); - -#ifdef NFS_SOCKET_DEBUGGING -#define NFS_SOCK_DBG(X) printf X -#else -#define NFS_SOCK_DBG(X) -#endif +int nfs_can_squish(struct nfsmount *); +int nfs_is_squishy(struct nfsmount *); +int nfs_is_dead(int, struct nfsmount *); /* * Estimate rto for an nfs rpc sent via. an unreliable datagram. @@ -313,18 +318,18 @@ nfs_connect_upcall(socket_t so, void *arg, __unused int waitflag) int error = 0, recv = 1; if (nso->nso_flags & NSO_CONNECTING) { - NFS_SOCK_DBG(("nfs connect - socket %p upcall - connecting\n", nso)); + 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)); + 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)); + NFS_SOCK_DBG("nfs connect - socket %p upcall\n", nso); nso->nso_flags |= NSO_UPCALL; /* loop while we make error-free progress */ @@ -473,6 +478,8 @@ nfs_socket_create( sinaddr = &((struct sockaddr_in6*)sa)->sin6_addr; if (inet_ntop(sa->sa_family, sinaddr, naddr, sizeof(naddr)) != naddr) strlcpy(naddr, "", sizeof(naddr)); +#else + char naddr[1] = { 0 }; #endif *nsop = NULL; @@ -528,14 +535,14 @@ nfs_socket_create( } if (error) { - NFS_SOCK_DBG(("nfs connect %s error %d creating socket %p %s type %d%s port %d prot %d %d\n", + 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)); + 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", + 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)); + sotype, resvport ? "r" : "", port, protocol, vers); *nsop = nso; } return (error); @@ -563,7 +570,7 @@ nfs_socket_destroy(struct nfs_socket *nso) 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)); + NFS_SOCK_DBG("nfs connect - socket %p destroyed\n", nso); FREE(nso, M_TEMP); } @@ -584,7 +591,7 @@ nfs_socket_options(struct nfsmount *nmp, struct nfs_socket *nso) int on = 1, proto; timeo.tv_usec = 0; - timeo.tv_sec = NMFLAG(nmp, SOFT) ? 5 : 60; + timeo.tv_sec = (NMFLAG(nmp, SOFT) || nfs_can_squish(nmp)) ? 5 : 60; sock_setsockopt(nso->nso_so, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); sock_setsockopt(nso->nso_so, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo)); if (nso->nso_sotype == SOCK_STREAM) { @@ -677,50 +684,52 @@ nfs_socket_search_update_error(struct nfs_socket_search *nss, int error) nss->nss_error = error; } -/* - * Continue the socket search until we have something to report. +/* nfs_connect_search_new_socket: + * Given a socket search structure for an nfs mount try to find a new socket from the set of addresses specified + * by nss. + * + * nss_last is set to -1 at initialization to indicate the first time. Its set to -2 if address was found but + * could not be used or if a socket timed out. */ int -nfs_connect_search_loop(struct nfsmount *nmp, struct nfs_socket_search *nss) +nfs_connect_search_new_socket(struct nfsmount *nmp, struct nfs_socket_search *nss, struct timeval *now) { - struct nfs_socket *nso, *nsonext; - struct timeval now; struct nfs_fs_location *fsl; struct nfs_fs_server *fss; struct sockaddr_storage ss; + struct nfs_socket *nso; char *addrstr; - int error, nomore = 0; + int error = 0; + -loop: - microuptime(&now); - NFS_SOCK_DBG(("nfs connect %s search %ld\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, now.tv_sec)); + NFS_SOCK_DBG("nfs connect %s nss_addrcnt = %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nss->nss_addrcnt); - /* Time to start another socket? */ - while ((nss->nss_last < 0) || (nss->nss_sockcnt == 0) || - ((nss->nss_sockcnt < 4) && (now.tv_sec >= (nss->nss_last + 2)))) { + /* + * while there are addresses and: + * we have no sockets or + * the last address failed and did not produce a socket (nss_last < 0) or + * Its been a while (2 seconds) and we have less than the max number of concurrent sockets to search (4) + * then attempt to create a socket with the current address. + */ + while (nss->nss_addrcnt > 0 && ((nss->nss_last < 0) || (nss->nss_sockcnt == 0) || + ((nss->nss_sockcnt < 4) && (now->tv_sec >= (nss->nss_last + 2))))) { if (nmp->nm_sockflags & NMSOCK_UNMOUNT) return (EINTR); - /* Find the next address to try... */ - /* Have we run out of locations? */ - if (!nomore && (nss->nss_last != -1) && !nfs_location_index_cmp(&nss->nss_nextloc, &nss->nss_startloc)) - nomore = 1; - if (nomore) { - if (nss->nss_last < 0) - nss->nss_last = now.tv_sec; - break; - } /* Can we convert the address to a sockaddr? */ fsl = nmp->nm_locations.nl_locations[nss->nss_nextloc.nli_loc]; fss = fsl->nl_servers[nss->nss_nextloc.nli_serv]; addrstr = fss->ns_addresses[nss->nss_nextloc.nli_addr]; if (!nfs_uaddr2sockaddr(addrstr, (struct sockaddr*)&ss)) { nfs_location_next(&nmp->nm_locations, &nss->nss_nextloc); + nss->nss_addrcnt -= 1; nss->nss_last = -2; continue; } /* Check that socket family is acceptable. */ if (nmp->nm_sofamily && (ss.ss_family != nmp->nm_sofamily)) { nfs_location_next(&nmp->nm_locations, &nss->nss_nextloc); + nss->nss_addrcnt -= 1; nss->nss_last = -2; continue; } @@ -745,136 +754,166 @@ loop: TAILQ_INSERT_TAIL(&nss->nss_socklist, nso, nso_link); nss->nss_sockcnt++; nfs_location_next(&nmp->nm_locations, &nss->nss_nextloc); - - nss->nss_last = now.tv_sec; + nss->nss_addrcnt -= 1; + + nss->nss_last = now->tv_sec; } - /* check each active socket and try to push it along */ - TAILQ_FOREACH(nso, &nss->nss_socklist, nso_link) { + if (nss->nss_addrcnt == 0 && nss->nss_last < 0) + nss->nss_last = now->tv_sec; + + return (error); +} + +/* + * nfs_connect_search_socket_connect: Connect an nfs socket nso for nfsmount nmp. + * If successful set the socket options for the socket as require from the mount. + * + * Assumes: nso->nso_lock is held on entry and return. + */ +int +nfs_connect_search_socket_connect(struct nfsmount *nmp, struct nfs_socket *nso, int verbose) +{ + int error; + + if ((nso->nso_sotype != SOCK_STREAM) && NMFLAG(nmp, NOCONNECT)) { + /* no connection needed, just say it's already connected */ + NFS_SOCK_DBG("nfs connect %s UDP socket %p noconnect\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso); + nso->nso_flags |= NSO_CONNECTED; + nfs_socket_options(nmp, nso); + return (1); /* Socket is connected and setup */ + } else if (!(nso->nso_flags & NSO_CONNECTING)) { + /* initiate the connection */ + nso->nso_flags |= NSO_CONNECTING; + lck_mtx_unlock(&nso->nso_lock); + NFS_SOCK_DBG("nfs connect %s connecting socket %p\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso); + error = sock_connect(nso->nso_so, nso->nso_saddr, MSG_DONTWAIT); lck_mtx_lock(&nso->nso_lock); - if (!(nso->nso_flags & NSO_CONNECTED)) { - if ((nso->nso_sotype != SOCK_STREAM) && NMFLAG(nmp, NOCONNECT)) { - /* no connection needed, just say it's already connected */ - nso->nso_flags |= NSO_CONNECTED; - NFS_SOCK_DBG(("nfs connect %s UDP socket %p noconnect\n", - vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso)); - } else if (!(nso->nso_flags & NSO_CONNECTING)) { - /* initiate the connection */ - nso->nso_flags |= NSO_CONNECTING; - lck_mtx_unlock(&nso->nso_lock); - NFS_SOCK_DBG(("nfs connect %s connecting socket %p\n", - vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso)); - error = sock_connect(nso->nso_so, nso->nso_saddr, MSG_DONTWAIT); - lck_mtx_lock(&nso->nso_lock); - if (error && (error != EINPROGRESS)) { - nso->nso_error = error; - nso->nso_flags |= NSO_DEAD; - lck_mtx_unlock(&nso->nso_lock); - continue; - } - } - if (nso->nso_flags & NSO_CONNECTING) { - /* check the connection */ - if (sock_isconnected(nso->nso_so)) { - NFS_SOCK_DBG(("nfs connect %s socket %p is connected\n", - vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso)); - nso->nso_flags &= ~NSO_CONNECTING; - nso->nso_flags |= NSO_CONNECTED; - } else { - int optlen = sizeof(error); - error = 0; - sock_getsockopt(nso->nso_so, SOL_SOCKET, SO_ERROR, &error, &optlen); - if (error) { /* we got an error on the socket */ - NFS_SOCK_DBG(("nfs connect %s socket %p connection error %d\n", - vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso, error)); - if (nss->nss_flags & NSS_VERBOSE) - log(LOG_INFO, "nfs_connect: socket error %d for %s\n", - error, vfs_statfs(nmp->nm_mountp)->f_mntfromname); - nso->nso_error = error; - nso->nso_flags |= NSO_DEAD; - lck_mtx_unlock(&nso->nso_lock); - continue; - } - } - } - if (nso->nso_flags & NSO_CONNECTED) - nfs_socket_options(nmp, nso); - } - if (!(nso->nso_flags & NSO_CONNECTED)) { - lck_mtx_unlock(&nso->nso_lock); - continue; + if (error && (error != EINPROGRESS)) { + nso->nso_error = error; + nso->nso_flags |= NSO_DEAD; + return (0); } - if (!(nso->nso_flags & (NSO_PINGING|NSO_VERIFIED)) || - ((nso->nso_sotype == SOCK_DGRAM) && (now.tv_sec >= nso->nso_reqtimestamp+2))) { - /* initiate a NULL RPC request */ - uint64_t xid = nso->nso_pingxid; - mbuf_t m, mreq = NULL; - struct msghdr msg; - size_t reqlen, sentlen; - uint32_t vers; - - if (!(vers = nso->nso_version)) { - if (nso->nso_protocol == PMAPPROG) - vers = (nso->nso_saddr->sa_family == AF_INET) ? PMAPVERS : RPCBVERS4; - else if (nso->nso_protocol == NFS_PROG) - vers = NFS_VER3; - } - lck_mtx_unlock(&nso->nso_lock); - error = nfsm_rpchead2(nmp, nso->nso_sotype, nso->nso_protocol, vers, 0, RPCAUTH_SYS, - vfs_context_ucred(vfs_context_kernel()), NULL, NULL, &xid, &mreq); - lck_mtx_lock(&nso->nso_lock); - if (!error) { - nso->nso_flags |= NSO_PINGING; - nso->nso_pingxid = R_XID32(xid); - nso->nso_reqtimestamp = now.tv_sec; - bzero(&msg, sizeof(msg)); - if ((nso->nso_sotype != SOCK_STREAM) && !sock_isconnected(nso->nso_so)) { - msg.msg_name = nso->nso_saddr; - msg.msg_namelen = nso->nso_saddr->sa_len; - } - for (reqlen=0, m=mreq; m; m = mbuf_next(m)) - reqlen += mbuf_len(m); - lck_mtx_unlock(&nso->nso_lock); - error = sock_sendmbuf(nso->nso_so, &msg, mreq, 0, &sentlen); - NFS_SOCK_DBG(("nfs connect %s verifying socket %p send rv %d\n", - vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso, error)); - lck_mtx_lock(&nso->nso_lock); - if (!error && (sentlen != reqlen)) - error = ETIMEDOUT; - } - if (error) { + } + if (nso->nso_flags & NSO_CONNECTING) { + /* check the connection */ + if (sock_isconnected(nso->nso_so)) { + NFS_SOCK_DBG("nfs connect %s socket %p is connected\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso); + nso->nso_flags &= ~NSO_CONNECTING; + nso->nso_flags |= NSO_CONNECTED; + nfs_socket_options(nmp, nso); + return (1); /* Socket is connected and setup */ + } else { + int optlen = sizeof(error); + error = 0; + sock_getsockopt(nso->nso_so, SOL_SOCKET, SO_ERROR, &error, &optlen); + if (error) { /* we got an error on the socket */ + NFS_SOCK_DBG("nfs connect %s socket %p connection error %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso, error); + if (verbose) + printf("nfs connect socket error %d for %s\n", + error, vfs_statfs(nmp->nm_mountp)->f_mntfromname); nso->nso_error = error; nso->nso_flags |= NSO_DEAD; - lck_mtx_unlock(&nso->nso_lock); - continue; + return (0); } } - if (nso->nso_flags & NSO_VERIFIED) { - /* WOOHOO!! This socket looks good! */ - 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 = NFS_VER3; - } - lck_mtx_unlock(&nso->nso_lock); - TAILQ_REMOVE(&nss->nss_socklist, nso, nso_link); - nss->nss_sockcnt--; - nss->nss_sock = nso; - break; + } + + return (0); /* Waiting to be connected */ +} + +/* + * nfs_connect_search_ping: Send a null proc on the nso socket. + */ +int +nfs_connect_search_ping(struct nfsmount *nmp, struct nfs_socket *nso, struct timeval *now) +{ + /* initiate a NULL RPC request */ + uint64_t xid = nso->nso_pingxid; + mbuf_t m, mreq = NULL; + struct msghdr msg; + size_t reqlen, sentlen; + uint32_t vers = nso->nso_version; + int error; + + if (!vers) { + if (nso->nso_protocol == PMAPPROG) + vers = (nso->nso_saddr->sa_family == AF_INET) ? PMAPVERS : RPCBVERS4; + else if (nso->nso_protocol == NFS_PROG) + vers = NFS_VER3; + } + lck_mtx_unlock(&nso->nso_lock); + error = nfsm_rpchead2(nmp, nso->nso_sotype, nso->nso_protocol, vers, 0, RPCAUTH_SYS, + vfs_context_ucred(vfs_context_kernel()), NULL, NULL, &xid, &mreq); + lck_mtx_lock(&nso->nso_lock); + if (!error) { + nso->nso_flags |= NSO_PINGING; + nso->nso_pingxid = R_XID32(xid); + nso->nso_reqtimestamp = now->tv_sec; + bzero(&msg, sizeof(msg)); + if ((nso->nso_sotype != SOCK_STREAM) && !sock_isconnected(nso->nso_so)) { + msg.msg_name = nso->nso_saddr; + msg.msg_namelen = nso->nso_saddr->sa_len; } + for (reqlen=0, m=mreq; m; m = mbuf_next(m)) + reqlen += mbuf_len(m); lck_mtx_unlock(&nso->nso_lock); + error = sock_sendmbuf(nso->nso_so, &msg, mreq, 0, &sentlen); + NFS_SOCK_DBG("nfs connect %s verifying socket %p send rv %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso, error); + lck_mtx_lock(&nso->nso_lock); + if (!error && (sentlen != reqlen)) + error = ETIMEDOUT; } + if (error) { + 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 __unused, 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 = NFS_VER3; + } + 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)) { + 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)); + 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; } @@ -883,38 +922,112 @@ loop: 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_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 (!nomore) + /* 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) || !nomore)) { - /* log a warning if connect is taking a while */ - if (((now.tv_sec - nss->nss_timestamp) >= 30) && ((nss->nss_flags & (NSS_VERBOSE|NSS_WARNED)) == NSS_VERBOSE)) { - log(LOG_INFO, "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 (nss->nss_last >= 0) - tsleep(nss, PSOCK, "nfs_connect_search_wait", hz); - goto loop; + 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)); + NFS_SOCK_DBG("nfs connect %s returning %d\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, error); return (error); } @@ -947,25 +1060,27 @@ nfs_connect(struct nfsmount *nmp, int verbose, int timeo) 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++) { - if (nmp->nm_locations.nl_locations[loc]->nl_servers[serv]->ns_addrcount) - break; - 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)); + 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 (serv < nmp->nm_locations.nl_locations[loc]->nl_servcount) - break; } - if (loc >= nmp->nm_locations.nl_numlocs) { - NFS_SOCK_DBG(("nfs connect %s search failed, no addresses\n", - vfs_statfs(nmp->nm_mountp)->f_mntfromname)); + + 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; @@ -977,6 +1092,7 @@ nfs_connect(struct nfsmount *nmp, int verbose, int timeo) 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; @@ -1020,9 +1136,9 @@ tryagain: nss.nss_version = nmp->nm_vers; } } - NFS_SOCK_DBG(("nfs connect first %s, so type %d port %d prot %d %d\n", + 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)); + nss.nss_protocol, nss.nss_version); } else { /* we've connected before, just connect to NFS port */ if (!nmp->nm_nfsport) { @@ -1035,9 +1151,9 @@ tryagain: 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", + 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)); + nss.nss_protocol, nss.nss_version); } /* Set next location to first valid location. */ @@ -1047,8 +1163,8 @@ tryagain: (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)); + 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); } } @@ -1064,8 +1180,8 @@ keepsearching: /* 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)); + 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) @@ -1081,8 +1197,8 @@ keepsearching: FREE(fh, M_TEMP); if (path) FREE_ZONE(path, MAXPATHLEN, M_NAMEI); - NFS_SOCK_DBG(("nfs connect %s search failed, returning %d\n", - vfs_statfs(nmp->nm_mountp)->f_mntfromname, error)); + NFS_SOCK_DBG("nfs connect %s search failed, returning %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, error); return (error); } @@ -1097,8 +1213,8 @@ keepsearching: port = ntohs(((struct sockaddr_in6*)nso->nso_saddr)->sin6_port); if (port == PMAPPORT) { /* Use this portmapper port to get the port #s we need. */ - NFS_SOCK_DBG(("nfs connect %s got portmapper socket %p\n", - vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso)); + NFS_SOCK_DBG("nfs connect %s got portmapper socket %p\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso); /* remove the connect upcall so nfs_portmap_lookup() can use this socket */ sock_setupcall(nso->nso_so, NULL, NULL); @@ -1115,7 +1231,7 @@ keepsearching: else if (ss.ss_family == AF_INET6) ((struct sockaddr_in6*)&ss)->sin6_port = htons(0); error = nfs_portmap_lookup(nmp, vfs_context_current(), (struct sockaddr*)&ss, - nso->nso_so, NFS_PROG, nfsvers, + nso->nso_so, NFS_PROG, nfsvers, (nso->nso_sotype == SOCK_DGRAM) ? IPPROTO_UDP : IPPROTO_TCP, timeo); if (!error) { if (ss.ss_family == AF_INET) @@ -1128,7 +1244,7 @@ keepsearching: if (error && !nmp->nm_vers) { nfsvers = NFS_VER2; error = nfs_portmap_lookup(nmp, vfs_context_current(), (struct sockaddr*)&ss, - nso->nso_so, NFS_PROG, nfsvers, + nso->nso_so, NFS_PROG, nfsvers, (nso->nso_sotype == SOCK_DGRAM) ? IPPROTO_UDP : IPPROTO_TCP, timeo); if (!error) { if (ss.ss_family == AF_INET) @@ -1208,7 +1324,7 @@ keepsearching: } /* nso is an NFS socket */ - NFS_SOCK_DBG(("nfs connect %s got NFS socket %p\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso)); + NFS_SOCK_DBG("nfs connect %s got NFS socket %p\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso); /* If NFS version wasn't specified, it was determined during the connect. */ nfsvers = nmp->nm_vers ? nmp->nm_vers : (int)nso->nso_version; @@ -1246,7 +1362,7 @@ keepsearching: if (saddr) MALLOC(fh, fhandle_t *, sizeof(fhandle_t), M_TEMP, M_WAITOK|M_ZERO); if (saddr && fh) - MALLOC_ZONE(path, char *, MAXPATHLEN, M_NAMEI, M_WAITOK); + MALLOC_ZONE(path, char *, MAXPATHLEN, M_NAMEI, M_WAITOK); if (!saddr || !fh || !path) { if (!error) error = ENOMEM; @@ -1263,8 +1379,8 @@ keepsearching: nfs_location_mntfromname(&nmp->nm_locations, nso->nso_location, path, MAXPATHLEN, 1); error = nfs3_mount_rpc(nmp, saddr, nso->nso_sotype, nfsvers, path, vfs_context_current(), timeo, fh, &nmp->nm_servsec); - NFS_SOCK_DBG(("nfs connect %s socket %p mount %d\n", - vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso, error)); + NFS_SOCK_DBG("nfs connect %s socket %p mount %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso, error); if (!error) { /* Make sure we can agree on a security flavor. */ int o, s; /* indices into mount option and server security flavor lists */ @@ -1397,8 +1513,8 @@ keepsearching: wakeup(&nmp->nm_sockflags); } if (error) { - NFS_SOCK_DBG(("nfs connect %s socket %p setup failed %d\n", - vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso, error)); + NFS_SOCK_DBG("nfs connect %s socket %p setup failed %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, nso, error); nfs_socket_search_update_error(&nss, error); nmp->nm_saddr = oldsaddr; if (!(nmp->nm_sockflags & NMSOCK_HASCONNECTED)) { @@ -1452,7 +1568,7 @@ keepsearching: FREE(fh, M_TEMP); if (path) FREE_ZONE(path, MAXPATHLEN, M_NAMEI); - NFS_SOCK_DBG(("nfs connect %s success\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname)); + NFS_SOCK_DBG("nfs connect %s success\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname); return (0); } @@ -1498,13 +1614,19 @@ nfs_reconnect(struct nfsmount *nmp) thread_t thd = current_thread(); int error, wentdown = 0, verbose = 1; time_t lastmsg; + int timeo; microuptime(&now); lastmsg = now.tv_sec - (nmp->nm_tprintf_delay - nmp->nm_tprintf_initial_delay); nfs_disconnect(nmp); - while ((error = nfs_connect(nmp, verbose, 30))) { + + lck_mtx_lock(&nmp->nm_lock); + timeo = nfs_is_squishy(nmp) ? 8 : 30; + lck_mtx_unlock(&nmp->nm_lock); + + while ((error = nfs_connect(nmp, verbose, timeo))) { verbose = 0; nfs_disconnect(nmp); if ((error == EINTR) || (error == ERESTART)) @@ -1522,6 +1644,7 @@ nfs_reconnect(struct nfsmount *nmp) /* we're not yet completely mounted and */ /* we can't reconnect, so we fail */ lck_mtx_unlock(&nmp->nm_lock); + NFS_SOCK_DBG("Not mounted returning %d\n", error); return (error); } nfs_mount_check_dead_timeout(nmp); @@ -1530,7 +1653,7 @@ nfs_reconnect(struct nfsmount *nmp) return (error); } lck_mtx_unlock(&nmp->nm_lock); - tsleep(&lbolt, PSOCK, "nfs_reconnect_delay", 0); + tsleep(nfs_reconnect, PSOCK, "nfs_reconnect_delay", 2*hz); if ((error = nfs_sigintr(nmp, NULL, thd, 0))) return (error); } @@ -1645,6 +1768,7 @@ nfs_mount_sock_thread(void *arg, __unused wait_result_t wr) struct timeval now; int error, dofinish; nfsnode_t np; + int do_reconnect_sleep = 0; lck_mtx_lock(&nmp->nm_lock); @@ -1664,9 +1788,26 @@ nfs_mount_sock_thread(void *arg, __unused wait_result_t wr) nmp->nm_reconnect_start = now.tv_sec; } lck_mtx_unlock(&nmp->nm_lock); - NFS_SOCK_DBG(("nfs reconnect %s\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname)); - if (nfs_reconnect(nmp) == 0) + NFS_SOCK_DBG("nfs reconnect %s\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname); + /* + * XXX We don't want to call reconnect again right away if returned errors + * before that may not have blocked. This has caused spamming null procs + * from machines in the pass. + */ + if (do_reconnect_sleep) + tsleep(nfs_mount_sock_thread, PSOCK, "nfs_reconnect_sock_thread_delay", hz); + error = nfs_reconnect(nmp); + if (error) { + int lvl = 7; + if (error == EIO || error == EINTR) { + lvl = (do_reconnect_sleep++ % 600) ? 7 : 0; + } + nfs_printf(NFS_FAC_SOCK, lvl, "nfs reconnect %s: returned %d\n", + vfs_statfs(nmp->nm_mountp)->f_mntfromname, error); + } else { nmp->nm_reconnect_start = 0; + do_reconnect_sleep = 0; + } lck_mtx_lock(&nmp->nm_lock); } if ((nmp->nm_sockflags & NMSOCK_READY) && @@ -1721,9 +1862,9 @@ nfs_mount_sock_thread(void *arg, __unused wait_result_t wr) if (error == ENEEDAUTH) req->r_xid = 0; } - NFS_SOCK_DBG(("nfs async%s restart: p %d x 0x%llx f 0x%x rtt %d\n", + NFS_SOCK_DBG("nfs async%s restart: p %d x 0x%llx f 0x%x rtt %d\n", nfs_request_using_gss(req) ? " gss" : "", req->r_procnum, req->r_xid, - req->r_flags, req->r_rtt)); + req->r_flags, req->r_rtt); error = !req->r_nmp ? ENXIO : 0; /* unmounted? */ if (!error) error = nfs_sigintr(nmp, req, req->r_thread, 0); @@ -1745,8 +1886,8 @@ nfs_mount_sock_thread(void *arg, __unused wait_result_t wr) error = 0; continue; } - NFS_SOCK_DBG(("nfs async resend: p %d x 0x%llx f 0x%x rtt %d\n", - req->r_procnum, req->r_xid, req->r_flags, req->r_rtt)); + NFS_SOCK_DBG("nfs async resend: p %d x 0x%llx f 0x%x rtt %d\n", + req->r_procnum, req->r_xid, req->r_flags, req->r_rtt); error = !req->r_nmp ? ENXIO : 0; /* unmounted? */ if (!error) error = nfs_sigintr(nmp, req, req->r_thread, 0); @@ -1849,16 +1990,18 @@ nfs_mount_check_dead_timeout(struct nfsmount *nmp) { struct timeval now; - if (nmp->nm_deadtimeout <= 0) - return; if (nmp->nm_deadto_start == 0) return; if (nmp->nm_state & NFSSTA_DEAD) return; + nfs_is_squishy(nmp); + if (nmp->nm_curdeadtimeout <= 0) + return; microuptime(&now); - if ((now.tv_sec - nmp->nm_deadto_start) < nmp->nm_deadtimeout) + if ((now.tv_sec - nmp->nm_deadto_start) < nmp->nm_curdeadtimeout) return; - printf("nfs server %s: dead\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname); + printf("nfs server %s: %sdead\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, + (nmp->nm_curdeadtimeout != nmp->nm_deadtimeout) ? "squished " : ""); nmp->nm_state |= NFSSTA_DEAD; vfs_event_signal(&vfs_statfs(nmp->nm_mountp)->f_fsid, VQ_DEAD, 0); } @@ -2360,7 +2503,7 @@ nfs4_cb_handler(struct nfs_callback_socket *ncbsp, mbuf_t mreq) status = error; else if ((error == ENOBUFS) || (error == ENOMEM)) status = NFSERR_RESOURCE; - else + else status = NFSERR_SERVERFAULT; error = 0; nfsm_chain_null(&nmrep); @@ -2508,7 +2651,7 @@ nfs4_cb_handler(struct nfs_callback_socket *ncbsp, mbuf_t mreq) status = error; else if ((error == ENOBUFS) || (error == ENOMEM)) status = NFSERR_RESOURCE; - else + else status = NFSERR_SERVERFAULT; error = 0; } @@ -2529,7 +2672,7 @@ nfs4_cb_handler(struct nfs_callback_socket *ncbsp, mbuf_t mreq) nfsmout: if (status == EBADRPC) - OSAddAtomic(1, &nfsstats.rpcinvalid); + OSAddAtomic64(1, &nfsstats.rpcinvalid); /* build reply header */ error = mbuf_gethdr(MBUF_WAITOK, MBUF_TYPE_DATA, &mhead); @@ -2821,7 +2964,7 @@ again: lck_mtx_unlock(&req->r_mtx); return (0); } - NFS_SOCK_DBG(("nfs_send: 0x%llx wait reconnect\n", req->r_xid)); + NFS_SOCK_DBG("nfs_send: 0x%llx wait reconnect\n", req->r_xid); lck_mtx_lock(&req->r_mtx); req->r_flags &= ~R_MUSTRESEND; req->r_rtt = 0; @@ -2838,7 +2981,7 @@ again: microuptime(&now); if ((now.tv_sec - nmp->nm_reconnect_start) >= 8) { /* soft mount in reconnect for a while... terminate ASAP */ - OSAddAtomic(1, &nfsstats.rpctimeouts); + OSAddAtomic64(1, &nfsstats.rpctimeouts); req->r_flags |= R_SOFTTERM; req->r_error = error = ETIMEDOUT; break; @@ -2918,7 +3061,7 @@ again: } else { /* * When retransmitting, turn timing off - * and divide congestion window by 2. + * and divide congestion window by 2. */ req->r_flags &= ~R_TIMING; nmp->nm_cwnd >>= 1; @@ -2953,8 +3096,8 @@ again: error = sock_sendmbuf(nso->nso_so, &msg, mreqcopy, 0, &sentlen); #ifdef NFS_SOCKET_DEBUGGING if (error || (sentlen != req->r_mreqlen)) - NFS_SOCK_DBG(("nfs_send: 0x%llx sent %d/%d error %d\n", - req->r_xid, (int)sentlen, (int)req->r_mreqlen, error)); + NFS_SOCK_DBG("nfs_send: 0x%llx sent %d/%d error %d\n", + req->r_xid, (int)sentlen, (int)req->r_mreqlen, error); #endif if (!error && (sentlen != req->r_mreqlen)) error = EWOULDBLOCK; @@ -2970,7 +3113,7 @@ again: /* SUCCESS */ req->r_flags &= ~R_RESENDERR; if (rexmit) - OSAddAtomic(1, &nfsstats.rpcretries); + OSAddAtomic64(1, &nfsstats.rpcretries); req->r_flags |= R_SENT; if (req->r_flags & R_WAITSENT) { req->r_flags &= ~R_WAITSENT; @@ -3005,8 +3148,8 @@ again: sock_getsockopt(nso->nso_so, SOL_SOCKET, SO_ERROR, &clearerror, &optlen); #ifdef NFS_SOCKET_DEBUGGING if (clearerror) - NFS_SOCK_DBG(("nfs_send: ignoring UDP socket error %d so %d\n", - error, clearerror)); + NFS_SOCK_DBG("nfs_send: ignoring UDP socket error %d so %d\n", + error, clearerror); #endif } } @@ -3033,7 +3176,7 @@ again: break; } if (needrecon && (nso == nmp->nm_nso)) { /* mark socket as needing reconnect */ - NFS_SOCK_DBG(("nfs_send: 0x%llx need reconnect %d\n", req->r_xid, error)); + NFS_SOCK_DBG("nfs_send: 0x%llx need reconnect %d\n", req->r_xid, error); nfs_need_reconnect(nmp); } @@ -3052,6 +3195,9 @@ again: !req->r_nmp ? "" : vfs_statfs(req->r_nmp->nm_mountp)->f_mntfromname); + if (nfs_is_dead(error, nmp)) + error = EIO; + /* prefer request termination error over other errors */ error2 = nfs_sigintr(req->r_nmp, req, req->r_thread, 0); if (error2) @@ -3104,7 +3250,7 @@ nfs_udp_rcv(socket_t so, void *arg, __unused int waitflag) if (error && (error != EWOULDBLOCK)) { /* problems with the socket... mark for reconnection */ - NFS_SOCK_DBG(("nfs_udp_rcv: need reconnect %d\n", error)); + NFS_SOCK_DBG("nfs_udp_rcv: need reconnect %d\n", error); nfs_need_reconnect(nmp); } } @@ -3164,12 +3310,12 @@ nfs_tcp_rcv(socket_t so, void *arg, __unused int waitflag) } #ifdef NFS_SOCKET_DEBUGGING if (!recv && (error != EWOULDBLOCK)) - NFS_SOCK_DBG(("nfs_tcp_rcv: got nothing, error %d, got FIN?\n", error)); + NFS_SOCK_DBG("nfs_tcp_rcv: got nothing, error %d, got FIN?\n", error); #endif /* note: no error and no data indicates server closed its end */ if ((error != EWOULDBLOCK) && (error || !recv)) { /* problems with the socket... mark for reconnection */ - NFS_SOCK_DBG(("nfs_tcp_rcv: need reconnect %d\n", error)); + NFS_SOCK_DBG("nfs_tcp_rcv: need reconnect %d\n", error); nfs_need_reconnect(nmp); } } @@ -3200,7 +3346,8 @@ nfs_sock_poke(struct nfsmount *nmp) msg.msg_iov = &aio; msg.msg_iovlen = 1; error = sock_send(nmp->nm_nso->nso_so, &msg, MSG_DONTWAIT, &len); - NFS_SOCK_DBG(("nfs_sock_poke: error %d\n", error)); + NFS_SOCK_DBG("nfs_sock_poke: error %d\n", error); + nfs_is_dead(error, nmp); } /* @@ -3219,7 +3366,7 @@ nfs_request_match_reply(struct nfsmount *nmp, mbuf_t mrep) nfsm_chain_get_32(error, &nmrep, rxid); nfsm_chain_get_32(error, &nmrep, reply); if (error || (reply != RPC_REPLY)) { - OSAddAtomic(1, &nfsstats.rpcinvalid); + OSAddAtomic64(1, &nfsstats.rpcinvalid); mbuf_freem(mrep); return; } @@ -3307,7 +3454,7 @@ nfs_request_match_reply(struct nfsmount *nmp, mbuf_t mrep) if (!req) { /* not matched to a request, so drop it. */ lck_mtx_unlock(nfs_request_mutex); - OSAddAtomic(1, &nfsstats.rpcunexpected); + OSAddAtomic64(1, &nfsstats.rpcunexpected); mbuf_freem(mrep); } } @@ -3335,8 +3482,8 @@ nfs_wait_reply(struct nfsreq *req) break; /* check if we need to resend */ if (req->r_flags & R_MUSTRESEND) { - NFS_SOCK_DBG(("nfs wait resend: p %d x 0x%llx f 0x%x rtt %d\n", - req->r_procnum, req->r_xid, req->r_flags, req->r_rtt)); + NFS_SOCK_DBG("nfs wait resend: p %d x 0x%llx f 0x%x rtt %d\n", + req->r_procnum, req->r_xid, req->r_flags, req->r_rtt); req->r_flags |= R_SENDING; lck_mtx_unlock(&req->r_mtx); if (nfs_request_using_gss(req)) { @@ -3353,8 +3500,8 @@ nfs_wait_reply(struct nfsreq *req) } error = nfs_send(req, 1); lck_mtx_lock(&req->r_mtx); - NFS_SOCK_DBG(("nfs wait resend: p %d x 0x%llx f 0x%x rtt %d err %d\n", - req->r_procnum, req->r_xid, req->r_flags, req->r_rtt, error)); + NFS_SOCK_DBG("nfs wait resend: p %d x 0x%llx f 0x%x rtt %d err %d\n", + req->r_procnum, req->r_xid, req->r_flags, req->r_rtt, error); if (error) break; if (((error = req->r_error)) || req->r_nmrep.nmc_mhead) @@ -3443,7 +3590,7 @@ nfs_request_create( } if ((nmp->nm_vers != NFS_VER4) && (procnum >= 0) && (procnum < NFS_NPROCS)) - OSAddAtomic(1, &nfsstats.rpccnt[procnum]); + OSAddAtomic64(1, &nfsstats.rpccnt[procnum]); if ((nmp->nm_vers == NFS_VER4) && (procnum != NFSPROC4_COMPOUND) && (procnum != NFSPROC4_NULL)) panic("nfs_request: invalid NFSv4 RPC request %d\n", procnum); @@ -3667,7 +3814,7 @@ nfs_request_send(struct nfsreq *req, int wait) ((nmp->nm_tprintf_delay) - (nmp->nm_tprintf_initial_delay)); } - OSAddAtomic(1, &nfsstats.rpcrequests); + OSAddAtomic64(1, &nfsstats.rpcrequests); /* * Chain request into list of outstanding requests. Be sure @@ -3884,7 +4031,7 @@ nfs_request_finish( if ((req->r_delay >= 30) && !(nmp->nm_state & NFSSTA_MOUNTED)) { /* we're not yet completely mounted and */ /* we can't complete an RPC, so we fail */ - OSAddAtomic(1, &nfsstats.rpctimeouts); + OSAddAtomic64(1, &nfsstats.rpctimeouts); nfs_softterm(req); error = req->r_error; goto nfsmout; @@ -3904,7 +4051,7 @@ nfs_request_finish( } if (NMFLAG(nmp, SOFT) && (req->r_delay == 30) && !(req->r_flags & R_NOINTR)) { /* for soft mounts, just give up after a short while */ - OSAddAtomic(1, &nfsstats.rpctimeouts); + OSAddAtomic64(1, &nfsstats.rpctimeouts); nfs_softterm(req); error = req->r_error; goto nfsmout; @@ -3918,7 +4065,7 @@ nfs_request_finish( do { if ((error = nfs_sigintr(req->r_nmp, req, req->r_thread, 0))) goto nfsmout; - tsleep(&lbolt, PSOCK|slpflag, "nfs_jukebox_trylater", 0); + tsleep(nfs_request_finish, PSOCK|slpflag, "nfs_jukebox_trylater", hz); slpflag = 0; } while (--delay > 0); } @@ -4174,7 +4321,7 @@ nfs_request2( * server. Associate the context that we are setting up with the request that we * are sending. */ - + int nfs_request_gss( mount_t mp, @@ -4192,7 +4339,7 @@ nfs_request_gss( if ((error = nfs_request_create(NULL, mp, nmrest, NFSPROC_NULL, thd, cred, &req))) return (error); req->r_flags |= (flags & R_OPTMASK); - + if (cp == NULL) { printf("nfs_request_gss request has no context\n"); nfs_request_rele(req); @@ -4218,7 +4365,7 @@ nfs_request_gss( nfs_request_rele(req); return (error); } - + /* * Create and start an asynchronous NFS request. */ @@ -4533,7 +4680,7 @@ nfs_request_timer(__unused void *param0, __unused void *param1) lck_mtx_unlock(&nmp->nm_lock); /* we're not yet completely mounted and */ /* we can't complete an RPC, so we fail */ - OSAddAtomic(1, &nfsstats.rpctimeouts); + OSAddAtomic64(1, &nfsstats.rpctimeouts); nfs_softterm(req); finish_asyncio = ((req->r_callback.rcb_func != NULL) && !(req->r_flags & R_WAITSENT)); wakeup(req); @@ -4549,10 +4696,10 @@ nfs_request_timer(__unused void *param0, __unused void *param1) * Put a reasonable limit on the maximum timeout, * and reduce that limit when soft mounts get timeouts or are in reconnect. */ - if (!NMFLAG(nmp, SOFT)) + if (!NMFLAG(nmp, SOFT) && !nfs_can_squish(nmp)) maxtime = NFS_MAXTIMEO; else if ((req->r_flags & (R_SETUP|R_RECOVER)) || - ((nmp->nm_reconnect_start <= 0) || ((now.tv_sec - nmp->nm_reconnect_start) < 8))) + ((nmp->nm_reconnect_start <= 0) || ((now.tv_sec - nmp->nm_reconnect_start) < 8))) maxtime = (NFS_MAXTIMEO / (nmp->nm_timeouts+1))/2; else maxtime = NFS_MINTIMEO/4; @@ -4589,10 +4736,10 @@ nfs_request_timer(__unused void *param0, __unused void *param1) continue; } /* The request has timed out */ - NFS_SOCK_DBG(("nfs timeout: proc %d %d xid %llx rtt %d to %d # %d, t %ld/%d\n", + NFS_SOCK_DBG("nfs timeout: proc %d %d xid %llx rtt %d to %d # %d, t %ld/%d\n", req->r_procnum, proct[req->r_procnum], req->r_xid, req->r_rtt, timeo, nmp->nm_timeouts, - (now.tv_sec - req->r_start)*NFS_HZ, maxtime)); + (now.tv_sec - req->r_start)*NFS_HZ, maxtime); if (nmp->nm_timeouts < 8) nmp->nm_timeouts++; nfs_mount_check_dead_timeout(nmp); @@ -4608,10 +4755,10 @@ nfs_request_timer(__unused void *param0, __unused void *param1) } /* For soft mounts (& SETUPs/RECOVERs), check for too many retransmits/timeout. */ - if ((NMFLAG(nmp, SOFT) || (req->r_flags & (R_SETUP|R_RECOVER))) && + if ((NMFLAG(nmp, SOFT) || (req->r_flags & (R_SETUP|R_RECOVER))) && ((req->r_rexmit >= req->r_retry) || /* too many */ ((now.tv_sec - req->r_start)*NFS_HZ > maxtime))) { /* too long */ - OSAddAtomic(1, &nfsstats.rpctimeouts); + OSAddAtomic64(1, &nfsstats.rpctimeouts); lck_mtx_lock(&nmp->nm_lock); if (!(nmp->nm_state & NFSSTA_TIMEO)) { lck_mtx_unlock(&nmp->nm_lock); @@ -4629,9 +4776,9 @@ nfs_request_timer(__unused void *param0, __unused void *param1) lck_mtx_unlock(&req->r_mtx); continue; } - NFS_SOCK_DBG(("nfs timer TERMINATE: p %d x 0x%llx f 0x%x rtt %d t %ld\n", + NFS_SOCK_DBG("nfs timer TERMINATE: p %d x 0x%llx f 0x%x rtt %d t %ld\n", req->r_procnum, req->r_xid, req->r_flags, req->r_rtt, - now.tv_sec - req->r_start)); + now.tv_sec - req->r_start); nfs_softterm(req); finish_asyncio = ((req->r_callback.rcb_func != NULL) && !(req->r_flags & R_WAITSENT)); wakeup(req); @@ -4658,8 +4805,8 @@ nfs_request_timer(__unused void *param0, __unused void *param1) lck_mtx_unlock(&req->r_mtx); continue; } - NFS_SOCK_DBG(("nfs timer mark resend: p %d x 0x%llx f 0x%x rtt %d\n", - req->r_procnum, req->r_xid, req->r_flags, req->r_rtt)); + NFS_SOCK_DBG("nfs timer mark resend: p %d x 0x%llx f 0x%x rtt %d\n", + req->r_procnum, req->r_xid, req->r_flags, req->r_rtt); req->r_flags |= R_MUSTRESEND; req->r_rtt = -1; wakeup(req); @@ -4735,7 +4882,8 @@ nfs_sigintr(struct nfsmount *nmp, struct nfsreq *req, thread_t thd, int nmplocke * If the mount is hung and we've requested not to hang * on remote filesystems, then bail now. */ - if (!error && (nmp->nm_state & NFSSTA_TIMEO) && nfs_noremotehang(thd)) + if (current_proc() != kernproc && + !error && (nmp->nm_state & NFSSTA_TIMEO) && nfs_noremotehang(thd)) error = EIO; if (!nmplocked) @@ -4744,7 +4892,7 @@ nfs_sigintr(struct nfsmount *nmp, struct nfsreq *req, thread_t thd, int nmplocke return (error); /* may not have a thread for async I/O */ - if (thd == NULL) + if (thd == NULL || current_proc() == kernproc) return (0); /* @@ -5037,7 +5185,7 @@ nfs_portmap_lookup( pmvers = RPCBVERS4; pmproc = RPCBPROC_GETVERSADDR; } else { - return (EINVAL); + return (EINVAL); } nfsm_chain_null(&nmreq); nfsm_chain_null(&nmrep); @@ -5140,6 +5288,145 @@ nfs_msg(thread_t thd, return (0); } +#define NFS_SQUISH_MOBILE_ONLY 0x0001 /* Squish mounts only on mobile machines */ +#define NFS_SQUISH_AUTOMOUNTED_ONLY 0x0002 /* Squish mounts only if the are automounted */ +#define NFS_SQUISH_SOFT 0x0004 /* Treat all soft mounts as though they were on a mobile machine */ +#define NFS_SQUISH_QUICK 0x0008 /* Try to squish mounts more quickly. */ +#define NFS_SQUISH_SHUTDOWN 0x1000 /* Squish all mounts on shutdown. Currently not implemented */ + +uint32_t nfs_squishy_flags = NFS_SQUISH_MOBILE_ONLY | NFS_SQUISH_AUTOMOUNTED_ONLY | NFS_SQUISH_QUICK; +int32_t nfs_is_mobile; + +#define NFS_SQUISHY_DEADTIMEOUT 8 /* Dead time out for squishy mounts */ +#define NFS_SQUISHY_QUICKTIMEOUT 4 /* Quicker dead time out when nfs_squish_flags NFS_SQUISH_QUICK bit is set*/ + +/* + * Could this mount be squished? + */ +int +nfs_can_squish(struct nfsmount *nmp) +{ + uint64_t flags = vfs_flags(nmp->nm_mountp); + int softsquish = ((nfs_squishy_flags & NFS_SQUISH_SOFT) & NMFLAG(nmp, SOFT)); + + if (!softsquish && (nfs_squishy_flags & NFS_SQUISH_MOBILE_ONLY) && nfs_is_mobile == 0) + return (0); + + if ((nfs_squishy_flags & NFS_SQUISH_AUTOMOUNTED_ONLY) && (flags & MNT_AUTOMOUNTED) == 0) + return (0); + + return (1); +} + +/* + * NFS mounts default to "rw,hard" - but frequently on mobile clients + * the mount may become "not responding". It's desirable to be able + * to unmount these dead mounts, but only if there is no risk of + * losing data or crashing applications. A "squishy" NFS mount is one + * that can be force unmounted with little risk of harm. + * + * nfs_is_squishy checks if a mount is in a squishy state. A mount is + * in a squishy state iff it is allowed to be squishy and there are no + * dirty pages and there are no mmapped files and there are no files + * open for write. Mounts are allowed to be squishy is controlled by + * the settings of the nfs_squishy_flags and its mobility state. These + * flags can be set by sysctls. + * + * If nfs_is_squishy determines that we are in a squishy state we will + * update the current dead timeout to at least NFS_SQUISHY_DEADTIMEOUT + * (or NFS_SQUISHY_QUICKTIMEOUT if NFS_SQUISH_QUICK is set) (see + * above) or 1/8th of the mount's nm_deadtimeout value, otherwise we just + * update the current dead timeout with the mount's nm_deadtimeout + * value set at mount time. + * + * Assumes that nm_lock is held. + * + * Note this routine is racey, but its effects on setting the + * dead timeout only have effects when we're in trouble and are likely + * to stay that way. Since by default its only for automounted + * volumes on mobile machines; this is a reasonable trade off between + * data integrity and user experience. It can be disabled or set via + * nfs.conf file. + */ + +int +nfs_is_squishy(struct nfsmount *nmp) +{ + mount_t mp = nmp->nm_mountp; + int squishy = 0; + int timeo = (nfs_squishy_flags & NFS_SQUISH_QUICK) ? NFS_SQUISHY_QUICKTIMEOUT : NFS_SQUISHY_DEADTIMEOUT; + + NFS_SOCK_DBG("%s: nm_curdeadtiemout = %d, nfs_is_mobile = %d\n", + vfs_statfs(mp)->f_mntfromname, nmp->nm_curdeadtimeout, nfs_is_mobile); + + if (!nfs_can_squish(nmp)) + goto out; + + timeo = (nmp->nm_deadtimeout > timeo) ? max(nmp->nm_deadtimeout/8, timeo) : timeo; + NFS_SOCK_DBG("nm_writers = %d nm_mappers = %d timeo = %d\n", nmp->nm_writers, nmp->nm_mappers, timeo); + + if (nmp->nm_writers == 0 && nmp->nm_mappers == 0) { + uint64_t flags = mp ? vfs_flags(mp) : 0; + squishy = 1; + + /* + * Walk the nfs nodes and check for dirty buffers it we're not + * RDONLY and we've not already been declared as squishy since + * this can be a bit expensive. + */ + if (!(flags & MNT_RDONLY) && !(nmp->nm_state & NFSSTA_SQUISHY)) + squishy = !nfs_mount_is_dirty(mp); + } + +out: + if (squishy) + nmp->nm_state |= NFSSTA_SQUISHY; + else + nmp->nm_state &= ~NFSSTA_SQUISHY; + + nmp->nm_curdeadtimeout = squishy ? timeo : nmp->nm_deadtimeout; + + NFS_SOCK_DBG("nm_curdeadtimeout = %d\n", nmp->nm_curdeadtimeout); + + return (squishy); +} + +/* + * On a send operation, if we can't reach the server and we've got only one server to talk to + * and NFS_SQUISH_QUICK flag is set and we are in a squishy state then mark the mount as dead + * and ask to be forcibly unmounted. Return 1 if we're dead and 0 otherwise. + */ +static int +nfs_is_dead_lock(int error, struct nfsmount *nmp) +{ + if (nmp->nm_state & NFSSTA_DEAD) + return (1); + + if ((error != ENETUNREACH && error != EHOSTUNREACH && error != EADDRNOTAVAIL) || + !(nmp->nm_locations.nl_numlocs == 1 && nmp->nm_locations.nl_locations[0]->nl_servcount == 1)) + return (0); + + if ((nfs_squishy_flags & NFS_SQUISH_QUICK) && nfs_is_squishy(nmp)) { + printf("nfs_is_dead: nfs server %s: unreachable. Squished dead\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname); + nmp->nm_state |= NFSSTA_DEAD; + vfs_event_signal(&vfs_statfs(nmp->nm_mountp)->f_fsid, VQ_DEAD, 0); + return (1); + } + return (0); +} + +int +nfs_is_dead(int error, struct nfsmount *nmp) +{ + int is_dead; + + lck_mtx_lock(&nmp->nm_lock); + is_dead = nfs_is_dead_lock(error, nmp); + lck_mtx_unlock(&nmp->nm_lock); + + return (is_dead); +} + void nfs_down(struct nfsmount *nmp, thread_t thd, int error, int flags, const char *msg) { @@ -5169,14 +5456,17 @@ nfs_down(struct nfsmount *nmp, thread_t thd, int error, int flags, const char *m unresponsive = (nmp->nm_state & timeoutmask); - if (unresponsive && (nmp->nm_deadtimeout > 0)) { + nfs_is_squishy(nmp); + + if (unresponsive && (nmp->nm_curdeadtimeout > 0)) { microuptime(&now); if (!wasunresponsive) { nmp->nm_deadto_start = now.tv_sec; nfs_mount_sock_thread_wake(nmp); - } else if ((now.tv_sec - nmp->nm_deadto_start) > nmp->nm_deadtimeout) { + } else if ((now.tv_sec - nmp->nm_deadto_start) > nmp->nm_curdeadtimeout) { if (!(nmp->nm_state & NFSSTA_DEAD)) - printf("nfs server %s: dead\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname); + printf("nfs server %s: %sdead\n", vfs_statfs(nmp->nm_mountp)->f_mntfromname, + (nmp->nm_curdeadtimeout != nmp->nm_deadtimeout) ? "squished " : ""); nmp->nm_state |= NFSSTA_DEAD; } } @@ -5225,8 +5515,9 @@ nfs_up(struct nfsmount *nmp, thread_t thd, int flags, const char *msg) unresponsive = (nmp->nm_state & timeoutmask); - if (nmp->nm_deadto_start) - nmp->nm_deadto_start = 0; + nmp->nm_deadto_start = 0; + nmp->nm_curdeadtimeout = nmp->nm_deadtimeout; + nmp->nm_state &= ~NFSSTA_SQUISHY; lck_mtx_unlock(&nmp->nm_lock); if (softnobrowse) @@ -5350,7 +5641,7 @@ done: *nmrepp = nmrep; if ((err != 0) && (err != NFSERR_RETVOID)) - OSAddAtomic(1, &nfsstats.srvrpc_errs); + OSAddAtomic64(1, &nfsstats.srvrpc_errs); return (0); } @@ -5487,11 +5778,11 @@ nfsrv_rcv_locked(socket_t so, struct nfsrv_sock *slp, int waitflag) ns_flag = SLP_NEEDQ; goto dorecs; } - + bzero(&msg, sizeof(msg)); msg.msg_name = (caddr_t)&nam; msg.msg_namelen = sizeof(nam); - + do { bytes_read = 1000000000; error = sock_receivembuf(so, &msg, &mp, MSG_DONTWAIT | MSG_NEEDSA, &bytes_read); @@ -5670,7 +5961,7 @@ nfsrv_getstream(struct nfsrv_sock *slp, int waitflag) if (slp->ns_frag == NULL) { slp->ns_frag = recm; } else { - m = slp->ns_frag; + m = slp->ns_frag; while ((m2 = mbuf_next(m))) m = m2; if ((error = mbuf_setnext(m, recm))) @@ -5918,4 +6209,3 @@ nfsrv_wakenfsd(struct nfsrv_sock *slp) } #endif /* NFSSERVER */ -