]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/mount/network.c
pdate addres for Free Software Foundation
[nfs-utils.git] / utils / mount / network.c
index c997c4cc0ee30e86d482bd23c4cc7646d53c5a49..e7bd522ac8da7927a7cfde97e3fc968137d356ab 100644 (file)
  *
  * You should have received a copy of the GNU General Public
  * License along with this program; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 021110-1307, USA.
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA
  *
  */
 
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
 #include <ctype.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <errno.h>
 #include <netdb.h>
 #include <time.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
 #include <rpc/rpc.h>
 #include <rpc/pmap_prot.h>
 #include <rpc/pmap_clnt.h>
-#include <sys/socket.h>
 
-#include "conn.h"
+#include "sockaddr.h"
 #include "xcommon.h"
 #include "mount.h"
 #include "nls.h"
 #include "nfs_mount.h"
 #include "mount_constants.h"
+#include "nfsrpc.h"
+#include "parse_opt.h"
 #include "network.h"
+#include "conffile.h"
+#include "nfslib.h"
 
-#ifdef HAVE_RPCSVC_NFS_PROT_H
-#include <rpcsvc/nfs_prot.h>
-#else
-#include <linux/nfs.h>
-#define nfsstat nfs_stat
-#endif
+#define PMAP_TIMEOUT   (10)
+#define CONNECT_TIMEOUT        (20)
+#define MOUNT_TIMEOUT  (30)
 
-#ifndef NFS_PORT
-#define NFS_PORT 2049
-#endif
+#define SAFE_SOCKADDR(x)       (struct sockaddr *)(char *)(x)
 
 extern int nfs_mount_data_version;
 extern char *progname;
 extern int verbose;
 
+static const char *nfs_ns_pgmtbl[] = {
+       "status",
+       NULL,
+};
+
+static const char *nfs_mnt_pgmtbl[] = {
+       "mount",
+       "mountd",
+       NULL,
+};
+
+static const char *nfs_nfs_pgmtbl[] = {
+       "nfs",
+       "nfsprog",
+       NULL,
+};
+
+static const char *nfs_transport_opttbl[] = {
+       "udp",
+       "tcp",
+       "rdma",
+       "proto",
+       NULL,
+};
+
+static const char *nfs_version_opttbl[] = {
+       "v2",
+       "v3",
+       "v4",
+       "vers",
+       "nfsvers",
+       NULL,
+};
+
+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,
@@ -98,163 +174,421 @@ static const unsigned long probe_mnt3_first[] = {
        0,
 };
 
-int nfs_gethostbyname(const char *hostname, struct sockaddr_in *saddr)
+static const unsigned int *nfs_default_proto(void);
+#ifdef MOUNT_CONFIG
+static const unsigned int *nfs_default_proto()
 {
-       struct hostent *hp;
+       extern unsigned long config_default_proto;
+       /*
+        * If the default proto has been set and 
+        * its not TCP, start with UDP
+        */
+       if (config_default_proto && config_default_proto != IPPROTO_TCP)
+               return probe_udp_first;
 
-       saddr->sin_family = AF_INET;
-       if (!inet_aton(hostname, &saddr->sin_addr)) {
-               if ((hp = gethostbyname(hostname)) == NULL) {
-                       nfs_error(_("%s: can't get address for %s\n"),
-                                       progname, hostname);
-                       return 0;
-               } else {
-                       if (hp->h_length > sizeof(*saddr)) {
-                               nfs_error(_("%s: got bad hp->h_length\n"),
-                                               progname);
-                               hp->h_length = sizeof(*saddr);
+       return probe_tcp_first; 
+}
+#else
+static const unsigned int *nfs_default_proto() 
+{
+       return probe_tcp_first; 
+}
+#endif /* MOUNT_CONFIG */
+
+/**
+ * nfs_lookup - resolve hostname to an IPv4 or IPv6 socket address
+ * @hostname: pointer to C string containing DNS hostname to resolve
+ * @family: address family hint
+ * @sap: pointer to buffer to fill with socket address
+ * @len: IN: size of buffer to fill; OUT: size of socket address
+ *
+ * Returns 1 and places a socket address at @sap if successful;
+ * otherwise zero.
+ */
+int nfs_lookup(const char *hostname, const sa_family_t family,
+               struct sockaddr *sap, socklen_t *salen)
+{
+       struct addrinfo *gai_results;
+       struct addrinfo gai_hint = {
+               .ai_family      = family,
+       };
+       socklen_t len = *salen;
+       int error, ret = 0;
+
+       *salen = 0;
+
+       error = getaddrinfo(hostname, NULL, &gai_hint, &gai_results);
+       switch (error) {
+       case 0:
+               break;
+       case EAI_SYSTEM:
+               nfs_error(_("%s: DNS resolution failed for %s: %s"),
+                       progname, hostname, strerror(errno));
+               return ret;
+       default:
+               nfs_error(_("%s: DNS resolution failed for %s: %s"),
+                       progname, hostname, gai_strerror(error));
+               return ret;
+       }
+
+       switch (gai_results->ai_addr->sa_family) {
+       case AF_INET:
+       case AF_INET6:
+               if (len >= gai_results->ai_addrlen) {
+                       *salen = gai_results->ai_addrlen;
+                       memcpy(sap, gai_results->ai_addr, *salen);
+                       ret = 1;
+               }
+               break;
+       default:
+               /* things are really broken if we get here, so warn */
+               nfs_error(_("%s: unrecognized DNS resolution results for %s"),
+                               progname, hostname);
+               break;
+       }
+
+       freeaddrinfo(gai_results);
+       return ret;
+}
+
+/**
+ * nfs_gethostbyname - resolve a hostname to an IPv4 address
+ * @hostname: pointer to a C string containing a DNS hostname
+ * @sin: returns an IPv4 address 
+ *
+ * Returns 1 if successful, otherwise zero.
+ */
+int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin)
+{
+       socklen_t len = sizeof(*sin);
+
+       return nfs_lookup(hostname, AF_INET, (struct sockaddr *)sin, &len);
+}
+
+/**
+ * nfs_string_to_sockaddr - convert string address to sockaddr
+ * @address:   pointer to presentation format address to convert
+ * @sap:       pointer to socket address buffer to fill in
+ * @salen:     IN: length of address buffer
+ *             OUT: length of converted socket address
+ *
+ * Convert a presentation format address string to a socket address.
+ * Similar to nfs_lookup(), but the DNS query is squelched, and it
+ * won't make any noise if the getaddrinfo() call fails.
+ *
+ * Returns 1 and fills in @sap and @salen if successful; otherwise zero.
+ *
+ * See RFC 4038 section 5.1 or RFC 3513 section 2.2 for more details
+ * on presenting IPv6 addresses as text strings.
+ */
+int nfs_string_to_sockaddr(const char *address, struct sockaddr *sap,
+                          socklen_t *salen)
+{
+       struct addrinfo *gai_results;
+       struct addrinfo gai_hint = {
+               .ai_flags       = AI_NUMERICHOST,
+       };
+       socklen_t len = *salen;
+       int ret = 0;
+
+       *salen = 0;
+
+       if (getaddrinfo(address, NULL, &gai_hint, &gai_results) == 0) {
+               switch (gai_results->ai_addr->sa_family) {
+               case AF_INET:
+               case AF_INET6:
+                       if (len >= gai_results->ai_addrlen) {
+                               *salen = gai_results->ai_addrlen;
+                               memcpy(sap, gai_results->ai_addr, *salen);
+                               ret = 1;
                        }
-                       memcpy(&saddr->sin_addr, hp->h_addr, hp->h_length);
+                       break;
                }
+               freeaddrinfo(gai_results);
        }
-       return 1;
+
+       return ret;
 }
 
-/*
- * 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.
+/**
+ * nfs_present_sockaddr - convert sockaddr to string
+ * @sap: pointer to socket address to convert
+ * @salen: length of socket address
+ * @buf: pointer to buffer to fill in
+ * @buflen: length of buffer
  *
- * A side effect of calling this function is that rpccreateerr is set.
+ * Convert the passed-in sockaddr-style address to presentation format.
+ * The presentation format address is placed in @buf and is
+ * '\0'-terminated.
+ *
+ * Returns 1 if successful; otherwise zero.
+ *
+ * See RFC 4038 section 5.1 or RFC 3513 section 2.2 for more details
+ * on presenting IPv6 addresses as text strings.
  */
-static unsigned short getport(struct sockaddr_in *saddr,
-                               unsigned long program,
-                               unsigned long version,
-                               unsigned int proto)
+int nfs_present_sockaddr(const struct sockaddr *sap, const socklen_t salen,
+                        char *buf, const size_t buflen)
 {
-       unsigned short port = 0;
-       int socket;
-       CLIENT *clnt = NULL;
-       enum clnt_stat stat;
+#ifdef HAVE_GETNAMEINFO
+       int result;
+
+       result = getnameinfo(sap, salen, buf, buflen,
+                                       NULL, 0, NI_NUMERICHOST);
+       if (!result)
+               return 1;
 
-       saddr->sin_port = htons(PMAPPORT);
+       nfs_error(_("%s: invalid server address: %s"), progname,
+                       gai_strerror(result));
+       return 0;
+#else  /* HAVE_GETNAMEINFO */
+       char *addr;
 
-       /*
-        * 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;
+       if (sap->sa_family == AF_INET) {
+               addr = inet_ntoa(((struct sockaddr_in *)sap)->sin_addr);
+               if (addr && strlen(addr) < buflen) {
+                       strcpy(buf, addr);
+                       return 1;
                }
-               return 0;
        }
 
-       switch (proto) {
-       case IPPROTO_UDP:
-               clnt = clntudp_bufcreate(saddr,
-                                        PMAPPROG, PMAPVERS,
-                                        RETRY_TIMEOUT, &socket,
-                                        RPCSMALLMSGSIZE,
-                                        RPCSMALLMSGSIZE);
-               break;
-       case IPPROTO_TCP:
-               clnt = clnttcp_create(saddr, PMAPPROG, PMAPVERS, &socket,
-                                     RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
-               break;
+       nfs_error(_("%s: invalid server address"), progname);
+       return 0;
+#endif /* HAVE_GETNAMEINFO */
+}
+
+/*
+ * Attempt to connect a socket, but time out after "timeout" seconds.
+ *
+ * On error return, caller closes the socket.
+ */
+static int connect_to(int fd, struct sockaddr *addr,
+                       socklen_t addrlen, int timeout)
+{
+       int ret, saved;
+       fd_set rset, wset;
+       struct timeval tv = {
+               .tv_sec = timeout,
+       };
+
+       saved = fcntl(fd, F_GETFL, 0);
+       fcntl(fd, F_SETFL, saved | O_NONBLOCK);
+
+       ret = connect(fd, addr, addrlen);
+       if (ret < 0 && errno != EINPROGRESS)
+               return -1;
+       if (ret == 0)
+               goto out;
+
+       FD_ZERO(&rset);
+       FD_SET(fd, &rset);
+       wset = rset;
+       ret = select(fd + 1, &rset, &wset, NULL, &tv);
+       if (ret == 0) {
+               errno = ETIMEDOUT;
+               return -1;
        }
-       if (clnt != NULL) {
-               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;
+       if (FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset)) {
+               int error;
+               socklen_t len = sizeof(error);
+               if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
+                       return -1;
+               if (error) {
+                       errno = error;
+                       return -1;
                }
-               clnt_destroy(clnt);
-               if (stat != RPC_SUCCESS)
-                       port = 0;
-               else if (port == 0)
-                       rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
+       } else
+               return -1;
+
+out:
+       fcntl(fd, F_SETFL, saved);
+       return 0;
+}
+
+/*
+ * Create a socket that is locally bound to a reserved or non-reserved port.
+ *
+ * The caller should check rpc_createerr to determine the cause of any error.
+ */
+static int get_socket(struct sockaddr_in *saddr, unsigned int p_prot,
+                       unsigned int timeout, 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, SAFE_SOCKADDR(&laddr), namelen);
+               if (cc < 0)
+                       goto err_bind;
+       }
+       if (type == SOCK_STREAM || (conn && type == SOCK_DGRAM)) {
+               cc = connect_to(so, SAFE_SOCKADDR(saddr), namelen,
+                               timeout);
+               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;
+}
+
+static void nfs_pp_debug(const struct sockaddr *sap, const socklen_t salen,
+                        const rpcprog_t program, const rpcvers_t version,
+                        const unsigned short protocol,
+                        const unsigned short port)
+{
+       char buf[NI_MAXHOST];
+
+       if (!verbose)
+               return;
+
+       if (nfs_present_sockaddr(sap, salen, buf, sizeof(buf)) == 0) {
+               buf[0] = '\0';
+               strcat(buf, "unknown host");
        }
-       if (socket != 1)
-               close(socket);
 
-       return port;
+       fprintf(stderr, _("%s: trying %s prog %lu vers %lu prot %s port %d\n"),
+                       progname, buf, (unsigned long)program,
+                       (unsigned long)version,
+                       (protocol == IPPROTO_UDP ? _("UDP") : _("TCP")),
+                       port);
+}
+
+static void nfs_pp_debug2(const char *str)
+{
+       if (!verbose)
+               return;
+
+       if (rpc_createerr.cf_error.re_status == RPC_CANTRECV ||
+           rpc_createerr.cf_error.re_status == RPC_CANTSEND)
+               nfs_error(_("%s: portmap query %s%s - %s"),
+                               progname, str, clnt_spcreateerror(""),
+                               strerror(rpc_createerr.cf_error.re_errno));
+       else
+               nfs_error(_("%s: portmap query %s%s"),
+                               progname, str, clnt_spcreateerror(""));
 }
 
 /*
  * Use the portmapper to discover whether or not the service we want is
  * available. The lists 'versions' and 'protos' define ordered sequences
  * of service versions and udp/tcp protocols to probe for.
+ *
+ * Returns 1 if the requested service port is unambiguous and pingable;
+ * @pmap is filled in with the version, port, and transport protocol used
+ * during the successful ping.  Note that if a port is already specified
+ * in @pmap and it matches the rpcbind query result, nfs_probe_port() does
+ * not perform an RPC ping.
+ * 
+ * If an error occurs or the requested service isn't available, zero is
+ * returned; rpccreateerr.cf_stat is set to reflect the nature of the error.
  */
-static int probe_port(clnt_addr_t *server, const unsigned long *versions,
-                       const unsigned int *protos)
+static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen,
+                         struct pmap *pmap, const unsigned long *versions,
+                         const unsigned int *protos)
 {
-       struct sockaddr_in *saddr = &server->saddr;
-       struct pmap *pmap = &server->pmap;
+       union nfs_sockaddr address;
+       struct sockaddr *saddr = &address.sa;
        const unsigned long prog = pmap->pm_prog, *p_vers;
        const unsigned int prot = (u_int)pmap->pm_prot, *p_prot;
        const u_short port = (u_short) pmap->pm_port;
        unsigned long vers = pmap->pm_vers;
        unsigned short p_port;
 
+       memcpy(saddr, sap, salen);
        p_prot = prot ? &prot : protos;
        p_vers = vers ? &vers : versions;
-       rpc_createerr.cf_stat = 0;
+
        for (;;) {
-               saddr->sin_port = htons(PMAPPORT);
-               p_port = getport(saddr, prog, *p_vers, *p_prot);
+               if (verbose)
+                       printf(_("%s: prog %lu, trying vers=%lu, prot=%u\n"),
+                               progname, prog, *p_vers, *p_prot);
+               p_port = nfs_getport(saddr, salen, prog, *p_vers, *p_prot);
                if (p_port) {
                        if (!port || port == p_port) {
-                               saddr->sin_port = htons(p_port);
-                               if (verbose) {
-                                       printf(_("%s: trying %s prog %ld vers "
-                                               "%ld prot %s port %d\n"),
-                                               progname,
-                                               inet_ntoa(saddr->sin_addr),
-                                               prog, *p_vers,
-                                               *p_prot == IPPROTO_UDP ?
-                                                       "udp" : "tcp",
-                                               p_port);
-                                }
-                               if (clnt_ping(saddr, prog, *p_vers, *p_prot, NULL))
+                               nfs_set_port(saddr, p_port);
+                               nfs_pp_debug(saddr, salen, prog, *p_vers,
+                                               *p_prot, p_port);
+                               if (nfs_rpc_ping(saddr, salen, prog,
+                                                       *p_vers, *p_prot, NULL))
                                        goto out_ok;
-                               if (rpc_createerr.cf_stat == RPC_TIMEDOUT)
-                                       goto out_bad;
-                       }
+                       } else
+                               rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
                }
-               if (rpc_createerr.cf_stat != RPC_PROGNOTREGISTERED)
-                       goto out_bad;
+               if (rpc_createerr.cf_stat != RPC_PROGNOTREGISTERED &&
+                   rpc_createerr.cf_stat != RPC_TIMEDOUT &&
+                   rpc_createerr.cf_stat != RPC_CANTRECV &&
+                   rpc_createerr.cf_stat != RPC_PROGVERSMISMATCH)
+                       break;
 
                if (!prot) {
-                       if (*++p_prot)
+                       if (*++p_prot) {
+                               nfs_pp_debug2("retrying");
                                continue;
+                       }
                        p_prot = protos;
                }
-               if (vers == pmap->pm_vers) {
-                       p_vers = versions;
-                       vers = 0;
-               }
+               if (rpc_createerr.cf_stat == RPC_TIMEDOUT ||
+                   rpc_createerr.cf_stat == RPC_CANTRECV)
+                       break;
+
                if (vers || !*++p_vers)
                        break;
        }
 
-out_bad:
+       nfs_pp_debug2("failed");
        return 0;
 
 out_ok:
@@ -264,50 +598,121 @@ out_ok:
                pmap->pm_prot = *p_prot;
        if (!port)
                pmap->pm_port = p_port;
-       rpc_createerr.cf_stat = 0;
+       nfs_clear_rpc_createerr();
        return 1;
 }
-
-static int probe_nfsport(clnt_addr_t *nfs_server)
+/*
+ * Probe a server's NFS service to determine which versions and
+ * transport protocols are supported.
+ *
+ * Returns 1 if the requested service port is unambiguous and pingable;
+ * @pmap is filled in with the version, port, and transport protocol used
+ * during the successful ping.  If all three are already specified, simply
+ * return success without an rpcbind query or RPC ping (we may be trying
+ * to mount an NFS service that is not advertised via rpcbind).
+ *
+ * If an error occurs or the requested service isn't available, zero is
+ * returned; rpccreateerr.cf_stat is set to reflect the nature of the error.
+ */
+static int nfs_probe_nfsport(const struct sockaddr *sap, const socklen_t salen,
+                               struct pmap *pmap)
 {
-       struct pmap *pmap = &nfs_server->pmap;
-
        if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port)
                return 1;
 
-       if (nfs_mount_data_version >= 4)
-               return probe_port(nfs_server, probe_nfs3_first, probe_tcp_first);
-       else
-               return probe_port(nfs_server, probe_nfs2_only, probe_udp_only);
+       if (nfs_mount_data_version >= 4) {
+               const unsigned int *probe_proto;
+
+               probe_proto = nfs_default_proto();
+
+               return nfs_probe_port(sap, salen, pmap,
+                                       probe_nfs3_first, probe_proto);
+       } else
+               return nfs_probe_port(sap, salen, pmap,
+                                       probe_nfs2_only, probe_udp_only);
 }
 
-static int probe_mntport(clnt_addr_t *mnt_server)
+/*
+ * Probe a server's mountd service to determine which versions and
+ * transport protocols are supported.
+ *
+ * Returns 1 if the requested service port is unambiguous and pingable;
+ * @pmap is filled in with the version, port, and transport protocol used
+ * during the successful ping.  If all three are already specified, simply
+ * return success without an rpcbind query or RPC ping (we may be trying
+ * to mount an NFS service that is not advertised via rpcbind).
+ * 
+ * If an error occurs or the requested service isn't available, zero is
+ * returned; rpccreateerr.cf_stat is set to reflect the nature of the error.
+ */
+static int nfs_probe_mntport(const struct sockaddr *sap, const socklen_t salen,
+                               struct pmap *pmap)
 {
-       struct pmap *pmap = &mnt_server->pmap;
-
        if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port)
                return 1;
 
        if (nfs_mount_data_version >= 4)
-               return probe_port(mnt_server, probe_mnt3_first, probe_udp_first);
+               return nfs_probe_port(sap, salen, pmap,
+                                       probe_mnt3_first, probe_udp_first);
        else
-               return probe_port(mnt_server, probe_mnt1_first, probe_udp_only);
+               return nfs_probe_port(sap, salen, pmap,
+                                       probe_mnt1_first, probe_udp_only);
 }
 
-int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server)
+/*
+ * Probe a server's mountd service to determine which versions and
+ * transport protocols are supported.  Invoked when the protocol
+ * version is already known for both the NFS and mountd service.
+ *
+ * Returns 1 and fills in both @pmap structs if the requested service
+ * ports are unambiguous and pingable.  Otherwise zero is returned;
+ * rpccreateerr.cf_stat is set to reflect the nature of the error.
+ */
+static int nfs_probe_version_fixed(const struct sockaddr *mnt_saddr,
+                       const socklen_t mnt_salen,
+                       struct pmap *mnt_pmap,
+                       const struct sockaddr *nfs_saddr,
+                       const socklen_t nfs_salen,
+                       struct pmap *nfs_pmap)
+{
+       if (!nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap))
+               return 0;
+       return nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap);
+}
+
+/**
+ * nfs_probe_bothports - discover the RPC endpoints of mountd and NFS server
+ * @mnt_saddr: pointer to socket address of mountd server
+ * @mnt_salen: length of mountd server's address
+ * @mnt_pmap:  IN: partially filled-in mountd RPC service tuple;
+ *             OUT: fully filled-in mountd RPC service tuple
+ * @nfs_saddr: pointer to socket address of NFS server
+ * @nfs_salen: length of NFS server's address
+ * @nfs_pmap:  IN: partially filled-in NFS RPC service tuple;
+ *             OUT: fully filled-in NFS RPC service tuple
+ *
+ * Returns 1 and fills in both @pmap structs if the requested service
+ * ports are unambiguous and pingable.  Otherwise zero is returned;
+ * rpccreateerr.cf_stat is set to reflect the nature of the error.
+ */
+int nfs_probe_bothports(const struct sockaddr *mnt_saddr,
+                       const socklen_t mnt_salen,
+                       struct pmap *mnt_pmap,
+                       const struct sockaddr *nfs_saddr,
+                       const socklen_t nfs_salen,
+                       struct pmap *nfs_pmap)
 {
-       struct pmap *nfs_pmap = &nfs_server->pmap;
-       struct pmap *mnt_pmap = &mnt_server->pmap;
        struct pmap save_nfs, save_mnt;
-       int res;
        const unsigned long *probe_vers;
 
        if (mnt_pmap->pm_vers && !nfs_pmap->pm_vers)
                nfs_pmap->pm_vers = mntvers_to_nfs(mnt_pmap->pm_vers);
        else if (nfs_pmap->pm_vers && !mnt_pmap->pm_vers)
                mnt_pmap->pm_vers = nfsvers_to_mnt(nfs_pmap->pm_vers);
+
        if (nfs_pmap->pm_vers)
-               goto version_fixed;
+               return nfs_probe_version_fixed(mnt_saddr, mnt_salen, mnt_pmap,
+                                              nfs_saddr, nfs_salen, nfs_pmap);
 
        memcpy(&save_nfs, nfs_pmap, sizeof(save_nfs));
        memcpy(&save_mnt, mnt_pmap, sizeof(save_mnt));
@@ -316,9 +721,9 @@ int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server)
 
        for (; *probe_vers; probe_vers++) {
                nfs_pmap->pm_vers = mntvers_to_nfs(*probe_vers);
-               if ((res = probe_nfsport(nfs_server) != 0)) {
-                       mnt_pmap->pm_vers = nfsvers_to_mnt(nfs_pmap->pm_vers);
-                       if ((res = probe_mntport(mnt_server)) != 0)
+               if (nfs_probe_nfsport(nfs_saddr, nfs_salen, nfs_pmap) != 0) {
+                       mnt_pmap->pm_vers = *probe_vers;
+                       if (nfs_probe_mntport(mnt_saddr, mnt_salen, mnt_pmap) != 0)
                                return 1;
                        memcpy(mnt_pmap, &save_mnt, sizeof(*mnt_pmap));
                }
@@ -327,42 +732,54 @@ int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server)
                case RPC_PROGNOTREGISTERED:
                        break;
                default:
-                       goto out_bad;
+                       return 0;
                }
                memcpy(nfs_pmap, &save_nfs, sizeof(*nfs_pmap));
        }
 
-out_bad:
        return 0;
-
-version_fixed:
-       if (!probe_nfsport(nfs_server))
-               goto out_bad;
-       return probe_mntport(mnt_server);
 }
 
-static int probe_statd(void)
+/**
+ * probe_bothports - discover the RPC endpoints of mountd and NFS server
+ * @mnt_server: pointer to address and pmap argument for mountd results
+ * @nfs_server: pointer to address and pmap argument for NFS server
+ *
+ * This is the legacy API that takes "clnt_addr_t" for both servers,
+ * but supports only AF_INET addresses.
+ *
+ * Returns 1 and fills in the pmap field in both clnt_addr_t structs
+ * if the requested service ports are unambiguous and pingable.
+ * Otherwise zero is returned; rpccreateerr.cf_stat is set to reflect
+ * the nature of the error.
+ */
+int probe_bothports(clnt_addr_t *mnt_server, clnt_addr_t *nfs_server)
 {
-       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);
+       struct sockaddr *mnt_addr = SAFE_SOCKADDR(&mnt_server->saddr);
+       struct sockaddr *nfs_addr = SAFE_SOCKADDR(&nfs_server->saddr);
 
-       if (port == 0)
-               return 0;
-       addr.sin_port = htons(port);
+       return nfs_probe_bothports(mnt_addr, sizeof(mnt_server->saddr),
+                                       &mnt_server->pmap,
+                                       nfs_addr, sizeof(nfs_server->saddr),
+                                       &nfs_server->pmap);
+}
 
-       if (clnt_ping(&addr, 100024, 1, IPPROTO_UDP, NULL) <= 0)
-               return 0;
+static int nfs_probe_statd(void)
+{
+       struct sockaddr_in addr = {
+               .sin_family             = AF_INET,
+               .sin_addr.s_addr        = htonl(INADDR_LOOPBACK),
+       };
+       rpcprog_t program = nfs_getrpcbyname(NSMPROG, nfs_ns_pgmtbl);
 
-       return 1;
+       return nfs_getport_ping(SAFE_SOCKADDR(&addr), sizeof(addr),
+                               program, (rpcvers_t)1, IPPROTO_UDP);
 }
 
-/*
- * Attempt to start rpc.statd
+/**
+ * start_statd - attempt to start rpc.statd
+ *
+ * Returns 1 if statd is running; otherwise zero.
  */
 int start_statd(void)
 {
@@ -370,14 +787,26 @@ int start_statd(void)
        struct stat stb;
 #endif
 
-       if (probe_statd())
+       if (nfs_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())
+                       pid_t pid = fork();
+                       switch (pid) {
+                       case 0: /* child */
+                               execl(START_STATD, START_STATD, NULL);
+                               exit(1);
+                       case -1: /* error */
+                               nfs_error(_("%s: fork failed: %s"),
+                                               progname, strerror(errno));
+                               break;
+                       default: /* parent */
+                               waitpid(pid, NULL,0);
+                               break;
+                       }
+                       if (nfs_probe_statd())
                                return 1;
                }
        }
@@ -386,7 +815,80 @@ int start_statd(void)
        return 0;
 }
 
-/*
+/**
+ * nfs_advise_umount - ask the server to remove a share from it's rmtab
+ * @sap: pointer to IP address of server to call
+ * @salen: length of server address
+ * @pmap: partially filled-in mountd RPC service tuple
+ * @argp: directory path of share to "unmount"
+ *
+ * Returns one if the unmount call succeeded; zero if the unmount
+ * failed for any reason;  rpccreateerr.cf_stat is set to reflect
+ * the nature of the error.
+ *
+ * We use a fast timeout since this call is advisory only.
+ */
+int nfs_advise_umount(const struct sockaddr *sap, const socklen_t salen,
+                     const struct pmap *pmap, const dirpath *argp)
+{
+       union nfs_sockaddr address;
+       struct sockaddr *saddr = &address.sa;
+       struct pmap mnt_pmap = *pmap;
+       struct timeval timeout = {
+               .tv_sec         = MOUNT_TIMEOUT >> 3,
+       };
+       CLIENT *client;
+       enum clnt_stat res = 0;
+
+       memcpy(saddr, sap, salen);
+       if (nfs_probe_mntport(saddr, salen, &mnt_pmap) == 0) {
+               if (verbose)
+                       nfs_error(_("%s: Failed to discover mountd port%s"),
+                               progname, clnt_spcreateerror(""));
+               return 0;
+       }
+       nfs_set_port(saddr, mnt_pmap.pm_port);
+
+       client = nfs_get_priv_rpcclient(saddr, salen, mnt_pmap.pm_prot,
+                                       mnt_pmap.pm_prog, mnt_pmap.pm_vers,
+                                       &timeout);
+       if (client == NULL) {
+               if (verbose)
+                       nfs_error(_("%s: Failed to create RPC client%s"),
+                               progname, clnt_spcreateerror(""));
+               return 0;
+       }
+
+       client->cl_auth = nfs_authsys_create();
+       if (client->cl_auth == NULL) {
+               if (verbose)
+                       nfs_error(_("%s: Failed to create RPC auth handle"),
+                               progname);
+               CLNT_DESTROY(client);
+               return 0;
+       }
+
+       res = CLNT_CALL(client, MOUNTPROC_UMNT,
+                       (xdrproc_t)xdr_dirpath, (caddr_t)argp,
+                       (xdrproc_t)xdr_void, NULL,
+                       timeout);
+       if (res != RPC_SUCCESS) {
+               rpc_createerr.cf_stat = res;
+               CLNT_GETERR(client, &rpc_createerr.cf_error);
+               if (verbose)
+                       nfs_error(_("%s: UMNT call failed: %s"),
+                               progname, clnt_sperrno(res));
+
+       }
+       auth_destroy(client->cl_auth);
+       CLNT_DESTROY(client);
+
+       if (res != RPC_SUCCESS)
+               return 0;
+       return 1;
+}
+
+/**
  * 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"
@@ -399,33 +901,794 @@ int start_statd(void)
  */
 int nfs_call_umount(clnt_addr_t *mnt_server, dirpath *argp)
 {
+       struct sockaddr *sap = SAFE_SOCKADDR(&mnt_server->saddr);
+       socklen_t salen = sizeof(mnt_server->saddr);
+       struct pmap *pmap = &mnt_server->pmap;
        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 (!nfs_probe_mntport(sap, salen, pmap))
+               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;
        return 0;
 }
+
+/**
+ * mnt_openclnt - get a handle for a remote mountd service
+ * @mnt_server: address and pmap arguments of mountd service
+ * @msock: returns a file descriptor of the underlying transport socket
+ *
+ * Returns an active handle for the remote's mountd service
+ */
+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, MOUNT_TIMEOUT,
+                               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 = nfs_authsys_create();
+               if (clnt->cl_auth)
+                       return clnt;
+               CLNT_DESTROY(clnt);
+       }
+       return NULL;
+}
+
+/**
+ * mnt_closeclnt - terminate a handle for a remote mountd service
+ * @clnt: pointer to an active handle for a remote mountd service
+ * @msock: file descriptor of the underlying transport socket
+ *
+ */
+void mnt_closeclnt(CLIENT *clnt, int msock)
+{
+       auth_destroy(clnt->cl_auth);
+       clnt_destroy(clnt);
+       close(msock);
+}
+
+/**
+ * clnt_ping - send an RPC ping to the remote RPC service endpoint
+ * @saddr: server's address
+ * @prog: target RPC program number
+ * @vers: target RPC version number
+ * @prot: target RPC protocol
+ * @caddr: filled in with our network address
+ *
+ * Sigh... GETPORT queries don't actually check the version number.
+ * In order to make sure that the server actually supports the service
+ * we're requesting, we open an RPC client, and fire off a NULL
+ * RPC call.
+ *
+ * caddr is the network address that the server will use to call us back.
+ * On multi-homed clients, this address depends on which NIC we use to
+ * route requests to the server.
+ *
+ * Returns one if successful, otherwise zero.
+ */
+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, status;
+       static char clnt_res;
+       struct sockaddr dissolve;
+
+       rpc_createerr.cf_stat = status = 0;
+       sock = get_socket(saddr, prot, CONNECT_TIMEOUT, FALSE, TRUE);
+       if (sock == RPC_ANYSOCK) {
+               if (rpc_createerr.cf_error.re_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));
+       status = clnt_call(clnt, NULLPROC,
+                        (xdrproc_t)xdr_void, (caddr_t)NULL,
+                        (xdrproc_t)xdr_void, (caddr_t)&clnt_res,
+                        TIMEOUT);
+       if (status) {
+               clnt_geterr(clnt, &rpc_createerr.cf_error);
+               rpc_createerr.cf_stat = status;
+       }
+       clnt_destroy(clnt);
+       close(sock);
+
+       if (status == RPC_SUCCESS)
+               return 1;
+       else
+               return 0;
+}
+
+/*
+ * Try a getsockname() on a connected datagram socket.
+ *
+ * Returns 1 and fills in @buf if successful; otherwise, zero.
+ *
+ * A connected datagram socket prevents leaving a socket in TIME_WAIT.
+ * This conserves the ephemeral port number space, helping reduce failed
+ * socket binds during mount storms.
+ */
+static int nfs_ca_sockname(const struct sockaddr *sap, const socklen_t salen,
+                          struct sockaddr *buf, socklen_t *buflen)
+{
+       struct sockaddr_in sin = {
+               .sin_family             = AF_INET,
+               .sin_addr.s_addr        = htonl(INADDR_ANY),
+       };
+       struct sockaddr_in6 sin6 = {
+               .sin6_family            = AF_INET6,
+               .sin6_addr              = IN6ADDR_ANY_INIT,
+       };
+       int sock, result = 0;
+
+       sock = socket(sap->sa_family, SOCK_DGRAM, IPPROTO_UDP);
+       if (sock < 0)
+               return 0;
+
+       switch (sap->sa_family) {
+       case AF_INET:
+               if (bind(sock, SAFE_SOCKADDR(&sin), sizeof(sin)) < 0)
+                       goto out;
+               break;
+       case AF_INET6:
+               if (bind(sock, SAFE_SOCKADDR(&sin6), sizeof(sin6)) < 0)
+                       goto out;
+               break;
+       default:
+               errno = EAFNOSUPPORT;
+               goto out;
+       }
+
+       if (connect(sock, sap, salen) < 0)
+               goto out;
+
+       result = !getsockname(sock, buf, buflen);
+
+out:
+       close(sock);
+       return result;
+}
+
+/*
+ * Try to generate an address that prevents the server from calling us.
+ *
+ * Returns 1 and fills in @buf if successful; otherwise, zero.
+ */
+static int nfs_ca_gai(const struct sockaddr *sap,
+                     struct sockaddr *buf, socklen_t *buflen)
+{
+       struct addrinfo *gai_results;
+       struct addrinfo gai_hint = {
+               .ai_family      = sap->sa_family,
+               .ai_flags       = AI_PASSIVE,   /* ANYADDR */
+       };
+
+       if (getaddrinfo(NULL, "", &gai_hint, &gai_results))
+               return 0;
+
+       *buflen = gai_results->ai_addrlen;
+       memcpy(buf, gai_results->ai_addr, *buflen);
+
+       freeaddrinfo(gai_results);
+
+       return 1;
+}
+
+/**
+ * nfs_callback_address - acquire our local network address
+ * @sap: pointer to address of remote
+ * @sap_len: length of address
+ * @buf: pointer to buffer to be filled in with local network address
+ * @buflen: IN: length of buffer to fill in; OUT: length of filled-in address
+ *
+ * Discover a network address that an NFSv4 server can use to call us back.
+ * On multi-homed clients, this address depends on which NIC we use to
+ * route requests to the server.
+ *
+ * Returns 1 and fills in @buf if an unambiguous local address is
+ * available; returns 1 and fills in an appropriate ANYADDR address
+ * if a local address isn't available; otherwise, returns zero.
+ */
+int nfs_callback_address(const struct sockaddr *sap, const socklen_t salen,
+                        struct sockaddr *buf, socklen_t *buflen)
+{
+       struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)buf;
+
+       if (nfs_ca_sockname(sap, salen, buf, buflen) == 0)
+               if (nfs_ca_gai(sap, buf, buflen) == 0)
+                       goto out_failed;
+
+       /*
+        * The server can't use an interface ID that was generated
+        * here on the client, so always clear sin6_scope_id.
+        */
+       if (sin6->sin6_family == AF_INET6)
+               sin6->sin6_scope_id = 0;
+
+       return 1;
+
+out_failed:
+       *buflen = 0;
+       if (verbose)
+               nfs_error(_("%s: failed to construct callback address"),
+                               progname);
+       return 0;
+}
+
+/*
+ * "nfsprog" is supported only by the legacy mount command.  The
+ * kernel mount client does not support this option.
+ *
+ * Returns TRUE if @program contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+static int
+nfs_nfs_program(struct mount_options *options, unsigned long *program)
+{
+       long tmp;
+
+       switch (po_get_numeric(options, "nfsprog", &tmp)) {
+       case PO_NOT_FOUND:
+               break;
+       case PO_FOUND:
+               if (tmp > 0) {
+                       *program = tmp;
+                       return 1;
+               }
+       case PO_BAD_VALUE:
+               nfs_error(_("%s: invalid value for 'nfsprog=' option"),
+                               progname);
+               return 0;
+       }
+
+       /*
+        * NFS RPC program wasn't specified.  The RPC program
+        * cannot be determined via an rpcbind query.
+        */
+       *program = nfs_getrpcbyname(NFSPROG, nfs_nfs_pgmtbl);
+       return 1;
+}
+
+/*
+ * Returns TRUE if @version contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+int
+nfs_nfs_version(struct mount_options *options, unsigned long *version)
+{
+       long tmp;
+
+       switch (po_rightmost(options, nfs_version_opttbl)) {
+       case 0: /* v2 */
+               *version = 2;
+               return 1;
+       case 1: /* v3 */
+               *version = 3;
+               return 1;
+       case 2: /* v4 */
+               *version = 4;
+               return 1;
+       case 3: /* vers */
+               switch (po_get_numeric(options, "vers", &tmp)) {
+               case PO_FOUND:
+                       if (tmp >= 2 && tmp <= 4) {
+                               *version = tmp;
+                               return 1;
+                       }
+                       return 0;
+               case PO_NOT_FOUND:
+                       nfs_error(_("%s: parsing error on 'vers=' option\n"),
+                                       progname);
+                       return 0;
+               case PO_BAD_VALUE:
+                       nfs_error(_("%s: invalid value for 'vers=' option"),
+                                       progname);
+                       return 0;
+               }
+       case 4: /* nfsvers */
+               switch (po_get_numeric(options, "nfsvers", &tmp)) {
+               case PO_FOUND:
+                       if (tmp >= 2 && tmp <= 4) {
+                               *version = tmp;
+                               return 1;
+                       }
+                       return 0;
+               case PO_NOT_FOUND:
+                       nfs_error(_("%s: parsing error on 'nfsvers=' option\n"),
+                                       progname);
+                       return 0;
+               case PO_BAD_VALUE:
+                       nfs_error(_("%s: invalid value for 'nfsvers=' option"),
+                                       progname);
+                       return 0;
+               }
+       }
+
+       /*
+        * NFS version wasn't specified.  The pmap version value
+        * will be filled in later by an rpcbind query in this case.
+        */
+       *version = 0;
+       return 1;
+}
+
+/*
+ * Returns TRUE if @protocol contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value. On
+ * error, errno is set.
+ */
+int
+nfs_nfs_protocol(struct mount_options *options, unsigned long *protocol)
+{
+       sa_family_t family;
+       char *option;
+
+       switch (po_rightmost(options, nfs_transport_opttbl)) {
+       case 0: /* udp */
+               *protocol = IPPROTO_UDP;
+               return 1;
+       case 1: /* tcp */
+               *protocol = IPPROTO_TCP;
+               return 1;
+       case 2: /* rdma */
+               *protocol = NFSPROTO_RDMA;
+               return 1;
+       case 3: /* proto */
+               option = po_get(options, "proto");
+               if (option != NULL) {
+                       if (!nfs_get_proto(option, &family, protocol)) {
+                               errno = EPROTONOSUPPORT;
+                               nfs_error(_("%s: Failed to find '%s' protocol"), 
+                                       progname, option);
+                               return 0;
+                       }
+                       return 1;
+               }
+       }
+
+       /*
+        * NFS transport protocol wasn't specified.  The pmap
+        * protocol value will be filled in later by an rpcbind
+        * query in this case.
+        */
+       *protocol = 0;
+       return 1;
+}
+
+/*
+ * Returns TRUE if @port contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+static int
+nfs_nfs_port(struct mount_options *options, unsigned long *port)
+{
+       long tmp;
+
+       switch (po_get_numeric(options, "port", &tmp)) {
+       case PO_NOT_FOUND:
+               break;
+       case PO_FOUND:
+               if (tmp >= 0 && tmp <= 65535) {
+                       *port = tmp;
+                       return 1;
+               }
+       case PO_BAD_VALUE:
+               nfs_error(_("%s: invalid value for 'port=' option"),
+                               progname);
+               return 0;
+       }
+
+       /*
+        * NFS service port wasn't specified.  The pmap port value
+        * will be filled in later by an rpcbind query in this case.
+        */
+       *port = 0;
+       return 1;
+}
+
+#ifdef IPV6_SUPPORTED
+sa_family_t    config_default_family = AF_UNSPEC;
+
+static int
+nfs_verify_family(sa_family_t UNUSED(family))
+{
+       return 1;
+}
+#else /* IPV6_SUPPORTED */
+sa_family_t    config_default_family = AF_INET;
+
+static int
+nfs_verify_family(sa_family_t family)
+{
+       if (family != AF_INET)
+               return 0;
+
+       return 1;
+}
+#endif /* IPV6_SUPPORTED */
+
+/*
+ * Returns TRUE and fills in @family if a valid NFS protocol option
+ * is found, or FALSE if the option was specified with an invalid value
+ * or if the protocol family isn't supported. On error, errno is set.
+ */
+int nfs_nfs_proto_family(struct mount_options *options,
+                               sa_family_t *family)
+{
+       unsigned long protocol;
+       char *option;
+       sa_family_t tmp_family = config_default_family;
+
+       switch (po_rightmost(options, nfs_transport_opttbl)) {
+       case 0: /* udp */
+       case 1: /* tcp */
+       case 2: /* rdma */
+               /* for compatibility; these are always AF_INET */
+               *family = AF_INET;
+               return 1;
+       case 3: /* proto */
+               option = po_get(options, "proto");
+               if (option != NULL &&
+                   !nfs_get_proto(option, &tmp_family, &protocol)) {
+
+                       nfs_error(_("%s: Failed to find '%s' protocol"), 
+                               progname, option);
+                       errno = EPROTONOSUPPORT;
+                       return 0;
+               }
+       }
+
+       if (!nfs_verify_family(tmp_family))
+               goto out_err;
+       *family = tmp_family;
+       return 1;
+out_err:
+       errno = EAFNOSUPPORT;
+       return 0;
+}
+
+/*
+ * "mountprog" is supported only by the legacy mount command.  The
+ * kernel mount client does not support this option.
+ *
+ * Returns TRUE if @program contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+static int
+nfs_mount_program(struct mount_options *options, unsigned long *program)
+{
+       long tmp;
+
+       switch (po_get_numeric(options, "mountprog", &tmp)) {
+       case PO_NOT_FOUND:
+               break;
+       case PO_FOUND:
+               if (tmp > 0) {
+                       *program = tmp;
+                       return 1;
+               }
+       case PO_BAD_VALUE:
+               nfs_error(_("%s: invalid value for 'mountprog=' option"),
+                               progname);
+               return 0;
+       }
+
+       /*
+        * MNT RPC program wasn't specified.  The RPC program
+        * cannot be determined via an rpcbind query.
+        */
+       *program = nfs_getrpcbyname(MOUNTPROG, nfs_mnt_pgmtbl);
+       return 1;
+}
+
+/*
+ * Returns TRUE if @version contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+static int
+nfs_mount_version(struct mount_options *options, unsigned long *version)
+{
+       long tmp;
+
+       switch (po_get_numeric(options, "mountvers", &tmp)) {
+       case PO_NOT_FOUND:
+               break;
+       case PO_FOUND:
+               if (tmp >= 1 && tmp <= 4) {
+                       *version = tmp;
+                       return 1;
+               }
+       case PO_BAD_VALUE:
+               nfs_error(_("%s: invalid value for 'mountvers=' option"),
+                               progname);
+               return 0;
+       }
+
+       /*
+        * MNT version wasn't specified.  The pmap version value
+        * will be filled in later by an rpcbind query in this case.
+        */
+       *version = 0;
+       return 1;
+}
+
+/*
+ * Returns TRUE if @protocol contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value. On
+ * error, errno is set.
+ */
+static int
+nfs_mount_protocol(struct mount_options *options, unsigned long *protocol)
+{
+       sa_family_t family;
+       char *option;
+
+       option = po_get(options, "mountproto");
+       if (option != NULL) {
+               if (!nfs_get_proto(option, &family, protocol)) {
+                       errno = EPROTONOSUPPORT;
+                       nfs_error(_("%s: Failed to find '%s' protocol"), 
+                               progname, option);
+                       return 0;
+               }
+               return 1;
+       }
+
+       /*
+        * MNT transport protocol wasn't specified.  If the NFS
+        * transport protocol was specified, use that; otherwise
+        * set @protocol to zero.  The pmap protocol value will
+        * be filled in later by an rpcbind query in this case.
+        */
+       if (!nfs_nfs_protocol(options, protocol))
+               return 0;
+       if (*protocol == NFSPROTO_RDMA)
+               *protocol = IPPROTO_TCP;
+       return 1;
+}
+
+/*
+ * Returns TRUE if @port contains a valid value for this option,
+ * or FALSE if the option was specified with an invalid value.
+ */
+static int
+nfs_mount_port(struct mount_options *options, unsigned long *port)
+{
+       long tmp;
+
+       switch (po_get_numeric(options, "mountport", &tmp)) {
+       case PO_NOT_FOUND:
+               break;
+       case PO_FOUND:
+               if (tmp >= 0 && tmp <= 65535) {
+                       *port = tmp;
+                       return 1;
+               }
+       case PO_BAD_VALUE:
+               nfs_error(_("%s: invalid value for 'mountport=' option"),
+                               progname);
+               return 0;
+       }
+
+       /*
+        * MNT service port wasn't specified.  The pmap port value
+        * will be filled in later by an rpcbind query in this case.
+        */
+       *port = 0;
+       return 1;
+}
+
+/*
+ * Returns TRUE and fills in @family if a valid MNT protocol option
+ * is found, or FALSE if the option was specified with an invalid value
+ * or if the protocol family isn't supported. On error, errno is set.
+ */
+int nfs_mount_proto_family(struct mount_options *options,
+                               sa_family_t *family)
+{
+       unsigned long protocol;
+       char *option;
+       sa_family_t tmp_family = config_default_family;
+
+       option = po_get(options, "mountproto");
+       if (option != NULL) {
+               if (!nfs_get_proto(option, &tmp_family, &protocol)) {
+                       nfs_error(_("%s: Failed to find '%s' protocol"), 
+                               progname, option);
+                       errno = EPROTONOSUPPORT;
+                       goto out_err;
+               }
+               if (!nfs_verify_family(tmp_family))
+                       goto out_err;
+               *family = tmp_family;
+               return 1;
+       }
+
+       /*
+        * MNT transport protocol wasn't specified.  If the NFS
+        * transport protocol was specified, derive the family
+        * from that; otherwise, return the default family for
+        * NFS.
+        */
+       return nfs_nfs_proto_family(options, family);
+out_err:
+       errno = EAFNOSUPPORT;
+       return 0;
+}
+
+/**
+ * nfs_options2pmap - set up pmap structs based on mount options
+ * @options: pointer to mount options
+ * @nfs_pmap: OUT: pointer to pmap arguments for NFS server
+ * @mnt_pmap: OUT: pointer to pmap arguments for mountd server
+ *
+ * Returns TRUE if the pmap options specified in @options have valid
+ * values; otherwise FALSE is returned.
+ */
+int nfs_options2pmap(struct mount_options *options,
+                    struct pmap *nfs_pmap, struct pmap *mnt_pmap)
+{
+       if (!nfs_nfs_program(options, &nfs_pmap->pm_prog))
+               return 0;
+       if (!nfs_nfs_version(options, &nfs_pmap->pm_vers))
+               return 0;
+       if (!nfs_nfs_protocol(options, &nfs_pmap->pm_prot))
+               return 0;
+       if (!nfs_nfs_port(options, &nfs_pmap->pm_port))
+               return 0;
+
+       if (!nfs_mount_program(options, &mnt_pmap->pm_prog))
+               return 0;
+       if (!nfs_mount_version(options, &mnt_pmap->pm_vers))
+               return 0;
+       if (!nfs_mount_protocol(options, &mnt_pmap->pm_prot))
+               return 0;
+       if (!nfs_mount_port(options, &mnt_pmap->pm_port))
+               return 0;
+
+       return 1;
+}
+
+/*
+ * Discover mount server's hostname/address by examining mount options
+ *
+ * Returns a pointer to a string that the caller must free, on
+ * success; otherwise NULL is returned.
+ */
+static char *nfs_umount_hostname(struct mount_options *options,
+                                char *hostname)
+{
+       char *option;
+
+       option = po_get(options, "mountaddr");
+       if (option)
+               goto out;
+       option = po_get(options, "mounthost");
+       if (option)
+               goto out;
+       option = po_get(options, "addr");
+       if (option)
+               goto out;
+
+       return hostname;
+
+out:
+       free(hostname);
+       return strdup(option);
+}
+
+
+/*
+ * Returns EX_SUCCESS if mount options and device name have been
+ * parsed successfully; otherwise EX_FAIL.
+ */
+int nfs_umount_do_umnt(struct mount_options *options,
+                      char **hostname, char **dirname)
+{
+       union nfs_sockaddr address;
+       struct sockaddr *sap = &address.sa;
+       socklen_t salen = sizeof(address);
+       struct pmap nfs_pmap, mnt_pmap;
+       sa_family_t family;
+
+       if (!nfs_options2pmap(options, &nfs_pmap, &mnt_pmap))
+               return EX_FAIL;
+
+       /* Skip UMNT call for vers=4 mounts */
+       if (nfs_pmap.pm_vers == 4)
+               return EX_SUCCESS;
+
+       *hostname = nfs_umount_hostname(options, *hostname);
+       if (!*hostname) {
+               nfs_error(_("%s: out of memory"), progname);
+               return EX_FAIL;
+       }
+
+       if (!nfs_mount_proto_family(options, &family))
+               return 0;
+       if (!nfs_lookup(*hostname, family, sap, &salen))
+               /* nfs_lookup reports any errors */
+               return EX_FAIL;
+
+       if (nfs_advise_umount(sap, salen, &mnt_pmap, dirname) == 0)
+               /* nfs_advise_umount reports any errors */
+               return EX_FAIL;
+
+       return EX_SUCCESS;
+}