]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/mount/network.c
Imported Upstream version 1.2.3
[nfs-utils.git] / utils / mount / network.c
index d262e940091e195449d9d0aea05473a556c2b9fe..d61242798f6a7153472cb132fb54927a602cb4d1 100644 (file)
 #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 "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"
 
 #define PMAP_TIMEOUT   (10)
 #define CONNECT_TIMEOUT        (20)
 #define MOUNT_TIMEOUT  (30)
 
-#if SIZEOF_SOCKLEN_T - 0 == 0
-#define socklen_t unsigned int
-#endif
-
 extern int nfs_mount_data_version;
 extern char *progname;
 extern int verbose;
@@ -67,6 +68,35 @@ static const char *nfs_ns_pgmtbl[] = {
        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,
@@ -142,54 +172,63 @@ static const unsigned long probe_mnt3_first[] = {
        0,
 };
 
-static void nfs_set_port(struct sockaddr *sap, const unsigned short port)
+static const unsigned int *nfs_default_proto(void);
+#ifdef MOUNT_CONFIG
+static const unsigned int *nfs_default_proto()
 {
-       switch (sap->sa_family) {
-       case AF_INET:
-               ((struct sockaddr_in *)sap)->sin_port = htons(port);
-               break;
-       case AF_INET6:
-               ((struct sockaddr_in6 *)sap)->sin6_port = htons(port);
-               break;
-       default:
-               nfs_error(_("%s: unrecognized address family in %s"),
-                       progname, __func__);
-       }
+       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;
+
+       return probe_tcp_first; 
 }
+#else
+static const unsigned int *nfs_default_proto() 
+{
+       return probe_tcp_first; 
+}
+#endif /* MOUNT_CONFIG */
 
-#ifdef HAVE_DECL_AI_ADDRCONFIG
 /**
- * nfs_name_to_address - resolve hostname to an IPv4 or IPv6 socket address
+ * nfs_lookup - resolve hostname to an IPv4 or IPv6 socket address
  * @hostname: pointer to C string containing DNS hostname to resolve
- * @af_hint: hint to restrict resolution to one address family
+ * @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_name_to_address(const char *hostname,
-                       const sa_family_t af_hint,
-                       struct sockaddr *sap, socklen_t *salen)
+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      = af_hint,
+#ifdef HAVE_DECL_AI_ADDRCONFIG
                .ai_flags       = AI_ADDRCONFIG,
+#endif /* HAVE_DECL_AI_ADDRCONFIG */
+               .ai_family      = family,
        };
        socklen_t len = *salen;
        int error, ret = 0;
 
-       if (af_hint == AF_INET6)
-               gai_hint.ai_flags |= AI_V4MAPPED|AI_ALL;
-
        *salen = 0;
 
        error = getaddrinfo(hostname, NULL, &gai_hint, &gai_results);
-       if (error) {
+       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, (error == EAI_SYSTEM ?
-                               strerror(errno) : gai_strerror(error)));
+                       progname, hostname, gai_strerror(error));
                return ret;
        }
 
@@ -212,61 +251,6 @@ int nfs_name_to_address(const char *hostname,
        freeaddrinfo(gai_results);
        return ret;
 }
-#else  /* HAVE_DECL_AI_ADDRCONFIG */
-/**
- * nfs_name_to_address - resolve hostname to an IPv4 socket address
- * @hostname: pointer to C string containing DNS hostname to resolve
- * @af_hint: hint to restrict resolution to one address family
- * @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.
- *
- * Some older getaddrinfo(3) implementations don't support
- * AI_ADDRCONFIG or AI_V4MAPPED properly.  For those cases, a DNS
- * resolver based on the traditional gethostbyname(3) is provided.
- */
-int nfs_name_to_address(const char *hostname,
-                       const sa_family_t af_hint,
-                       struct sockaddr *sap, socklen_t *salen)
-{
-       struct sockaddr_in *sin = (struct sockaddr_in *)sap;
-       socklen_t len = *salen;
-       struct hostent *hp;
-
-       *salen = 0;
-
-       if (af_hint != AF_INET) {
-               nfs_error(_("%s: address family not supported by DNS resolver\n"),
-                               progname, hostname);
-               return 0;
-       }
-
-       sin->sin_family = AF_INET;
-       if (inet_aton(hostname, &sin->sin_addr)) {
-               *salen = sizeof(*sin);
-               return 1;
-       }
-
-       hp = gethostbyname(hostname);
-       if (hp == NULL) {
-               nfs_error(_("%s: DNS resolution failed for %s: %s"),
-                               progname, hostname, hstrerror(h_errno));
-               return 0;
-       }
-
-       if (hp->h_length > len) {
-               nfs_error(_("%s: DNS resolution results too long for buffer\n"),
-                               progname);
-               return 0;
-       }
-
-       memcpy(&sin->sin_addr, hp->h_addr, hp->h_length);
-       *salen = hp->h_length;
-       return 1;
-}
-#endif /* HAVE_DECL_AI_ADDRCONFIG */
 
 /**
  * nfs_gethostbyname - resolve a hostname to an IPv4 address
@@ -279,29 +263,27 @@ int nfs_gethostbyname(const char *hostname, struct sockaddr_in *sin)
 {
        socklen_t len = sizeof(*sin);
 
-       return nfs_name_to_address(hostname, AF_INET,
-                                       (struct sockaddr *)sin, &len);
+       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
- * @addrlen:   length of presentation address
  * @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_name_to_address(), but the DNS query is squelched,
- * and won't make any noise if the getaddrinfo() call fails.
+ * 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, const size_t addrlen,
-                          struct sockaddr *sap, socklen_t *salen)
+int nfs_string_to_sockaddr(const char *address, struct sockaddr *sap,
+                          socklen_t *salen)
 {
        struct addrinfo *gai_results;
        struct addrinfo gai_hint = {
@@ -518,12 +500,28 @@ static void nfs_pp_debug(const struct sockaddr *sap, const socklen_t salen,
                strcat(buf, "unknown host");
        }
 
-       fprintf(stderr, _("%s: trying %s prog %ld vers %ld prot %s port %d\n"),
-                       progname, buf, program, version,
+       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
@@ -542,8 +540,8 @@ 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_storage address;
-       struct sockaddr *saddr = (struct sockaddr *)&address;
+       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;
@@ -553,9 +551,11 @@ static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen,
        memcpy(saddr, sap, salen);
        p_prot = prot ? &prot : protos;
        p_vers = vers ? &vers : versions;
-       rpc_createerr.cf_stat = 0;
 
        for (;;) {
+               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) {
@@ -565,28 +565,31 @@ static int nfs_probe_port(const struct sockaddr *sap, const socklen_t salen,
                                if (nfs_rpc_ping(saddr, salen, prog,
                                                        *p_vers, *p_prot, NULL))
                                        goto out_ok;
-                       }
+                       } else
+                               rpc_createerr.cf_stat = RPC_PROGNOTREGISTERED;
                }
                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)
-                       goto out_bad;
+                       break;
 
                if (!prot) {
-                       if (*++p_prot)
+                       if (*++p_prot) {
+                               nfs_pp_debug2("retrying");
                                continue;
+                       }
                        p_prot = protos;
                }
                if (rpc_createerr.cf_stat == RPC_TIMEDOUT ||
                    rpc_createerr.cf_stat == RPC_CANTRECV)
-                       goto out_bad;
+                       break;
 
                if (vers || !*++p_vers)
                        break;
        }
 
-out_bad:
+       nfs_pp_debug2("failed");
        return 0;
 
 out_ok:
@@ -596,10 +599,9 @@ 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;
 }
-
 /*
  * Probe a server's NFS service to determine which versions and
  * transport protocols are supported.
@@ -619,10 +621,14 @@ static int nfs_probe_nfsport(const struct sockaddr *sap, const socklen_t salen,
        if (pmap->pm_vers && pmap->pm_prot && pmap->pm_port)
                return 1;
 
-       if (nfs_mount_data_version >= 4)
+       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_tcp_first);
-       else
+                                       probe_nfs3_first, probe_proto);
+       else
                return nfs_probe_port(sap, salen, pmap,
                                        probe_nfs2_only, probe_udp_only);
 }
@@ -793,8 +799,8 @@ int start_statd(void)
                                execl(START_STATD, START_STATD, NULL);
                                exit(1);
                        case -1: /* error */
-                               nfs_error(_("fork failed: %s"),
-                                                       strerror(errno));
+                               nfs_error(_("%s: fork failed: %s"),
+                                               progname, strerror(errno));
                                break;
                        default: /* parent */
                                waitpid(pid, NULL,0);
@@ -809,6 +815,79 @@ 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
@@ -887,8 +966,10 @@ CLIENT *mnt_openclnt(clnt_addr_t *mnt_server, int *msock)
        }
        if (clnt) {
                /* try to mount hostname:dirname */
-               clnt->cl_auth = authunix_create_default();
-               return clnt;
+               clnt->cl_auth = nfs_authsys_create();
+               if (clnt->cl_auth)
+                       return clnt;
+               CLNT_DESTROY(clnt);
        }
        return NULL;
 }
@@ -1051,7 +1132,7 @@ static int nfs_ca_sockname(const struct sockaddr *sap, const socklen_t salen,
  *
  * Returns 1 and fills in @buf if successful; otherwise, zero.
  */
-static int nfs_ca_gai(const struct sockaddr *sap, const socklen_t salen,
+static int nfs_ca_gai(const struct sockaddr *sap,
                      struct sockaddr *buf, socklen_t *buflen)
 {
        struct addrinfo *gai_results;
@@ -1092,7 +1173,7 @@ int nfs_callback_address(const struct sockaddr *sap, const socklen_t salen,
        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)buf;
 
        if (nfs_ca_sockname(sap, salen, buf, buflen) == 0)
-               if (nfs_ca_gai(sap, salen, buf, buflen) == 0)
+               if (nfs_ca_gai(sap, buf, buflen) == 0)
                        goto out_failed;
 
        /*
@@ -1107,7 +1188,437 @@ int nfs_callback_address(const struct sockaddr *sap, const socklen_t salen,
 out_failed:
        *buflen = 0;
        if (verbose)
-               nfs_error(_("%s: failed to construct callback address"));
+               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 >= 1 && 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.
+        */
+       return nfs_nfs_protocol(options, protocol);
+}
+
+/*
+ * 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 >= 1 && 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;
 }