]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/nfs/nfs_socket.c
xnu-517.9.5.tar.gz
[apple/xnu.git] / bsd / nfs / nfs_socket.c
index 9b55ac48f845fcbf2e5f2e868f7518b1da5e7b5f..ff2f55066d0e05d3edf18ec5deea59a6d6fc2d0c 100644 (file)
@@ -1,24 +1,21 @@
 /*
- * Copyright (c) 2000-2003 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved.
  *
  * @APPLE_LICENSE_HEADER_START@
  * 
- * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
+ * The contents of this file constitute Original Code as defined in and
+ * are subject to the Apple Public Source License Version 1.1 (the
+ * "License").  You may not use this file except in compliance with the
+ * License.  Please obtain a copy of the License at
+ * http://www.apple.com/publicsource and read it before using this file.
  * 
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this
- * file.
- * 
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * This Original Code and all software distributed under the License are
+ * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
+ * License for the specific language governing rights and limitations
+ * under the License.
  * 
  * @APPLE_LICENSE_HEADER_END@
  */
@@ -84,6 +81,8 @@
 
 #include <sys/time.h>
 #include <kern/clock.h>
+#include <kern/task.h>
+#include <kern/thread.h>
 #include <sys/user.h>
 
 #include <netinet/in.h>
@@ -179,8 +178,6 @@ int nfsrtton = 0;
 struct nfsrtt nfsrtt;
 
 static int     nfs_msg __P((struct proc *, const char *, const char *, int));
-static void    nfs_up(struct nfsreq *, const char *, int);
-static void    nfs_down(struct nfsreq *, const char *, int);
 static int     nfs_rcvlock __P((struct nfsreq *));
 static void    nfs_rcvunlock __P((struct nfsreq *));
 static int     nfs_receive __P((struct nfsreq *rep, struct mbuf **aname,
@@ -189,6 +186,11 @@ static int nfs_reconnect __P((struct nfsreq *rep));
 static void    nfs_repbusy(struct nfsreq *rep);
 static struct nfsreq * nfs_repnext(struct nfsreq *rep);
 static void    nfs_repdequeue(struct nfsreq *rep);
+
+/* XXX */
+boolean_t      current_thread_aborted(void);
+kern_return_t  thread_terminate(thread_act_t);
+
 #ifndef NFS_NOSERVER 
 static int     nfsrv_getstream __P((struct nfssvc_sock *,int));
 
@@ -349,20 +351,139 @@ nfsdup(struct nfsreq *rep)
 }
 #endif /* NFSDIAG */
 
+
+/*
+ * attempt to bind a socket to a reserved port
+ */
+static int
+nfs_bind_resv(struct nfsmount *nmp)
+{
+       struct socket *so = nmp->nm_so;
+       struct sockaddr_in sin;
+       int error;
+       u_short tport;
+
+       if (!so)
+               return (EINVAL);
+
+       sin.sin_len = sizeof (struct sockaddr_in);
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = INADDR_ANY;
+       tport = IPPORT_RESERVED - 1;
+       sin.sin_port = htons(tport);
+
+       while (((error = sobind(so, (struct sockaddr *) &sin)) == EADDRINUSE) &&
+              (--tport > IPPORT_RESERVED / 2))
+               sin.sin_port = htons(tport);
+       return (error);
+}
+
+/*
+ * variables for managing the nfs_bind_resv_thread
+ */
+int nfs_resv_mounts = 0;
+static int nfs_bind_resv_thread_state = 0;
+#define NFS_BIND_RESV_THREAD_STATE_INITTED     1
+#define NFS_BIND_RESV_THREAD_STATE_RUNNING     2
+static struct slock nfs_bind_resv_slock;
+struct nfs_bind_resv_request {
+       TAILQ_ENTRY(nfs_bind_resv_request) brr_chain;
+       struct nfsmount *brr_nmp;
+       int brr_error;
+};
+static TAILQ_HEAD(, nfs_bind_resv_request) nfs_bind_resv_request_queue;
+
+/*
+ * thread to handle any reserved port bind requests
+ */
+static void
+nfs_bind_resv_thread(void)
+{
+       struct nfs_bind_resv_request *brreq;
+        boolean_t funnel_state;
+
+       funnel_state = thread_funnel_set(network_flock, TRUE);
+       nfs_bind_resv_thread_state = NFS_BIND_RESV_THREAD_STATE_RUNNING;
+
+       while (nfs_resv_mounts > 0) {
+               simple_lock(&nfs_bind_resv_slock);
+               while ((brreq = TAILQ_FIRST(&nfs_bind_resv_request_queue))) {
+                       TAILQ_REMOVE(&nfs_bind_resv_request_queue, brreq, brr_chain);
+                       simple_unlock(&nfs_bind_resv_slock);
+                       brreq->brr_error = nfs_bind_resv(brreq->brr_nmp);
+                       wakeup(brreq);
+                       simple_lock(&nfs_bind_resv_slock);
+               }
+               simple_unlock(&nfs_bind_resv_slock);
+               (void)tsleep((caddr_t)&nfs_bind_resv_request_queue, PSOCK,
+                               "nfs_bind_resv_request_queue", 0);
+       }
+
+       nfs_bind_resv_thread_state = NFS_BIND_RESV_THREAD_STATE_INITTED;
+       (void) thread_funnel_set(network_flock, funnel_state);
+       (void) thread_terminate(current_act());
+}
+
+int
+nfs_bind_resv_thread_wake(void)
+{
+       if (nfs_bind_resv_thread_state < NFS_BIND_RESV_THREAD_STATE_RUNNING)
+               return (EIO);
+       wakeup(&nfs_bind_resv_request_queue);
+       return (0);
+}
+
+/*
+ * underprivileged procs call this to request nfs_bind_resv_thread
+ * to perform the reserved port binding for them.
+ */
+static int
+nfs_bind_resv_nopriv(struct nfsmount *nmp)
+{
+       struct nfs_bind_resv_request brreq;
+       int error;
+
+       if (nfs_bind_resv_thread_state < NFS_BIND_RESV_THREAD_STATE_RUNNING) {
+               if (nfs_bind_resv_thread_state < NFS_BIND_RESV_THREAD_STATE_INITTED) {
+                       simple_lock_init(&nfs_bind_resv_slock);
+                       TAILQ_INIT(&nfs_bind_resv_request_queue);
+                       nfs_bind_resv_thread_state = NFS_BIND_RESV_THREAD_STATE_INITTED;
+               }
+               kernel_thread(kernel_task, nfs_bind_resv_thread);
+               nfs_bind_resv_thread_state = NFS_BIND_RESV_THREAD_STATE_RUNNING;
+       }
+
+       brreq.brr_nmp = nmp;
+       brreq.brr_error = 0;
+
+       simple_lock(&nfs_bind_resv_slock);
+       TAILQ_INSERT_TAIL(&nfs_bind_resv_request_queue, &brreq, brr_chain);
+       simple_unlock(&nfs_bind_resv_slock);
+
+       error = nfs_bind_resv_thread_wake();
+       if (error) {
+               TAILQ_REMOVE(&nfs_bind_resv_request_queue, &brreq, brr_chain);
+               /* Note: we might be able to simply restart the thread */
+               return (error);
+       }
+
+       (void) tsleep((caddr_t)&brreq, PSOCK, "nfsbindresv", 0);
+
+       return (brreq.brr_error);
+}
+
 /*
  * Initialize sockets and congestion for a new NFS connection.
  * We do not free the sockaddr if error.
  */
 int
 nfs_connect(nmp, rep)
-       register struct nfsmount *nmp;
+       struct nfsmount *nmp;
        struct nfsreq *rep;
 {
-       register struct socket *so;
+       struct socket *so;
        int s, error, rcvreserve, sndreserve;
        struct sockaddr *saddr;
-       struct sockaddr_in sin;
-       u_short tport;
 
        thread_funnel_switch(KERNEL_FUNNEL, NETWORK_FUNNEL);
        nmp->nm_so = (struct socket *)0;
@@ -379,18 +500,22 @@ nfs_connect(nmp, rep)
         * Some servers require that the client port be a reserved port number.
         */
        if (saddr->sa_family == AF_INET && (nmp->nm_flag & NFSMNT_RESVPORT)) {
-               sin.sin_len = sizeof (struct sockaddr_in);
-               sin.sin_family = AF_INET;
-               sin.sin_addr.s_addr = INADDR_ANY;
-               tport = IPPORT_RESERVED - 1;
-               sin.sin_port = htons(tport);
-
-               while ((error = sobind(so, (struct sockaddr *) &sin) == EADDRINUSE) &&
-                      (--tport > IPPORT_RESERVED / 2))
-                       sin.sin_port = htons(tport);
-               if (error) {
-                       goto bad;
+               struct proc *p;
+               /*
+                * sobind() requires current_proc() to have superuser privs.
+                * If this bind is part of a reconnect, and the current proc
+                * doesn't have superuser privs, we hand the sobind() off to
+                * a kernel thread to process.
+                */
+               if ((nmp->nm_state & NFSSTA_MOUNTED) &&
+                   (p = current_proc()) && suser(p->p_ucred, &p->p_acflag)) {
+                       /* request nfs_bind_resv_thread() to do bind */
+                       error = nfs_bind_resv_nopriv(nmp);
+               } else {
+                       error = nfs_bind_resv(nmp);
                }
+               if (error)
+                       goto bad;
        }
 
        /*
@@ -540,7 +665,15 @@ nfs_reconnect(rep)
                        return (EINTR);
                if (error == EIO)
                        return (EIO);
-               nfs_down(rep, "can not connect", error);
+               nfs_down(rep, rep->r_nmp, rep->r_procp, "can not connect",
+                       error, NFSSTA_TIMEO);
+               if (!(nmp->nm_state & NFSSTA_MOUNTED)) {
+                       /* we're not yet completely mounted and */
+                       /* we can't reconnect, so we fail */
+                       return (error);
+               }
+               if ((error = nfs_sigintr(rep->r_nmp, rep, rep->r_procp)))
+                       return (error);
                (void) tsleep((caddr_t)&lbolt, PSOCK, "nfscon", 0);
        }
 
@@ -864,6 +997,7 @@ tryagain:
 
                        thread_funnel_switch(KERNEL_FUNNEL, NETWORK_FUNNEL);
                        do {
+                           control = NULL;
                            rcvflg = 0;
                            error =  soreceive(so, (struct sockaddr **)0,
                                               &auio, mp, &control, &rcvflg);
@@ -902,10 +1036,12 @@ errout:
                                    error,
                                 rep->r_nmp->nm_mountp->mnt_stat.f_mntfromname);
                        error = nfs_sndlock(rep);
-                       if (!error)
+                       if (!error) {
                                error = nfs_reconnect(rep);
-                       if (!error)
-                               goto tryagain;
+                               if (!error)
+                                       goto tryagain;
+                               nfs_sndunlock(rep);
+                       }
                }
        } else {
                /*
@@ -1049,13 +1185,14 @@ nfs_reply(myrep)
                 * Get the next Rpc reply off the socket. Assume myrep->r_nmp
                 * is still intact by checks done in nfs_rcvlock.
                 */
+               /* XXX why do we ask for nam here? we don't use it! */
                error = nfs_receive(myrep, &nam, &mrep);
                if (nam)
                        m_freem(nam);
                /*
                 * Bailout asap if nfsmount struct gone (unmounted). 
                 */
-               if (!myrep->r_nmp || !nmp->nm_so) {
+               if (!myrep->r_nmp) {
                        FSDBG(530, myrep->r_xid, myrep, nmp, -2);
                        return (ENXIO);
                }
@@ -1064,14 +1201,15 @@ nfs_reply(myrep)
                        nfs_rcvunlock(myrep);
 
                        /* Bailout asap if nfsmount struct gone (unmounted). */
-                       if (!myrep->r_nmp || !nmp->nm_so)
+                       if (!myrep->r_nmp)
                                return (ENXIO);
 
                        /*
                         * Ignore routing errors on connectionless protocols??
                         */
                        if (NFSIGNORE_SOERROR(nmp->nm_soflags, error)) {
-                               nmp->nm_so->so_error = 0;
+                               if (nmp->nm_so)
+                                       nmp->nm_so->so_error = 0;
                                if (myrep->r_flags & R_GETONEREP)
                                        return (0);
                                continue;
@@ -1093,6 +1231,7 @@ nfs_reply(myrep)
                  * just check here and get out. (ekn)
                 */
                if (!mrep) {
+                       nfs_rcvunlock(myrep);
                         FSDBG(530, myrep->r_xid, myrep, nmp, -3);
                         return (ENXIO); /* sounds good */
                 }
@@ -1267,6 +1406,8 @@ nfs_request(vp, mrest, procnum, procp, cred, mrp, mdp, dposp, xidp)
        int nmsotype;
        struct timeval now;
 
+       if (mrp)
+               *mrp = NULL;
        if (xidp)
                *xidp = 0;
 
@@ -1461,7 +1602,8 @@ tryagain:
         * If there was a successful reply and a tprintf msg.
         * tprintf a response.
         */
-       nfs_up(rep, "is alive again", error);
+       if (!error)
+               nfs_up(rep, nmp, procp, "is alive again", NFSSTA_TIMEO);
        mrep = rep->r_mrep;
        md = rep->r_md;
        dpos = rep->r_dpos;
@@ -1551,8 +1693,10 @@ tryagain:
                                *mdp = md;
                                *dposp = dpos;
                                error |= NFSERR_RETERR;
-                       } else
+                       } else {
                                m_freem(mrep);
+                               error &= ~NFSERR_RETERR;
+                       }
                        m_freem(rep->r_mreq);
                        FSDBG_BOT(531, error, rep->r_xid, nmp, rep);
                        FREE_ZONE((caddr_t)rep,
@@ -1907,7 +2051,15 @@ nfs_timer(arg)
                    (rep->r_rexmit > 2 || (rep->r_flags & R_RESENDERR)) &&
                    rep->r_lastmsg + nmp->nm_tprintf_delay < now.tv_sec) {
                        rep->r_lastmsg = now.tv_sec;
-                       nfs_down(rep, "not responding", 0);
+                       nfs_down(rep, rep->r_nmp, rep->r_procp, "not responding",
+                               0, NFSSTA_TIMEO);
+                       if (!(nmp->nm_state & NFSSTA_MOUNTED)) {
+                               /* we're not yet completely mounted and */
+                               /* we can't complete an RPC, so we fail */
+                               nfsstats.rpctimeouts++;
+                               nfs_softterm(rep);
+                               continue;
+                       }
                }
                if (rep->r_rtt >= 0) {
                        rep->r_rtt++;
@@ -2024,11 +2176,11 @@ nfs_timer(arg)
                                rep->r_rtt = 0;
                }
        }
+       microuptime(&now);
 #ifndef NFS_NOSERVER
        /*
         * Call the nqnfs server timer once a second to handle leases.
         */
-       microuptime(&now);
        if (lasttime != now.tv_sec) {
                lasttime = now.tv_sec;
                nqnfs_serverd();
@@ -2046,6 +2198,15 @@ nfs_timer(arg)
        }
 #endif /* NFS_NOSERVER */
        splx(s);
+
+       if (nfsbuffreeuptimestamp + 30 <= now.tv_sec) {
+               /*
+                * We haven't called nfs_buf_freeup() in a little while.
+                * So, see if we can free up any stale/unused bufs now.
+                */
+               nfs_buf_freeup(1);
+       }
+
        timeout(nfs_timer_funnel, (void *)0, nfs_ticks);
 
 }
@@ -2306,7 +2467,7 @@ nfsrv_rcv(so, arg, waitflag)
        register struct nfssvc_sock *slp = (struct nfssvc_sock *)arg;
        register struct mbuf *m;
        struct mbuf *mp, *mhck;
-       struct sockaddr *nam=0;
+       struct sockaddr *nam;
        struct uio auio;
        int flags, ns_nflag=0, error;
        struct sockaddr_in  *sin;
@@ -2372,8 +2533,9 @@ nfsrv_rcv(so, arg, waitflag)
        } else {
                do {
                        auio.uio_resid = 1000000000;
-                       flags = MSG_DONTWAIT;
+                       flags = MSG_DONTWAIT | MSG_NEEDSA;
                        nam = 0;
+                       mp = 0;
                        error = soreceive(so, &nam, &auio, &mp,
                                                (struct mbuf **)0, &flags);
                        
@@ -2384,7 +2546,6 @@ nfsrv_rcv(so, arg, waitflag)
                                        sin = mtod(mhck, struct sockaddr_in *);
                                        bcopy(nam, sin, sizeof(struct sockaddr_in));
                                        mhck->m_hdr.mh_len = sizeof(struct sockaddr_in);
-                                       FREE(nam, M_SONAME);
 
                                        m = mhck;
                                        m->m_next = mp;
@@ -2397,6 +2558,9 @@ nfsrv_rcv(so, arg, waitflag)
                                slp->ns_recend = m;
                                m->m_nextpkt = (struct mbuf *)0;
                        }
+                       if (nam) {
+                               FREE(nam, M_SONAME);
+                       }
                        if (error) {
                                if ((so->so_proto->pr_flags & PR_CONNREQUIRED)
                                        && error != EWOULDBLOCK) {
@@ -2891,41 +3055,52 @@ nfs_msg(p, server, msg, error)
        return (0);
 }
 
-static void
-nfs_down(rep, msg, error)
+void
+nfs_down(rep, nmp, proc, msg, error, flags)
        struct nfsreq *rep;
+       struct nfsmount *nmp;
+       struct proc *proc;
        const char *msg;
-       int error;
+       int error, flags;
 {
-       int dosignal;
-
-       if (rep == NULL || rep->r_nmp == NULL)
+       if (nmp == NULL)
                return;
-       if (!(rep->r_nmp->nm_state & NFSSTA_TIMEO)) {
-               vfs_event_signal(&rep->r_nmp->nm_mountp->mnt_stat.f_fsid,
+       if ((flags & NFSSTA_TIMEO) && !(nmp->nm_state & NFSSTA_TIMEO)) {
+               vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid,
                    VQ_NOTRESP, 0);
-               rep->r_nmp->nm_state |= NFSSTA_TIMEO;
+               nmp->nm_state |= NFSSTA_TIMEO;
+       }
+       if ((flags & NFSSTA_LOCKTIMEO) && !(nmp->nm_state & NFSSTA_LOCKTIMEO)) {
+               vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid,
+                   VQ_NOTRESPLOCK, 0);
+               nmp->nm_state |= NFSSTA_LOCKTIMEO;
        }
-       rep->r_flags |= R_TPRINTFMSG;
-       nfs_msg(rep->r_procp, rep->r_nmp->nm_mountp->mnt_stat.f_mntfromname,
-           msg, error);
+       if (rep)
+               rep->r_flags |= R_TPRINTFMSG;
+       nfs_msg(proc, nmp->nm_mountp->mnt_stat.f_mntfromname, msg, error);
 }
 
-static void
-nfs_up(rep, msg, error)
+void
+nfs_up(rep, nmp, proc, msg, flags)
        struct nfsreq *rep;
+       struct nfsmount *nmp;
+       struct proc *proc;
        const char *msg;
-       int error;
+       int flags;
 {
-
-       if (error != 0 || rep == NULL || rep->r_nmp == NULL)
+       if (nmp == NULL)
                return;
-       if ((rep->r_flags & R_TPRINTFMSG) != 0)
-               nfs_msg(rep->r_procp,
-                   rep->r_nmp->nm_mountp->mnt_stat.f_mntfromname, msg, 0);
-       if ((rep->r_nmp->nm_state & NFSSTA_TIMEO)) {
-               rep->r_nmp->nm_state &= ~NFSSTA_TIMEO;
-               vfs_event_signal(&rep->r_nmp->nm_mountp->mnt_stat.f_fsid,
+       if ((rep == NULL) || (rep->r_flags & R_TPRINTFMSG) != 0)
+               nfs_msg(proc, nmp->nm_mountp->mnt_stat.f_mntfromname, msg, 0);
+       if ((flags & NFSSTA_TIMEO) && (nmp->nm_state & NFSSTA_TIMEO)) {
+               nmp->nm_state &= ~NFSSTA_TIMEO;
+               vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid,
                    VQ_NOTRESP, 1);
        }
+       if ((flags & NFSSTA_LOCKTIMEO) && (nmp->nm_state & NFSSTA_LOCKTIMEO)) {
+               nmp->nm_state &= ~NFSSTA_LOCKTIMEO;
+               vfs_event_signal(&nmp->nm_mountp->mnt_stat.f_fsid,
+                   VQ_NOTRESPLOCK, 1);
+       }
 }
+