+ /* record marker complete */
+ nrrsp->nrrs_fragleft = ntohl(nrrsp->nrrs_fragleft);
+ if (nrrsp->nrrs_fragleft & 0x80000000) {
+ nrrsp->nrrs_lastfrag = 1;
+ nrrsp->nrrs_fragleft &= ~0x80000000;
+ }
+ nrrsp->nrrs_reclen += nrrsp->nrrs_fragleft;
+ if (nrrsp->nrrs_reclen > NFS_MAXPACKET) {
+ /* This is SERIOUS! We are out of sync with the sender. */
+ log(LOG_ERR, "impossible RPC record length (%d) on callback", nrrsp->nrrs_reclen);
+ error = EFBIG;
+ }
+ }
+
+ /* read the TCP RPC record fragment */
+ while (!error && !nrrsp->nrrs_markerleft && nrrsp->nrrs_fragleft) {
+ m = NULL;
+ rcvlen = nrrsp->nrrs_fragleft;
+ error = sock_receivembuf(so, NULL, &m, flags, &rcvlen);
+ if (error || !rcvlen || !m)
+ break;
+ *recvp = 1;
+ /* append mbufs to list */
+ nrrsp->nrrs_fragleft -= rcvlen;
+ if (!nrrsp->nrrs_m) {
+ nrrsp->nrrs_m = m;
+ } else {
+ error = mbuf_setnext(nrrsp->nrrs_mlast, m);
+ if (error) {
+ printf("nfs tcp rcv: mbuf_setnext failed %d\n", error);
+ mbuf_freem(m);
+ break;
+ }
+ }
+ while (mbuf_next(m))
+ m = mbuf_next(m);
+ nrrsp->nrrs_mlast = m;
+ }
+
+ /* done reading fragment? */
+ if (!error && !nrrsp->nrrs_markerleft && !nrrsp->nrrs_fragleft) {
+ /* reset socket fragment parsing state */
+ nrrsp->nrrs_markerleft = sizeof(nrrsp->nrrs_fragleft);
+ if (nrrsp->nrrs_lastfrag) {
+ /* RPC record complete */
+ *mp = nrrsp->nrrs_m;
+ /* reset socket record parsing state */
+ nrrsp->nrrs_reclen = 0;
+ nrrsp->nrrs_m = nrrsp->nrrs_mlast = NULL;
+ nrrsp->nrrs_lastfrag = 0;
+ }
+ }
+
+ return (error);
+}
+
+
+
+/*
+ * The NFS client send routine.
+ *
+ * Send the given NFS request out the mount's socket.
+ * Holds nfs_sndlock() for the duration of this call.
+ *
+ * - check for request termination (sigintr)
+ * - wait for reconnect, if necessary
+ * - UDP: check the congestion window
+ * - make a copy of the request to send
+ * - UDP: update the congestion window
+ * - send the request
+ *
+ * If sent successfully, R_MUSTRESEND and R_RESENDERR are cleared.
+ * rexmit count is also updated if this isn't the first send.
+ *
+ * If the send is not successful, make sure R_MUSTRESEND is set.
+ * If this wasn't the first transmit, set R_RESENDERR.
+ * Also, undo any UDP congestion window changes made.
+ *
+ * If the error appears to indicate that the socket should
+ * be reconnected, mark the socket for reconnection.
+ *
+ * Only return errors when the request should be aborted.
+ */
+int
+nfs_send(struct nfsreq *req, int wait)
+{
+ struct nfsmount *nmp;
+ struct nfs_socket *nso;
+ int error, error2, sotype, rexmit, slpflag = 0, needrecon;
+ struct msghdr msg;
+ struct sockaddr *sendnam;
+ mbuf_t mreqcopy;
+ size_t sentlen = 0;
+ struct timespec ts = { 2, 0 };
+
+again:
+ error = nfs_sndlock(req);
+ if (error) {
+ lck_mtx_lock(&req->r_mtx);
+ req->r_error = error;
+ req->r_flags &= ~R_SENDING;
+ lck_mtx_unlock(&req->r_mtx);
+ return (error);
+ }
+
+ error = nfs_sigintr(req->r_nmp, req, NULL, 0);
+ if (error) {
+ nfs_sndunlock(req);
+ lck_mtx_lock(&req->r_mtx);
+ req->r_error = error;
+ req->r_flags &= ~R_SENDING;
+ lck_mtx_unlock(&req->r_mtx);
+ return (error);
+ }
+ nmp = req->r_nmp;
+ sotype = nmp->nm_sotype;
+
+ /*
+ * If it's a setup RPC but we're not in SETUP... must need reconnect.
+ * If it's a recovery RPC but the socket's not ready... must need reconnect.
+ */
+ if (((req->r_flags & R_SETUP) && !(nmp->nm_sockflags & NMSOCK_SETUP)) ||
+ ((req->r_flags & R_RECOVER) && !(nmp->nm_sockflags & NMSOCK_READY))) {
+ error = ETIMEDOUT;
+ nfs_sndunlock(req);
+ lck_mtx_lock(&req->r_mtx);
+ req->r_error = error;
+ req->r_flags &= ~R_SENDING;
+ lck_mtx_unlock(&req->r_mtx);
+ return (error);
+ }
+
+ /* If the socket needs reconnection, do that now. */
+ /* wait until socket is ready - unless this request is part of setup */
+ lck_mtx_lock(&nmp->nm_lock);
+ if (!(nmp->nm_sockflags & NMSOCK_READY) &&
+ !((nmp->nm_sockflags & NMSOCK_SETUP) && (req->r_flags & R_SETUP))) {
+ if (NMFLAG(nmp, INTR) && !(req->r_flags & R_NOINTR))
+ slpflag |= PCATCH;
+ lck_mtx_unlock(&nmp->nm_lock);
+ nfs_sndunlock(req);
+ if (!wait) {
+ lck_mtx_lock(&req->r_mtx);
+ req->r_flags &= ~R_SENDING;
+ req->r_flags |= R_MUSTRESEND;
+ req->r_rtt = 0;
+ lck_mtx_unlock(&req->r_mtx);
+ return (0);
+ }
+ 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;
+ lck_mtx_unlock(&req->r_mtx);
+ lck_mtx_lock(&nmp->nm_lock);
+ while (!(nmp->nm_sockflags & NMSOCK_READY)) {
+ /* don't bother waiting if the socket thread won't be reconnecting it */
+ if (nmp->nm_state & NFSSTA_FORCE) {
+ error = EIO;
+ break;
+ }
+ if (NMFLAG(nmp, SOFT) && (nmp->nm_reconnect_start > 0)) {
+ struct timeval now;
+ microuptime(&now);
+ if ((now.tv_sec - nmp->nm_reconnect_start) >= 8) {
+ /* soft mount in reconnect for a while... terminate ASAP */
+ OSAddAtomic(1, &nfsstats.rpctimeouts);
+ req->r_flags |= R_SOFTTERM;
+ req->r_error = error = ETIMEDOUT;
+ break;
+ }
+ }
+ /* make sure socket thread is running, then wait */
+ nfs_mount_sock_thread_wake(nmp);
+ if ((error = nfs_sigintr(req->r_nmp, req, req->r_thread, 1)))
+ break;
+ msleep(req, &nmp->nm_lock, slpflag|PSOCK, "nfsconnectwait", &ts);
+ slpflag = 0;
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+ if (error) {
+ lck_mtx_lock(&req->r_mtx);
+ req->r_error = error;
+ req->r_flags &= ~R_SENDING;
+ lck_mtx_unlock(&req->r_mtx);
+ return (error);
+ }
+ goto again;
+ }
+ nso = nmp->nm_nso;
+ /* note that we're using the mount's socket to do the send */
+ nmp->nm_state |= NFSSTA_SENDING; /* will be cleared by nfs_sndunlock() */
+ lck_mtx_unlock(&nmp->nm_lock);
+ if (!nso) {
+ nfs_sndunlock(req);
+ lck_mtx_lock(&req->r_mtx);
+ req->r_flags &= ~R_SENDING;
+ req->r_flags |= R_MUSTRESEND;
+ req->r_rtt = 0;
+ lck_mtx_unlock(&req->r_mtx);
+ return (0);
+ }
+
+ lck_mtx_lock(&req->r_mtx);
+ rexmit = (req->r_flags & R_SENT);
+
+ if (sotype == SOCK_DGRAM) {
+ lck_mtx_lock(&nmp->nm_lock);
+ if (!(req->r_flags & R_CWND) && (nmp->nm_sent >= nmp->nm_cwnd)) {
+ /* if we can't send this out yet, wait on the cwnd queue */
+ slpflag = (NMFLAG(nmp, INTR) && req->r_thread) ? PCATCH : 0;
+ lck_mtx_unlock(&nmp->nm_lock);
+ nfs_sndunlock(req);
+ req->r_flags &= ~R_SENDING;
+ req->r_flags |= R_MUSTRESEND;
+ lck_mtx_unlock(&req->r_mtx);
+ if (!wait) {
+ req->r_rtt = 0;
+ return (0);
+ }
+ lck_mtx_lock(&nmp->nm_lock);
+ while (nmp->nm_sent >= nmp->nm_cwnd) {
+ if ((error = nfs_sigintr(req->r_nmp, req, req->r_thread, 1)))
+ break;
+ TAILQ_INSERT_TAIL(&nmp->nm_cwndq, req, r_cchain);
+ msleep(req, &nmp->nm_lock, slpflag | (PZERO - 1), "nfswaitcwnd", &ts);
+ slpflag = 0;
+ if ((req->r_cchain.tqe_next != NFSREQNOLIST)) {
+ TAILQ_REMOVE(&nmp->nm_cwndq, req, r_cchain);
+ req->r_cchain.tqe_next = NFSREQNOLIST;
+ }
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+ goto again;
+ }
+ /*
+ * We update these *before* the send to avoid racing
+ * against others who may be looking to send requests.
+ */
+ if (!rexmit) {
+ /* first transmit */
+ req->r_flags |= R_CWND;
+ nmp->nm_sent += NFS_CWNDSCALE;
+ } else {
+ /*
+ * When retransmitting, turn timing off
+ * and divide congestion window by 2.
+ */
+ req->r_flags &= ~R_TIMING;
+ nmp->nm_cwnd >>= 1;
+ if (nmp->nm_cwnd < NFS_CWNDSCALE)
+ nmp->nm_cwnd = NFS_CWNDSCALE;
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+ }
+
+ req->r_flags &= ~R_MUSTRESEND;
+ lck_mtx_unlock(&req->r_mtx);
+
+ error = mbuf_copym(req->r_mhead, 0, MBUF_COPYALL,
+ wait ? MBUF_WAITOK : MBUF_DONTWAIT, &mreqcopy);
+ if (error) {
+ if (wait)
+ log(LOG_INFO, "nfs_send: mbuf copy failed %d\n", error);
+ nfs_sndunlock(req);
+ lck_mtx_lock(&req->r_mtx);
+ req->r_flags &= ~R_SENDING;
+ req->r_flags |= R_MUSTRESEND;
+ req->r_rtt = 0;
+ lck_mtx_unlock(&req->r_mtx);
+ return (0);
+ }
+
+ bzero(&msg, sizeof(msg));
+ if ((sotype != SOCK_STREAM) && !sock_isconnected(nso->nso_so) && ((sendnam = nmp->nm_saddr))) {
+ msg.msg_name = (caddr_t)sendnam;
+ msg.msg_namelen = sendnam->sa_len;
+ }
+ 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));
+#endif
+ if (!error && (sentlen != req->r_mreqlen))
+ error = EWOULDBLOCK;
+ needrecon = ((sotype == SOCK_STREAM) && sentlen && (sentlen != req->r_mreqlen));
+
+ lck_mtx_lock(&req->r_mtx);
+ req->r_flags &= ~R_SENDING;
+ req->r_rtt = 0;
+ if (rexmit && (++req->r_rexmit > NFS_MAXREXMIT))
+ req->r_rexmit = NFS_MAXREXMIT;
+
+ if (!error) {
+ /* SUCCESS */
+ req->r_flags &= ~R_RESENDERR;
+ if (rexmit)
+ OSAddAtomic(1, &nfsstats.rpcretries);
+ req->r_flags |= R_SENT;
+ if (req->r_flags & R_WAITSENT) {
+ req->r_flags &= ~R_WAITSENT;
+ wakeup(req);
+ }
+ nfs_sndunlock(req);
+ lck_mtx_unlock(&req->r_mtx);
+ return (0);
+ }
+
+ /* send failed */
+ req->r_flags |= R_MUSTRESEND;
+ if (rexmit)
+ req->r_flags |= R_RESENDERR;
+ if ((error == EINTR) || (error == ERESTART))
+ req->r_error = error;
+ lck_mtx_unlock(&req->r_mtx);
+
+ if (sotype == SOCK_DGRAM) {
+ /*
+ * Note: even though a first send may fail, we consider
+ * the request sent for congestion window purposes.
+ * So we don't need to undo any of the changes made above.
+ */
+ /*
+ * Socket errors ignored for connectionless sockets??
+ * For now, ignore them all
+ */
+ if ((error != EINTR) && (error != ERESTART) &&
+ (error != EWOULDBLOCK) && (error != EIO) && (nso == nmp->nm_nso)) {
+ int clearerror = 0, optlen = sizeof(clearerror);
+ 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));
+#endif
+ }
+ }
+
+ /* check if it appears we should reconnect the socket */
+ switch (error) {
+ case EWOULDBLOCK:
+ /* if send timed out, reconnect if on TCP */
+ if (sotype != SOCK_STREAM)
+ break;
+ case EPIPE:
+ case EADDRNOTAVAIL:
+ case ENETDOWN:
+ case ENETUNREACH:
+ case ENETRESET:
+ case ECONNABORTED:
+ case ECONNRESET:
+ case ENOTCONN:
+ case ESHUTDOWN:
+ case ECONNREFUSED:
+ case EHOSTDOWN:
+ case EHOSTUNREACH:
+ needrecon = 1;
+ 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_need_reconnect(nmp);
+ }
+
+ nfs_sndunlock(req);
+
+ /*
+ * Don't log some errors:
+ * EPIPE errors may be common with servers that drop idle connections.
+ * EADDRNOTAVAIL may occur on network transitions.
+ * ENOTCONN may occur under some network conditions.
+ */
+ if ((error == EPIPE) || (error == EADDRNOTAVAIL) || (error == ENOTCONN))
+ error = 0;
+ if (error && (error != EINTR) && (error != ERESTART))
+ log(LOG_INFO, "nfs send error %d for server %s\n", error,
+ !req->r_nmp ? "<unmounted>" :
+ vfs_statfs(req->r_nmp->nm_mountp)->f_mntfromname);
+
+ /* prefer request termination error over other errors */
+ error2 = nfs_sigintr(req->r_nmp, req, req->r_thread, 0);
+ if (error2)
+ error = error2;
+
+ /* only allow the following errors to be returned */
+ if ((error != EINTR) && (error != ERESTART) && (error != EIO) &&
+ (error != ENXIO) && (error != ETIMEDOUT))
+ error = 0;
+ return (error);
+}
+
+/*
+ * NFS client socket upcalls
+ *
+ * Pull RPC replies out of an NFS mount's socket and match them
+ * up with the pending request.
+ *
+ * The datagram code is simple because we always get whole
+ * messages out of the socket.
+ *
+ * The stream code is more involved because we have to parse
+ * the RPC records out of the stream.
+ */
+
+/* NFS client UDP socket upcall */
+void
+nfs_udp_rcv(socket_t so, void *arg, __unused int waitflag)
+{
+ struct nfsmount *nmp = arg;
+ struct nfs_socket *nso = nmp->nm_nso;
+ size_t rcvlen;
+ mbuf_t m;
+ int error = 0;
+
+ if (nmp->nm_sockflags & NMSOCK_CONNECTING)
+ return;
+
+ do {
+ /* make sure we're on the current socket */
+ if (!nso || (nso->nso_so != so))
+ return;
+
+ m = NULL;
+ rcvlen = 1000000;
+ error = sock_receivembuf(so, NULL, &m, MSG_DONTWAIT, &rcvlen);
+ if (m)
+ nfs_request_match_reply(nmp, m);
+ } while (m && !error);
+
+ if (error && (error != EWOULDBLOCK)) {
+ /* problems with the socket... mark for reconnection */
+ NFS_SOCK_DBG(("nfs_udp_rcv: need reconnect %d\n", error));
+ nfs_need_reconnect(nmp);
+ }
+}
+
+/* NFS client TCP socket upcall */
+void
+nfs_tcp_rcv(socket_t so, void *arg, __unused int waitflag)
+{
+ struct nfsmount *nmp = arg;
+ struct nfs_socket *nso = nmp->nm_nso;
+ struct nfs_rpc_record_state nrrs;
+ mbuf_t m;
+ int error = 0;
+ int recv = 1;
+
+ if (nmp->nm_sockflags & NMSOCK_CONNECTING)
+ return;
+
+ /* make sure we're on the current socket */
+ lck_mtx_lock(&nmp->nm_lock);
+ nso = nmp->nm_nso;
+ if (!nso || (nso->nso_so != so) || (nmp->nm_sockflags & (NMSOCK_DISCONNECTING))) {
+ lck_mtx_unlock(&nmp->nm_lock);
+ return;
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+
+ /* make sure this upcall should be trying to do work */
+ lck_mtx_lock(&nso->nso_lock);
+ if (nso->nso_flags & (NSO_UPCALL|NSO_DISCONNECTING|NSO_DEAD)) {
+ lck_mtx_unlock(&nso->nso_lock);
+ return;
+ }
+ nso->nso_flags |= NSO_UPCALL;
+ nrrs = nso->nso_rrs;
+ lck_mtx_unlock(&nso->nso_lock);
+
+ /* loop while we make error-free progress */
+ while (!error && recv) {
+ error = nfs_rpc_record_read(so, &nrrs, MSG_DONTWAIT, &recv, &m);
+ if (m) /* match completed response with request */
+ nfs_request_match_reply(nmp, m);
+ }
+
+ lck_mtx_lock(&nmp->nm_lock);
+ if (nmp->nm_nso == nso) {
+ /* still the same socket, so update socket's RPC parsing state */
+ lck_mtx_unlock(&nmp->nm_lock);
+ lck_mtx_lock(&nso->nso_lock);
+ nso->nso_rrs = nrrs;
+ nso->nso_flags &= ~NSO_UPCALL;
+ lck_mtx_unlock(&nso->nso_lock);
+ if (nmp->nm_sockflags & NMSOCK_DISCONNECTING)
+ wakeup(&nmp->nm_sockflags);
+ } else {
+ lck_mtx_unlock(&nmp->nm_lock);
+ }
+#ifdef NFS_SOCKET_DEBUGGING
+ if (!recv && (error != EWOULDBLOCK))
+ 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_need_reconnect(nmp);
+ }
+}
+
+/*
+ * "poke" a socket to try to provoke any pending errors
+ */
+void
+nfs_sock_poke(struct nfsmount *nmp)
+{
+ struct iovec aio;
+ struct msghdr msg;
+ size_t len;
+ int error = 0;
+ int dummy;
+
+ lck_mtx_lock(&nmp->nm_lock);
+ if ((nmp->nm_sockflags & NMSOCK_UNMOUNT) ||
+ !(nmp->nm_sockflags & NMSOCK_READY) || !nmp->nm_nso || !nmp->nm_nso->nso_so) {
+ lck_mtx_unlock(&nmp->nm_lock);
+ return;
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+ aio.iov_base = &dummy;
+ aio.iov_len = 0;
+ len = 0;
+ bzero(&msg, sizeof(msg));
+ 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));
+}
+
+/*
+ * Match an RPC reply with the corresponding request
+ */
+void
+nfs_request_match_reply(struct nfsmount *nmp, mbuf_t mrep)
+{
+ struct nfsreq *req;
+ struct nfsm_chain nmrep;
+ u_int32_t reply = 0, rxid = 0;
+ int error = 0, asyncioq, t1;
+
+ /* Get the xid and check that it is an rpc reply */
+ nfsm_chain_dissect_init(error, &nmrep, mrep);
+ nfsm_chain_get_32(error, &nmrep, rxid);
+ nfsm_chain_get_32(error, &nmrep, reply);
+ if (error || (reply != RPC_REPLY)) {
+ OSAddAtomic(1, &nfsstats.rpcinvalid);
+ mbuf_freem(mrep);
+ return;
+ }
+
+ /*
+ * Loop through the request list to match up the reply
+ * Iff no match, just drop it.
+ */
+ lck_mtx_lock(nfs_request_mutex);
+ TAILQ_FOREACH(req, &nfs_reqq, r_chain) {
+ if (req->r_nmrep.nmc_mhead || (rxid != R_XID32(req->r_xid)))
+ continue;
+ /* looks like we have it, grab lock and double check */
+ lck_mtx_lock(&req->r_mtx);
+ if (req->r_nmrep.nmc_mhead || (rxid != R_XID32(req->r_xid))) {
+ lck_mtx_unlock(&req->r_mtx);
+ continue;
+ }
+ /* Found it.. */
+ req->r_nmrep = nmrep;
+ lck_mtx_lock(&nmp->nm_lock);
+ if (nmp->nm_sotype == SOCK_DGRAM) {
+ /*
+ * Update congestion window.
+ * Do the additive increase of one rpc/rtt.
+ */
+ FSDBG(530, R_XID32(req->r_xid), req, nmp->nm_sent, nmp->nm_cwnd);
+ if (nmp->nm_cwnd <= nmp->nm_sent) {
+ nmp->nm_cwnd +=
+ ((NFS_CWNDSCALE * NFS_CWNDSCALE) +
+ (nmp->nm_cwnd >> 1)) / nmp->nm_cwnd;
+ if (nmp->nm_cwnd > NFS_MAXCWND)
+ nmp->nm_cwnd = NFS_MAXCWND;
+ }
+ if (req->r_flags & R_CWND) {
+ nmp->nm_sent -= NFS_CWNDSCALE;
+ req->r_flags &= ~R_CWND;
+ }
+ if ((nmp->nm_sent < nmp->nm_cwnd) && !TAILQ_EMPTY(&nmp->nm_cwndq)) {
+ /* congestion window is open, poke the cwnd queue */
+ struct nfsreq *req2 = TAILQ_FIRST(&nmp->nm_cwndq);
+ TAILQ_REMOVE(&nmp->nm_cwndq, req2, r_cchain);
+ req2->r_cchain.tqe_next = NFSREQNOLIST;
+ wakeup(req2);
+ }
+ }
+ /*
+ * Update rtt using a gain of 0.125 on the mean
+ * and a gain of 0.25 on the deviation.
+ */
+ if (req->r_flags & R_TIMING) {
+ /*
+ * Since the timer resolution of
+ * NFS_HZ is so course, it can often
+ * result in r_rtt == 0. Since
+ * r_rtt == N means that the actual
+ * rtt is between N+dt and N+2-dt ticks,
+ * add 1.
+ */
+ if (proct[req->r_procnum] == 0)
+ panic("nfs_request_match_reply: proct[%d] is zero", req->r_procnum);
+ t1 = req->r_rtt + 1;
+ t1 -= (NFS_SRTT(req) >> 3);
+ NFS_SRTT(req) += t1;
+ if (t1 < 0)
+ t1 = -t1;
+ t1 -= (NFS_SDRTT(req) >> 2);
+ NFS_SDRTT(req) += t1;
+ }
+ nmp->nm_timeouts = 0;
+ lck_mtx_unlock(&nmp->nm_lock);
+ /* signal anyone waiting on this request */
+ wakeup(req);
+ asyncioq = (req->r_callback.rcb_func != NULL);
+ if (nfs_request_using_gss(req))
+ nfs_gss_clnt_rpcdone(req);
+ lck_mtx_unlock(&req->r_mtx);
+ lck_mtx_unlock(nfs_request_mutex);
+ /* if it's an async RPC with a callback, queue it up */
+ if (asyncioq)
+ nfs_asyncio_finish(req);
+ break;
+ }
+
+ if (!req) {
+ /* not matched to a request, so drop it. */
+ lck_mtx_unlock(nfs_request_mutex);
+ OSAddAtomic(1, &nfsstats.rpcunexpected);
+ mbuf_freem(mrep);
+ }
+}
+
+/*
+ * Wait for the reply for a given request...
+ * ...potentially resending the request if necessary.
+ */
+int
+nfs_wait_reply(struct nfsreq *req)
+{
+ struct timespec ts = { 2, 0 };
+ int error = 0, slpflag, first = 1;
+
+ if (req->r_nmp && NMFLAG(req->r_nmp, INTR) && req->r_thread && !(req->r_flags & R_NOINTR))
+ slpflag = PCATCH;
+ else
+ slpflag = 0;
+
+ lck_mtx_lock(&req->r_mtx);
+ while (!req->r_nmrep.nmc_mhead) {
+ if ((error = nfs_sigintr(req->r_nmp, req, first ? NULL : req->r_thread, 0)))
+ break;
+ if (((error = req->r_error)) || req->r_nmrep.nmc_mhead)
+ 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));
+ req->r_flags |= R_SENDING;
+ lck_mtx_unlock(&req->r_mtx);
+ if (nfs_request_using_gss(req)) {
+ /*
+ * It's an RPCSEC_GSS request.
+ * Can't just resend the original request
+ * without bumping the cred sequence number.
+ * Go back and re-build the request.
+ */
+ lck_mtx_lock(&req->r_mtx);
+ req->r_flags &= ~R_SENDING;
+ lck_mtx_unlock(&req->r_mtx);
+ return (EAGAIN);
+ }
+ 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));
+ if (error)
+ break;
+ if (((error = req->r_error)) || req->r_nmrep.nmc_mhead)
+ break;
+ }
+ /* need to poll if we're P_NOREMOTEHANG */
+ if (nfs_noremotehang(req->r_thread))
+ ts.tv_sec = 1;
+ msleep(req, &req->r_mtx, slpflag | (PZERO - 1), "nfswaitreply", &ts);
+ first = slpflag = 0;
+ }
+ lck_mtx_unlock(&req->r_mtx);
+
+ return (error);
+}
+
+/*
+ * An NFS request goes something like this:
+ * (nb: always frees up mreq mbuf list)
+ * nfs_request_create()
+ * - allocates a request struct if one is not provided
+ * - initial fill-in of the request struct
+ * nfs_request_add_header()
+ * - add the RPC header
+ * nfs_request_send()
+ * - link it into list
+ * - call nfs_send() for first transmit
+ * nfs_request_wait()
+ * - call nfs_wait_reply() to wait for the reply
+ * nfs_request_finish()
+ * - break down rpc header and return with error or nfs reply
+ * pointed to by nmrep.
+ * nfs_request_rele()
+ * nfs_request_destroy()
+ * - clean up the request struct
+ * - free the request struct if it was allocated by nfs_request_create()
+ */
+
+/*
+ * Set up an NFS request struct (allocating if no request passed in).
+ */
+int
+nfs_request_create(
+ nfsnode_t np,
+ mount_t mp, /* used only if !np */
+ struct nfsm_chain *nmrest,
+ int procnum,
+ thread_t thd,
+ kauth_cred_t cred,
+ struct nfsreq **reqp)
+{
+ struct nfsreq *req, *newreq = NULL;
+ struct nfsmount *nmp;
+
+ req = *reqp;
+ if (!req) {
+ /* allocate a new NFS request structure */
+ MALLOC_ZONE(newreq, struct nfsreq*, sizeof(*newreq), M_NFSREQ, M_WAITOK);
+ if (!newreq) {
+ mbuf_freem(nmrest->nmc_mhead);
+ nmrest->nmc_mhead = NULL;
+ return (ENOMEM);
+ }
+ req = newreq;
+ }
+
+ bzero(req, sizeof(*req));
+ if (req == newreq)
+ req->r_flags = R_ALLOCATED;
+
+ nmp = VFSTONFS(np ? NFSTOMP(np) : mp);
+ if (!nmp) {
+ if (newreq)
+ FREE_ZONE(newreq, sizeof(*newreq), M_NFSREQ);
+ return (ENXIO);
+ }
+ lck_mtx_lock(&nmp->nm_lock);
+ if ((nmp->nm_state & (NFSSTA_FORCE|NFSSTA_TIMEO)) ==
+ (NFSSTA_FORCE|NFSSTA_TIMEO)) {
+ lck_mtx_unlock(&nmp->nm_lock);
+ mbuf_freem(nmrest->nmc_mhead);
+ nmrest->nmc_mhead = NULL;
+ if (newreq)
+ FREE_ZONE(newreq, sizeof(*newreq), M_NFSREQ);
+ return (ENXIO);
+ }
+
+ if ((nmp->nm_vers != NFS_VER4) && (procnum >= 0) && (procnum < NFS_NPROCS))
+ OSAddAtomic(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);
+
+ lck_mtx_init(&req->r_mtx, nfs_request_grp, LCK_ATTR_NULL);
+ req->r_nmp = nmp;
+ req->r_np = np;
+ req->r_thread = thd;
+ if (!thd)
+ req->r_flags |= R_NOINTR;
+ if (IS_VALID_CRED(cred)) {
+ kauth_cred_ref(cred);
+ req->r_cred = cred;
+ }
+ req->r_procnum = procnum;
+ if (proct[procnum] > 0)
+ req->r_flags |= R_TIMING;
+ req->r_nmrep.nmc_mhead = NULL;
+ SLIST_INIT(&req->r_gss_seqlist);
+ req->r_achain.tqe_next = NFSREQNOLIST;
+ req->r_rchain.tqe_next = NFSREQNOLIST;
+ req->r_cchain.tqe_next = NFSREQNOLIST;
+
+ /* set auth flavor to use for request */
+ if (!req->r_cred)
+ req->r_auth = RPCAUTH_NONE;
+ else if (req->r_np && (req->r_np->n_auth != RPCAUTH_INVALID))
+ req->r_auth = req->r_np->n_auth;
+ else
+ req->r_auth = nmp->nm_auth;
+
+ lck_mtx_unlock(&nmp->nm_lock);
+
+ /* move the request mbuf chain to the nfsreq */
+ req->r_mrest = nmrest->nmc_mhead;
+ nmrest->nmc_mhead = NULL;
+
+ req->r_flags |= R_INITTED;
+ req->r_refs = 1;
+ if (newreq)
+ *reqp = req;
+ return (0);
+}
+
+/*
+ * Clean up and free an NFS request structure.
+ */
+void
+nfs_request_destroy(struct nfsreq *req)
+{
+ struct nfsmount *nmp = req->r_np ? NFSTONMP(req->r_np) : req->r_nmp;
+ struct gss_seq *gsp, *ngsp;
+ struct timespec ts = { 1, 0 };
+ int clearjbtimeo = 0;
+
+ if (!req || !(req->r_flags & R_INITTED))
+ return;
+ req->r_flags &= ~R_INITTED;
+ if (req->r_lflags & RL_QUEUED)
+ nfs_reqdequeue(req);
+ if (req->r_achain.tqe_next != NFSREQNOLIST) {
+ /* still on an async I/O queue? */
+ lck_mtx_lock(nfsiod_mutex);
+ if (nmp && (req->r_achain.tqe_next != NFSREQNOLIST)) {
+ TAILQ_REMOVE(&nmp->nm_iodq, req, r_achain);
+ req->r_achain.tqe_next = NFSREQNOLIST;
+ }
+ lck_mtx_unlock(nfsiod_mutex);
+ }
+ lck_mtx_lock(&req->r_mtx);
+ if (nmp) {
+ lck_mtx_lock(&nmp->nm_lock);
+ if (req->r_flags & R_CWND) {
+ /* Decrement the outstanding request count. */
+ req->r_flags &= ~R_CWND;
+ nmp->nm_sent -= NFS_CWNDSCALE;
+ if ((nmp->nm_sent < nmp->nm_cwnd) && !TAILQ_EMPTY(&nmp->nm_cwndq)) {
+ /* congestion window is open, poke the cwnd queue */
+ struct nfsreq *req2 = TAILQ_FIRST(&nmp->nm_cwndq);
+ TAILQ_REMOVE(&nmp->nm_cwndq, req2, r_cchain);
+ req2->r_cchain.tqe_next = NFSREQNOLIST;
+ wakeup(req2);
+ }
+ }
+ if (req->r_rchain.tqe_next != NFSREQNOLIST) {
+ TAILQ_REMOVE(&nmp->nm_resendq, req, r_rchain);
+ req->r_rchain.tqe_next = NFSREQNOLIST;
+ if (req->r_flags & R_RESENDQ)
+ req->r_flags &= ~R_RESENDQ;
+ }
+ if (req->r_cchain.tqe_next != NFSREQNOLIST) {
+ TAILQ_REMOVE(&nmp->nm_cwndq, req, r_cchain);
+ req->r_cchain.tqe_next = NFSREQNOLIST;
+ }
+ if (req->r_flags & R_JBTPRINTFMSG) {
+ req->r_flags &= ~R_JBTPRINTFMSG;
+ nmp->nm_jbreqs--;
+ clearjbtimeo = (nmp->nm_jbreqs == 0) ? NFSSTA_JUKEBOXTIMEO : 0;
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+ }
+ while (req->r_flags & R_RESENDQ)
+ msleep(req, &req->r_mtx, (PZERO - 1), "nfsresendqwait", &ts);
+ lck_mtx_unlock(&req->r_mtx);
+ if (clearjbtimeo)
+ nfs_up(nmp, req->r_thread, clearjbtimeo, NULL);
+ if (req->r_mhead)
+ mbuf_freem(req->r_mhead);
+ else if (req->r_mrest)
+ mbuf_freem(req->r_mrest);
+ if (req->r_nmrep.nmc_mhead)
+ mbuf_freem(req->r_nmrep.nmc_mhead);
+ if (IS_VALID_CRED(req->r_cred))
+ kauth_cred_unref(&req->r_cred);
+ if (nfs_request_using_gss(req))
+ nfs_gss_clnt_rpcdone(req);
+ SLIST_FOREACH_SAFE(gsp, &req->r_gss_seqlist, gss_seqnext, ngsp)
+ FREE(gsp, M_TEMP);
+ if (req->r_gss_ctx)
+ nfs_gss_clnt_ctx_unref(req);
+ if (req->r_wrongsec)
+ FREE(req->r_wrongsec, M_TEMP);
+
+ lck_mtx_destroy(&req->r_mtx, nfs_request_grp);
+ if (req->r_flags & R_ALLOCATED)
+ FREE_ZONE(req, sizeof(*req), M_NFSREQ);
+}
+
+void
+nfs_request_ref(struct nfsreq *req, int locked)
+{
+ if (!locked)
+ lck_mtx_lock(&req->r_mtx);
+ if (req->r_refs <= 0)
+ panic("nfsreq reference error");
+ req->r_refs++;
+ if (!locked)
+ lck_mtx_unlock(&req->r_mtx);
+}
+
+void
+nfs_request_rele(struct nfsreq *req)
+{
+ int destroy;
+
+ lck_mtx_lock(&req->r_mtx);
+ if (req->r_refs <= 0)
+ panic("nfsreq reference underflow");
+ req->r_refs--;
+ destroy = (req->r_refs == 0);
+ lck_mtx_unlock(&req->r_mtx);
+ if (destroy)
+ nfs_request_destroy(req);
+}
+
+
+/*
+ * Add an (updated) RPC header with authorization to an NFS request.
+ */
+int
+nfs_request_add_header(struct nfsreq *req)
+{
+ struct nfsmount *nmp;
+ int error = 0;
+ mbuf_t m;
+
+ /* free up any previous header */
+ if ((m = req->r_mhead)) {
+ while (m && (m != req->r_mrest))
+ m = mbuf_free(m);
+ req->r_mhead = NULL;
+ }
+
+ nmp = req->r_np ? NFSTONMP(req->r_np) : req->r_nmp;
+ if (!nmp)
+ return (ENXIO);
+
+ error = nfsm_rpchead(req, req->r_mrest, &req->r_xid, &req->r_mhead);
+ if (error)
+ return (error);
+
+ req->r_mreqlen = mbuf_pkthdr_len(req->r_mhead);
+ nmp = req->r_np ? NFSTONMP(req->r_np) : req->r_nmp;
+ if (!nmp)
+ return (ENXIO);
+ lck_mtx_lock(&nmp->nm_lock);
+ if (NMFLAG(nmp, SOFT))
+ req->r_retry = nmp->nm_retry;
+ else
+ req->r_retry = NFS_MAXREXMIT + 1; /* past clip limit */
+ lck_mtx_unlock(&nmp->nm_lock);
+
+ return (error);
+}
+
+
+/*
+ * Queue an NFS request up and send it out.
+ */
+int
+nfs_request_send(struct nfsreq *req, int wait)
+{
+ struct nfsmount *nmp;
+ struct timeval now;
+
+ lck_mtx_lock(&req->r_mtx);
+ req->r_flags |= R_SENDING;
+ lck_mtx_unlock(&req->r_mtx);
+
+ lck_mtx_lock(nfs_request_mutex);
+
+ nmp = req->r_np ? NFSTONMP(req->r_np) : req->r_nmp;
+ if (!nmp) {
+ lck_mtx_unlock(nfs_request_mutex);
+ return (ENXIO);
+ }
+
+ microuptime(&now);
+ if (!req->r_start) {
+ req->r_start = now.tv_sec;
+ req->r_lastmsg = now.tv_sec -
+ ((nmp->nm_tprintf_delay) - (nmp->nm_tprintf_initial_delay));
+ }
+
+ OSAddAtomic(1, &nfsstats.rpcrequests);
+
+ /*
+ * Chain request into list of outstanding requests. Be sure
+ * to put it LAST so timer finds oldest requests first.
+ * Make sure that the request queue timer is running
+ * to check for possible request timeout.
+ */
+ TAILQ_INSERT_TAIL(&nfs_reqq, req, r_chain);
+ req->r_lflags |= RL_QUEUED;
+ if (!nfs_request_timer_on) {
+ nfs_request_timer_on = 1;
+ nfs_interval_timer_start(nfs_request_timer_call,
+ NFS_REQUESTDELAY);
+ }
+ lck_mtx_unlock(nfs_request_mutex);
+
+ /* Send the request... */
+ return (nfs_send(req, wait));
+}
+
+/*
+ * Call nfs_wait_reply() to wait for the reply.
+ */
+void
+nfs_request_wait(struct nfsreq *req)
+{
+ req->r_error = nfs_wait_reply(req);
+}
+
+/*
+ * Finish up an NFS request by dequeueing it and
+ * doing the initial NFS request reply processing.
+ */
+int
+nfs_request_finish(
+ struct nfsreq *req,
+ struct nfsm_chain *nmrepp,
+ int *status)
+{
+ struct nfsmount *nmp;
+ mbuf_t mrep;
+ int verf_type = 0;
+ uint32_t verf_len = 0;
+ uint32_t reply_status = 0;
+ uint32_t rejected_status = 0;
+ uint32_t auth_status = 0;
+ uint32_t accepted_status = 0;
+ struct nfsm_chain nmrep;
+ int error, clearjbtimeo;
+
+ error = req->r_error;
+
+ if (nmrepp)
+ nmrepp->nmc_mhead = NULL;
+
+ /* RPC done, unlink the request. */
+ nfs_reqdequeue(req);
+
+ mrep = req->r_nmrep.nmc_mhead;
+
+ nmp = req->r_np ? NFSTONMP(req->r_np) : req->r_nmp;
+
+ if ((req->r_flags & R_CWND) && nmp) {
+ /*
+ * Decrement the outstanding request count.
+ */
+ req->r_flags &= ~R_CWND;
+ lck_mtx_lock(&nmp->nm_lock);
+ FSDBG(273, R_XID32(req->r_xid), req, nmp->nm_sent, nmp->nm_cwnd);
+ nmp->nm_sent -= NFS_CWNDSCALE;
+ if ((nmp->nm_sent < nmp->nm_cwnd) && !TAILQ_EMPTY(&nmp->nm_cwndq)) {
+ /* congestion window is open, poke the cwnd queue */
+ struct nfsreq *req2 = TAILQ_FIRST(&nmp->nm_cwndq);
+ TAILQ_REMOVE(&nmp->nm_cwndq, req2, r_cchain);
+ req2->r_cchain.tqe_next = NFSREQNOLIST;
+ wakeup(req2);
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+ }
+
+ if (nfs_request_using_gss(req)) {
+ /*
+ * If the request used an RPCSEC_GSS credential
+ * then reset its sequence number bit in the
+ * request window.
+ */
+ nfs_gss_clnt_rpcdone(req);
+
+ /*
+ * If we need to re-send, go back and re-build the
+ * request based on a new sequence number.
+ * Note that we're using the original XID.
+ */
+ if (error == EAGAIN) {
+ req->r_error = 0;
+ if (mrep)
+ mbuf_freem(mrep);
+ error = nfs_gss_clnt_args_restore(req); // remove any trailer mbufs
+ req->r_nmrep.nmc_mhead = NULL;
+ req->r_flags |= R_RESTART;
+ if (error == ENEEDAUTH) {
+ req->r_xid = 0; // get a new XID
+ error = 0;
+ }
+ goto nfsmout;
+ }
+ }
+
+ /*
+ * If there was a successful reply, make sure to mark the mount as up.
+ * If a tprintf message was given (or if this is a timed-out soft mount)
+ * then post a tprintf message indicating the server is alive again.
+ */
+ if (!error) {
+ if ((req->r_flags & R_TPRINTFMSG) ||
+ (nmp && NMFLAG(nmp, SOFT) &&
+ ((nmp->nm_state & (NFSSTA_TIMEO|NFSSTA_FORCE)) == NFSSTA_TIMEO)))
+ nfs_up(nmp, req->r_thread, NFSSTA_TIMEO, "is alive again");
+ else
+ nfs_up(nmp, req->r_thread, NFSSTA_TIMEO, NULL);
+ }
+ if (!error && !nmp)
+ error = ENXIO;
+ nfsmout_if(error);
+
+ /*
+ * break down the RPC header and check if ok
+ */
+ nmrep = req->r_nmrep;
+ nfsm_chain_get_32(error, &nmrep, reply_status);
+ nfsmout_if(error);
+ if (reply_status == RPC_MSGDENIED) {
+ nfsm_chain_get_32(error, &nmrep, rejected_status);
+ nfsmout_if(error);
+ if (rejected_status == RPC_MISMATCH) {
+ error = ENOTSUP;
+ goto nfsmout;
+ }
+ nfsm_chain_get_32(error, &nmrep, auth_status);
+ nfsmout_if(error);
+ switch (auth_status) {
+ case RPCSEC_GSS_CREDPROBLEM:
+ case RPCSEC_GSS_CTXPROBLEM:
+ /*
+ * An RPCSEC_GSS cred or context problem.
+ * We can't use it anymore.
+ * Restore the args, renew the context
+ * and set up for a resend.
+ */
+ error = nfs_gss_clnt_args_restore(req);
+ if (error && error != ENEEDAUTH)
+ break;
+
+ if (!error) {
+ error = nfs_gss_clnt_ctx_renew(req);
+ if (error)
+ break;
+ }
+ mbuf_freem(mrep);
+ req->r_nmrep.nmc_mhead = NULL;
+ req->r_xid = 0; // get a new XID
+ req->r_flags |= R_RESTART;
+ goto nfsmout;
+ default:
+ error = EACCES;
+ break;
+ }
+ goto nfsmout;
+ }
+
+ /* Now check the verifier */
+ nfsm_chain_get_32(error, &nmrep, verf_type); // verifier flavor
+ nfsm_chain_get_32(error, &nmrep, verf_len); // verifier length
+ nfsmout_if(error);
+
+ switch (req->r_auth) {
+ case RPCAUTH_NONE:
+ case RPCAUTH_SYS:
+ /* Any AUTH_SYS verifier is ignored */
+ if (verf_len > 0)
+ nfsm_chain_adv(error, &nmrep, nfsm_rndup(verf_len));
+ nfsm_chain_get_32(error, &nmrep, accepted_status);
+ break;
+ case RPCAUTH_KRB5:
+ case RPCAUTH_KRB5I:
+ case RPCAUTH_KRB5P:
+ error = nfs_gss_clnt_verf_get(req, &nmrep,
+ verf_type, verf_len, &accepted_status);
+ break;
+ }
+ nfsmout_if(error);
+
+ switch (accepted_status) {
+ case RPC_SUCCESS:
+ if (req->r_procnum == NFSPROC_NULL) {
+ /*
+ * The NFS null procedure is unique,
+ * in not returning an NFS status.
+ */
+ *status = NFS_OK;
+ } else {
+ nfsm_chain_get_32(error, &nmrep, *status);
+ nfsmout_if(error);
+ }
+
+ if ((nmp->nm_vers != NFS_VER2) && (*status == NFSERR_TRYLATER)) {
+ /*
+ * It's a JUKEBOX error - delay and try again
+ */
+ int delay, slpflag = (NMFLAG(nmp, INTR) && !(req->r_flags & R_NOINTR)) ? PCATCH : 0;
+
+ mbuf_freem(mrep);
+ req->r_nmrep.nmc_mhead = NULL;
+ 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);
+ nfs_softterm(req);
+ error = req->r_error;
+ goto nfsmout;
+ }
+ req->r_delay = !req->r_delay ? NFS_TRYLATERDEL : (req->r_delay * 2);
+ if (req->r_delay > 30)
+ req->r_delay = 30;
+ if (nmp->nm_tprintf_initial_delay && (req->r_delay >= nmp->nm_tprintf_initial_delay)) {
+ if (!(req->r_flags & R_JBTPRINTFMSG)) {
+ req->r_flags |= R_JBTPRINTFMSG;
+ lck_mtx_lock(&nmp->nm_lock);
+ nmp->nm_jbreqs++;
+ lck_mtx_unlock(&nmp->nm_lock);
+ }
+ nfs_down(req->r_nmp, req->r_thread, 0, NFSSTA_JUKEBOXTIMEO,
+ "resource temporarily unavailable (jukebox)");
+ }
+ 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);
+ nfs_softterm(req);
+ error = req->r_error;
+ goto nfsmout;
+ }
+ delay = req->r_delay;
+ if (req->r_callback.rcb_func) {
+ struct timeval now;
+ microuptime(&now);
+ req->r_resendtime = now.tv_sec + delay;
+ } else {
+ do {
+ if ((error = nfs_sigintr(req->r_nmp, req, req->r_thread, 0)))
+ goto nfsmout;
+ tsleep(&lbolt, PSOCK|slpflag, "nfs_jukebox_trylater", 0);
+ slpflag = 0;
+ } while (--delay > 0);
+ }
+ req->r_xid = 0; // get a new XID
+ req->r_flags |= R_RESTART;
+ req->r_start = 0;
+ FSDBG(273, R_XID32(req->r_xid), nmp, req, NFSERR_TRYLATER);
+ return (0);
+ }
+
+ if (req->r_flags & R_JBTPRINTFMSG) {
+ req->r_flags &= ~R_JBTPRINTFMSG;
+ lck_mtx_lock(&nmp->nm_lock);
+ nmp->nm_jbreqs--;
+ clearjbtimeo = (nmp->nm_jbreqs == 0) ? NFSSTA_JUKEBOXTIMEO : 0;
+ lck_mtx_unlock(&nmp->nm_lock);
+ nfs_up(nmp, req->r_thread, clearjbtimeo, "resource available again");
+ }
+
+ if ((nmp->nm_vers >= NFS_VER4) && (*status == NFSERR_WRONGSEC)) {
+ /*
+ * Hmmm... we need to try a different security flavor.
+ * The first time a request hits this, we will allocate an array
+ * to track flavors to try. We fill the array with the mount's
+ * preferred flavors or the server's preferred flavors or just the
+ * flavors we support.
+ */
+ uint32_t srvflavors[NX_MAX_SEC_FLAVORS];
+ int srvcount, i, j;
+
+ /* Call SECINFO to try to get list of flavors from server. */
+ srvcount = NX_MAX_SEC_FLAVORS;
+ nfs4_secinfo_rpc(nmp, &req->r_secinfo, req->r_cred, srvflavors, &srvcount);
+
+ if (!req->r_wrongsec) {
+ /* first time... set up flavor array */
+ MALLOC(req->r_wrongsec, uint32_t*, NX_MAX_SEC_FLAVORS*sizeof(uint32_t), M_TEMP, M_WAITOK);
+ if (!req->r_wrongsec) {
+ error = EACCES;
+ goto nfsmout;
+ }
+ i=0;
+ if (nmp->nm_sec.count) { /* use the mount's preferred list of flavors */
+ for(; i < nmp->nm_sec.count; i++)
+ req->r_wrongsec[i] = nmp->nm_sec.flavors[i];
+ } else if (srvcount) { /* otherwise use the server's list of flavors */
+ for(; i < srvcount; i++)
+ req->r_wrongsec[i] = srvflavors[i];
+ } else { /* otherwise, just try the flavors we support. */
+ req->r_wrongsec[i++] = RPCAUTH_KRB5P;
+ req->r_wrongsec[i++] = RPCAUTH_KRB5I;
+ req->r_wrongsec[i++] = RPCAUTH_KRB5;
+ req->r_wrongsec[i++] = RPCAUTH_SYS;
+ req->r_wrongsec[i++] = RPCAUTH_NONE;
+ }
+ for(; i < NX_MAX_SEC_FLAVORS; i++) /* invalidate any remaining slots */
+ req->r_wrongsec[i] = RPCAUTH_INVALID;
+ }
+
+ /* clear the current flavor from the list */
+ for(i=0; i < NX_MAX_SEC_FLAVORS; i++)
+ if (req->r_wrongsec[i] == req->r_auth)
+ req->r_wrongsec[i] = RPCAUTH_INVALID;
+
+ /* find the next flavor to try */
+ for(i=0; i < NX_MAX_SEC_FLAVORS; i++)
+ if (req->r_wrongsec[i] != RPCAUTH_INVALID) {
+ if (((req->r_wrongsec[i] == RPCAUTH_KRB5P) ||
+ (req->r_wrongsec[i] == RPCAUTH_KRB5I) ||
+ (req->r_wrongsec[i] == RPCAUTH_KRB5)) && (req->r_gss_ctx &&
+ (req->r_gss_ctx->gss_clnt_service == RPCSEC_GSS_SVC_SYS))) {
+ /* don't bother trying Kerberos if we've already got a fallback context */
+ req->r_wrongsec[i] = RPCAUTH_INVALID;
+ continue;
+ }
+ if (!srvcount) /* no server list, just try it */
+ break;
+ /* check that it's in the server's list */
+ for(j=0; j < srvcount; j++)
+ if (req->r_wrongsec[i] == srvflavors[j])
+ break;
+ if (j < srvcount) /* found */
+ break;
+ /* not found in server list */
+ req->r_wrongsec[i] = RPCAUTH_INVALID;
+ }
+ if (i == NX_MAX_SEC_FLAVORS) {
+ /* nothing left to try! */
+ error = EACCES;
+ goto nfsmout;
+ }
+
+ /* retry with the next auth flavor */
+ req->r_auth = req->r_wrongsec[i];
+ req->r_xid = 0; // get a new XID
+ req->r_flags |= R_RESTART;
+ req->r_start = 0;
+ FSDBG(273, R_XID32(req->r_xid), nmp, req, NFSERR_WRONGSEC);
+ return (0);
+ }
+ if ((nmp->nm_vers >= NFS_VER4) && req->r_wrongsec) {
+ /*
+ * We renegotiated security for this request; so update the
+ * default security flavor for the associated node.
+ */
+ if (req->r_np)
+ req->r_np->n_auth = req->r_auth;
+ }
+
+ if (*status == NFS_OK) {
+ /*
+ * Successful NFS request
+ */
+ *nmrepp = nmrep;
+ req->r_nmrep.nmc_mhead = NULL;
+ break;
+ }
+ /* Got an NFS error of some kind */
+
+ /*
+ * If the File Handle was stale, invalidate the
+ * lookup cache, just in case.
+ */
+ if ((*status == ESTALE) && req->r_np) {
+ cache_purge(NFSTOV(req->r_np));
+ /* if monitored, also send delete event */
+ if (vnode_ismonitored(NFSTOV(req->r_np)))
+ nfs_vnode_notify(req->r_np, (VNODE_EVENT_ATTRIB|VNODE_EVENT_DELETE));
+ }
+ if (nmp->nm_vers == NFS_VER2)
+ mbuf_freem(mrep);
+ else
+ *nmrepp = nmrep;
+ req->r_nmrep.nmc_mhead = NULL;
+ error = 0;
+ break;
+ case RPC_PROGUNAVAIL:
+ error = EPROGUNAVAIL;
+ break;
+ case RPC_PROGMISMATCH:
+ error = ERPCMISMATCH;
+ break;
+ case RPC_PROCUNAVAIL:
+ error = EPROCUNAVAIL;
+ break;
+ case RPC_GARBAGE:
+ error = EBADRPC;
+ break;
+ case RPC_SYSTEM_ERR:
+ default:
+ error = EIO;
+ break;
+ }
+nfsmout:
+ if (req->r_flags & R_JBTPRINTFMSG) {
+ req->r_flags &= ~R_JBTPRINTFMSG;
+ lck_mtx_lock(&nmp->nm_lock);
+ nmp->nm_jbreqs--;
+ clearjbtimeo = (nmp->nm_jbreqs == 0) ? NFSSTA_JUKEBOXTIMEO : 0;
+ lck_mtx_unlock(&nmp->nm_lock);
+ if (clearjbtimeo)
+ nfs_up(nmp, req->r_thread, clearjbtimeo, NULL);
+ }
+ FSDBG(273, R_XID32(req->r_xid), nmp, req,
+ (!error && (*status == NFS_OK)) ? 0xf0f0f0f0 : error);
+ return (error);
+}
+
+/*
+ * NFS request using a GSS/Kerberos security flavor?
+ */
+int
+nfs_request_using_gss(struct nfsreq *req)
+{
+ if (!req->r_gss_ctx)
+ return (0);
+ switch (req->r_auth) {
+ case RPCAUTH_KRB5:
+ case RPCAUTH_KRB5I:
+ case RPCAUTH_KRB5P:
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Perform an NFS request synchronously.
+ */
+
+int
+nfs_request(
+ nfsnode_t np,
+ mount_t mp, /* used only if !np */
+ struct nfsm_chain *nmrest,
+ int procnum,
+ vfs_context_t ctx,
+ struct nfsreq_secinfo_args *si,
+ struct nfsm_chain *nmrepp,
+ u_int64_t *xidp,
+ int *status)
+{
+ return nfs_request2(np, mp, nmrest, procnum,
+ vfs_context_thread(ctx), vfs_context_ucred(ctx),
+ si, 0, nmrepp, xidp, status);
+}
+
+int
+nfs_request2(
+ nfsnode_t np,
+ mount_t mp, /* used only if !np */
+ struct nfsm_chain *nmrest,
+ int procnum,
+ thread_t thd,
+ kauth_cred_t cred,
+ struct nfsreq_secinfo_args *si,
+ int flags,
+ struct nfsm_chain *nmrepp,
+ u_int64_t *xidp,
+ int *status)
+{
+ struct nfsreq rq, *req = &rq;
+ int error;
+
+ if ((error = nfs_request_create(np, mp, nmrest, procnum, thd, cred, &req)))
+ return (error);
+ req->r_flags |= (flags & R_OPTMASK);
+ if (si)
+ req->r_secinfo = *si;
+
+ FSDBG_TOP(273, R_XID32(req->r_xid), np, procnum, 0);
+ do {
+ req->r_error = 0;
+ req->r_flags &= ~R_RESTART;
+ if ((error = nfs_request_add_header(req)))
+ break;
+ if (xidp)
+ *xidp = req->r_xid;
+ if ((error = nfs_request_send(req, 1)))
+ break;
+ nfs_request_wait(req);
+ if ((error = nfs_request_finish(req, nmrepp, status)))
+ break;
+ } while (req->r_flags & R_RESTART);
+
+ FSDBG_BOT(273, R_XID32(req->r_xid), np, procnum, error);
+ nfs_request_rele(req);
+ return (error);
+}
+
+
+/*
+ * Set up a new null proc request to exchange GSS context tokens with the
+ * server. Associate the context that we are setting up with the request that we
+ * are sending.
+ */
+
+int
+nfs_request_gss(
+ mount_t mp,
+ struct nfsm_chain *nmrest,
+ thread_t thd,
+ kauth_cred_t cred,
+ int flags,
+ struct nfs_gss_clnt_ctx *cp, /* Set to gss context to renew or setup */
+ struct nfsm_chain *nmrepp,
+ int *status)
+{
+ struct nfsreq rq, *req = &rq;
+ int error;
+
+ 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);
+ return (NFSERR_EAUTH);
+ }
+ nfs_gss_clnt_ctx_ref(req, cp);
+
+ FSDBG_TOP(273, R_XID32(req->r_xid), NULL, NFSPROC_NULL, 0);
+ do {
+ req->r_error = 0;
+ req->r_flags &= ~R_RESTART;
+ if ((error = nfs_request_add_header(req)))
+ break;
+
+ if ((error = nfs_request_send(req, 1)))
+ break;
+ nfs_request_wait(req);
+ if ((error = nfs_request_finish(req, nmrepp, status)))
+ break;
+ } while (req->r_flags & R_RESTART);
+
+ FSDBG_BOT(273, R_XID32(req->r_xid), NULL, NFSPROC_NULL, error);
+ nfs_request_rele(req);
+ return (error);
+}
+
+/*
+ * Create and start an asynchronous NFS request.
+ */
+int
+nfs_request_async(
+ nfsnode_t np,
+ mount_t mp, /* used only if !np */
+ struct nfsm_chain *nmrest,
+ int procnum,
+ thread_t thd,
+ kauth_cred_t cred,
+ struct nfsreq_secinfo_args *si,
+ int flags,
+ struct nfsreq_cbinfo *cb,
+ struct nfsreq **reqp)
+{
+ struct nfsreq *req;
+ struct nfsmount *nmp;
+ int error, sent;
+
+ error = nfs_request_create(np, mp, nmrest, procnum, thd, cred, reqp);
+ req = *reqp;
+ FSDBG(274, (req ? R_XID32(req->r_xid) : 0), np, procnum, error);
+ if (error)
+ return (error);
+ req->r_flags |= (flags & R_OPTMASK);
+ req->r_flags |= R_ASYNC;
+ if (si)
+ req->r_secinfo = *si;
+ if (cb)
+ req->r_callback = *cb;
+ error = nfs_request_add_header(req);
+ if (!error) {
+ req->r_flags |= R_WAITSENT;
+ if (req->r_callback.rcb_func)
+ nfs_request_ref(req, 0);
+ error = nfs_request_send(req, 1);
+ lck_mtx_lock(&req->r_mtx);
+ if (!error && !(req->r_flags & R_SENT) && req->r_callback.rcb_func) {
+ /* make sure to wait until this async I/O request gets sent */
+ int slpflag = (req->r_nmp && NMFLAG(req->r_nmp, INTR) && req->r_thread && !(req->r_flags & R_NOINTR)) ? PCATCH : 0;
+ struct timespec ts = { 2, 0 };
+ while (!(req->r_flags & R_SENT)) {
+ if ((req->r_flags & R_RESENDQ) && ((nmp = req->r_nmp))) {
+ lck_mtx_lock(&nmp->nm_lock);
+ if ((nmp->nm_state & NFSSTA_RECOVER) && (req->r_rchain.tqe_next != NFSREQNOLIST)) {
+ /*
+ * It's not going to get off the resend queue if we're in recovery.
+ * So, just take it off ourselves. We could be holding mount state
+ * busy and thus holding up the start of recovery.
+ */
+ TAILQ_REMOVE(&nmp->nm_resendq, req, r_rchain);
+ req->r_rchain.tqe_next = NFSREQNOLIST;
+ if (req->r_flags & R_RESENDQ)
+ req->r_flags &= ~R_RESENDQ;
+ lck_mtx_unlock(&nmp->nm_lock);
+ req->r_flags |= R_SENDING;
+ lck_mtx_unlock(&req->r_mtx);
+ error = nfs_send(req, 1);
+ lck_mtx_lock(&req->r_mtx);
+ if (error)
+ break;
+ continue;
+ }
+ lck_mtx_unlock(&nmp->nm_lock);
+ }
+ if ((error = nfs_sigintr(req->r_nmp, req, req->r_thread, 0)))
+ break;
+ msleep(req, &req->r_mtx, slpflag | (PZERO - 1), "nfswaitsent", &ts);
+ slpflag = 0;
+ }