]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/mount/network.c
libnfs.a: eliminate conn.c and conn.h
[nfs-utils.git] / utils / mount / network.c
index c11fa3ea2dd72eb33b3e9c3c02d2fa507314dcc5..efedf25462f89d0989e0903ec6634b0500e090b9 100644 (file)
 #include <rpc/pmap_clnt.h>
 #include <sys/socket.h>
 
-#include "conn.h"
 #include "xcommon.h"
 #include "mount.h"
 #include "nls.h"
-#include "nfsumount.h"
 #include "nfs_mount.h"
 #include "mount_constants.h"
 #include "network.h"
 #define NFS_PORT 2049
 #endif
 
+#if SIZEOF_SOCKLEN_T - 0 == 0
+#define socklen_t unsigned int
+#endif
+
 extern int nfs_mount_data_version;
 extern char *progname;
 extern int verbose;
 
+static const unsigned long nfs_to_mnt[] = {
+       0,
+       0,
+       1,
+       3,
+};
+
+static const unsigned long mnt_to_nfs[] = {
+       0,
+       2,
+       2,
+       3,
+};
+
+/*
+ * Map an NFS version into the corresponding Mountd version
+ */
+unsigned long nfsvers_to_mnt(const unsigned long vers)
+{
+       if (vers <= 3)
+               return nfs_to_mnt[vers];
+       return 0;
+}
+
+/*
+ * Map a Mountd version into the corresponding NFS version
+ */
+static unsigned long mntvers_to_nfs(const unsigned long vers)
+{
+       if (vers <= 3)
+               return mnt_to_nfs[vers];
+       return 0;
+}
+
 static const unsigned int probe_udp_only[] = {
        IPPROTO_UDP,
        0,
@@ -106,12 +142,13 @@ int nfs_gethostbyname(const char *hostname, struct sockaddr_in *saddr)
        saddr->sin_family = AF_INET;
        if (!inet_aton(hostname, &saddr->sin_addr)) {
                if ((hp = gethostbyname(hostname)) == NULL) {
-                       nfs_error(_("mount: can't get address for %s\n"),
-                               hostname);
+                       nfs_error(_("%s: can't get address for %s\n"),
+                                       progname, hostname);
                        return 0;
                } else {
                        if (hp->h_length > sizeof(*saddr)) {
-                               nfs_error(_("mount: got bad hp->h_length\n"));
+                               nfs_error(_("%s: got bad hp->h_length\n"),
+                                               progname);
                                hp->h_length = sizeof(*saddr);
                        }
                        memcpy(&saddr->sin_addr, hp->h_addr, hp->h_length);
@@ -121,42 +158,145 @@ int nfs_gethostbyname(const char *hostname, struct sockaddr_in *saddr)
 }
 
 /*
- * getport() is very similar to pmap_getport() with
- * the exception this version uses a non-reserve ports
- * instead of reserve ports since reserve ports
- * are not needed for pmap requests.
+ * Create a socket that is locally bound to a reserved or non-reserved
+ * port. For any failures, RPC_ANYSOCK is returned which will cause 
+ * the RPC code to create the socket instead. 
  */
-unsigned short getport(struct sockaddr_in *saddr, unsigned long prog,
-                       unsigned long vers, unsigned int prot)
+static int get_socket(struct sockaddr_in *saddr, unsigned int p_prot,
+                       int resvp, int conn)
+{
+       int so, cc, type;
+       struct sockaddr_in laddr;
+       socklen_t namelen = sizeof(laddr);
+
+       type = (p_prot == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM);
+       if ((so = socket (AF_INET, type, p_prot)) < 0)
+               goto err_socket;
+
+       laddr.sin_family = AF_INET;
+       laddr.sin_port = 0;
+       laddr.sin_addr.s_addr = htonl(INADDR_ANY);
+       if (resvp) {
+               if (bindresvport(so, &laddr) < 0)
+                       goto err_bindresvport;
+       } else {
+               cc = bind(so, (struct sockaddr *)&laddr, namelen);
+               if (cc < 0)
+                       goto err_bind;
+       }
+       if (type == SOCK_STREAM || (conn && type == SOCK_DGRAM)) {
+               cc = connect(so, (struct sockaddr *)saddr, namelen);
+               if (cc < 0)
+                       goto err_connect;
+       }
+       return so;
+
+err_socket:
+       rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+       rpc_createerr.cf_error.re_errno = errno;
+       if (verbose) {
+               nfs_error(_("%s: Unable to create %s socket: errno %d (%s)\n"),
+                       progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"),
+                       errno, strerror(errno));
+       }
+       return RPC_ANYSOCK;
+
+err_bindresvport:
+       rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+       rpc_createerr.cf_error.re_errno = errno;
+       if (verbose) {
+               nfs_error(_("%s: Unable to bindresvport %s socket: errno %d"
+                               " (%s)\n"),
+                       progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"),
+                       errno, strerror(errno));
+       }
+       close(so);
+       return RPC_ANYSOCK;
+
+err_bind:
+       rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+       rpc_createerr.cf_error.re_errno = errno;
+       if (verbose) {
+               nfs_error(_("%s: Unable to bind to %s socket: errno %d (%s)\n"),
+                       progname, p_prot == IPPROTO_UDP ? _("UDP") : _("TCP"),
+                       errno, strerror(errno));
+       }
+       close(so);
+       return RPC_ANYSOCK;
+
+err_connect:
+       rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+       rpc_createerr.cf_error.re_errno = errno;
+       if (verbose) {
+               nfs_error(_("%s: Unable to connect to %s:%d, errno %d (%s)\n"),
+                       progname, inet_ntoa(saddr->sin_addr),
+                       ntohs(saddr->sin_port), errno, strerror(errno));
+       }
+       close(so);
+       return RPC_ANYSOCK;
+}
+
+/*
+ * getport() is very similar to pmap_getport() with the exception that
+ * this version tries to use an ephemeral port, since reserved ports are
+ * not needed for GETPORT queries.  This conserves the very limited
+ * reserved port space, which helps reduce failed socket binds
+ * during mount storms.
+ *
+ * A side effect of calling this function is that rpccreateerr is set.
+ */
+static unsigned short getport(struct sockaddr_in *saddr,
+                               unsigned long program,
+                               unsigned long version,
+                               unsigned int proto)
 {
        unsigned short port = 0;
        int socket;
        CLIENT *clnt = NULL;
-       struct pmap parms;
        enum clnt_stat stat;
 
-       saddr->sin_port = htons (PMAPPORT);
-       socket = get_socket(saddr, prot, FALSE, FALSE);
+       saddr->sin_port = htons(PMAPPORT);
+
+       /*
+        * Try to get a socket with a non-privileged port.
+        * clnt*create() will create one anyway if this
+        * fails.
+        */
+       socket = get_socket(saddr, proto, FALSE, FALSE);
+       if (socket == RPC_ANYSOCK) {
+               if (proto == IPPROTO_TCP && errno == ETIMEDOUT) {
+                       /*
+                        * TCP SYN timed out, so exit now.
+                        */
+                       rpc_createerr.cf_stat = RPC_TIMEDOUT;
+               }
+               return 0;
+       }
 
-       switch (prot) {
+       switch (proto) {
        case IPPROTO_UDP:
                clnt = clntudp_bufcreate(saddr,
-                                        PMAPPROG, PMAPVERS, TIMEOUT, &socket,
-                                        UDPMSGSIZE, UDPMSGSIZE);
+                                        PMAPPROG, PMAPVERS,
+                                        RETRY_TIMEOUT, &socket,
+                                        RPCSMALLMSGSIZE,
+                                        RPCSMALLMSGSIZE);
                break;
        case IPPROTO_TCP:
-               clnt = clnttcp_create(saddr,
-                       PMAPPROG, PMAPVERS, &socket, 50, 500);
+               clnt = clnttcp_create(saddr, PMAPPROG, PMAPVERS, &socket,
+                                     RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
                break;
        }
        if (clnt != NULL) {
-               parms.pm_prog = prog;
-               parms.pm_vers = vers;
-               parms.pm_prot = prot;
-               parms.pm_port = 0;    /* not needed or used */
-
-               stat = clnt_call(clnt, PMAPPROC_GETPORT, (xdrproc_t)xdr_pmap,
-                       (caddr_t)&parms, (xdrproc_t)xdr_u_short, (caddr_t)&port, TIMEOUT);
+               struct pmap parms = {
+                       .pm_prog        = program,
+                       .pm_vers        = version,
+                       .pm_prot        = proto,
+               };
+
+               stat = clnt_call(clnt, PMAPPROC_GETPORT,
+                                (xdrproc_t)xdr_pmap, (caddr_t)&parms,
+                                (xdrproc_t)xdr_u_short, (caddr_t)&port,
+                                TIMEOUT);
                if (stat) {
                        clnt_geterr(clnt, &rpc_createerr.cf_error);
                        rpc_createerr.cf_stat = stat;
@@ -244,7 +384,7 @@ out_ok:
        return 1;
 }
 
-int probe_nfsport(clnt_addr_t *nfs_server)
+static int probe_nfsport(clnt_addr_t *nfs_server)
 {
        struct pmap *pmap = &nfs_server->pmap;
 
@@ -257,7 +397,7 @@ int probe_nfsport(clnt_addr_t *nfs_server)
                return probe_port(nfs_server, probe_nfs2_only, probe_udp_only);
 }
 
-int probe_mntport(clnt_addr_t *mnt_server)
+static int probe_mntport(clnt_addr_t *mnt_server)
 {
        struct pmap *pmap = &mnt_server->pmap;
 
@@ -316,3 +456,214 @@ version_fixed:
                goto out_bad;
        return probe_mntport(mnt_server);
 }
+
+static int probe_statd(void)
+{
+       struct sockaddr_in addr;
+       unsigned short port;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sin_family = AF_INET;
+       addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+       port = getport(&addr, 100024, 1, IPPROTO_UDP);
+
+       if (port == 0)
+               return 0;
+       addr.sin_port = htons(port);
+
+       if (clnt_ping(&addr, 100024, 1, IPPROTO_UDP, NULL) <= 0)
+               return 0;
+
+       return 1;
+}
+
+/*
+ * Attempt to start rpc.statd
+ */
+int start_statd(void)
+{
+#ifdef START_STATD
+       struct stat stb;
+#endif
+
+       if (probe_statd())
+               return 1;
+
+#ifdef START_STATD
+       if (stat(START_STATD, &stb) == 0) {
+               if (S_ISREG(stb.st_mode) && (stb.st_mode & S_IXUSR)) {
+                       system(START_STATD);
+                       if (probe_statd())
+                               return 1;
+               }
+       }
+#endif
+
+       return 0;
+}
+
+/*
+ * nfs_call_umount - ask the server to remove a share from it's rmtab
+ * @mnt_server: address of RPC MNT program server
+ * @argp: directory path of share to "unmount"
+ *
+ * Returns one if the unmount call succeeded; zero if the unmount
+ * failed for any reason.
+ *
+ * Note that a side effect of calling this function is that rpccreateerr
+ * is set.
+ */
+int nfs_call_umount(clnt_addr_t *mnt_server, dirpath *argp)
+{
+       CLIENT *clnt;
+       enum clnt_stat res = 0;
+       int msock;
+
+       switch (mnt_server->pmap.pm_vers) {
+       case 3:
+       case 2:
+       case 1:
+               if (!probe_mntport(mnt_server))
+                       return 0;
+               clnt = mnt_openclnt(mnt_server, &msock);
+               if (!clnt)
+                       return 0;
+               res = clnt_call(clnt, MOUNTPROC_UMNT,
+                               (xdrproc_t)xdr_dirpath, (caddr_t)argp,
+                               (xdrproc_t)xdr_void, NULL,
+                               TIMEOUT);
+               mnt_closeclnt(clnt, msock);
+               if (res == RPC_SUCCESS)
+                       return 1;
+               break;
+       default:
+               res = RPC_SUCCESS;
+               break;
+       }
+
+       if (res == RPC_SUCCESS)
+               return 1;
+       return 0;
+}
+
+CLIENT *mnt_openclnt(clnt_addr_t *mnt_server, int *msock)
+{
+       struct sockaddr_in *mnt_saddr = &mnt_server->saddr;
+       struct pmap *mnt_pmap = &mnt_server->pmap;
+       CLIENT *clnt = NULL;
+
+       mnt_saddr->sin_port = htons((u_short)mnt_pmap->pm_port);
+       *msock = get_socket(mnt_saddr, mnt_pmap->pm_prot, TRUE, FALSE);
+       if (*msock == RPC_ANYSOCK) {
+               if (rpc_createerr.cf_error.re_errno == EADDRINUSE)
+                       /*
+                        * Probably in-use by a TIME_WAIT connection,
+                        * It is worth waiting a while and trying again.
+                        */
+                       rpc_createerr.cf_stat = RPC_TIMEDOUT;
+               return NULL;
+       }
+
+       switch (mnt_pmap->pm_prot) {
+       case IPPROTO_UDP:
+               clnt = clntudp_bufcreate(mnt_saddr,
+                                        mnt_pmap->pm_prog, mnt_pmap->pm_vers,
+                                        RETRY_TIMEOUT, msock,
+                                        MNT_SENDBUFSIZE, MNT_RECVBUFSIZE);
+               break;
+       case IPPROTO_TCP:
+               clnt = clnttcp_create(mnt_saddr,
+                                     mnt_pmap->pm_prog, mnt_pmap->pm_vers,
+                                     msock,
+                                     MNT_SENDBUFSIZE, MNT_RECVBUFSIZE);
+               break;
+       }
+       if (clnt) {
+               /* try to mount hostname:dirname */
+               clnt->cl_auth = authunix_create_default();
+               return clnt;
+       }
+       return NULL;
+}
+
+void mnt_closeclnt(CLIENT *clnt, int msock)
+{
+       auth_destroy(clnt->cl_auth);
+       clnt_destroy(clnt);
+       close(msock);
+}
+
+/*
+ * Sigh... getport() doesn't actually check the version number.
+ * In order to make sure that the server actually supports the service
+ * we're requesting, we open and RPC client, and fire off a NULL
+ * RPC call.
+ */
+int clnt_ping(struct sockaddr_in *saddr, const unsigned long prog,
+               const unsigned long vers, const unsigned int prot,
+               struct sockaddr_in *caddr)
+{
+       CLIENT *clnt = NULL;
+       int sock, stat;
+       static char clnt_res;
+       struct sockaddr dissolve;
+
+       rpc_createerr.cf_stat = stat = errno = 0;
+       sock = get_socket(saddr, prot, FALSE, TRUE);
+       if (sock == RPC_ANYSOCK) {
+               if (errno == ETIMEDOUT) {
+                       /*
+                        * TCP timeout. Bubble up the error to see 
+                        * how it should be handled.
+                        */
+                       rpc_createerr.cf_stat = RPC_TIMEDOUT;
+               }
+               return 0;
+       }
+
+       if (caddr) {
+               /* Get the address of our end of this connection */
+               socklen_t len = sizeof(*caddr);
+               if (getsockname(sock, caddr, &len) != 0)
+                       caddr->sin_family = 0;
+       }
+
+       switch(prot) {
+       case IPPROTO_UDP:
+               /* The socket is connected (so we could getsockname successfully),
+                * but some servers on multi-homed hosts reply from
+                * the wrong address, so if we stay connected, we lose the reply.
+                */
+               dissolve.sa_family = AF_UNSPEC;
+               connect(sock, &dissolve, sizeof(dissolve));
+
+               clnt = clntudp_bufcreate(saddr, prog, vers,
+                                        RETRY_TIMEOUT, &sock,
+                                        RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
+               break;
+       case IPPROTO_TCP:
+               clnt = clnttcp_create(saddr, prog, vers, &sock,
+                                     RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
+               break;
+       }
+       if (!clnt) {
+               close(sock);
+               return 0;
+       }
+       memset(&clnt_res, 0, sizeof(clnt_res));
+       stat = clnt_call(clnt, NULLPROC,
+                        (xdrproc_t)xdr_void, (caddr_t)NULL,
+                        (xdrproc_t)xdr_void, (caddr_t)&clnt_res,
+                        TIMEOUT);
+       if (stat) {
+               clnt_geterr(clnt, &rpc_createerr.cf_error);
+               rpc_createerr.cf_stat = stat;
+       }
+       clnt_destroy(clnt);
+       close(sock);
+
+       if (stat == RPC_SUCCESS)
+               return 1;
+       else
+               return 0;
+}