--- /dev/null
+/*
+ * parse_dev.c -- parse device name into hostname and export path
+ *
+ * Copyright (C) 2008 Oracle.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xcommon.h"
+#include "nls.h"
+#include "parse_dev.h"
+
+#ifndef NFS_MAXHOSTNAME
+#define NFS_MAXHOSTNAME                (255)
+#endif
+
+#ifndef NFS_MAXPATHNAME
+#define NFS_MAXPATHNAME                (1024)
+#endif
+
+extern char *progname;
+extern int verbose;
+
+static int nfs_pdn_no_devname_err(void)
+{
+       nfs_error(_("%s: no device name was provided"), progname);
+       return 0;
+}
+
+static int nfs_pdn_hostname_too_long_err(void)
+{
+       nfs_error(_("%s: server hostname is too long"), progname);
+       return 0;
+}
+
+static int nfs_pdn_pathname_too_long_err(void)
+{
+       nfs_error(_("%s: export pathname is too long"), progname);
+       return 0;
+}
+
+static int nfs_pdn_bad_format_err(void)
+{
+       nfs_error(_("%s: remote share not in 'host:dir' format"), progname);
+       return 0;
+}
+
+static int nfs_pdn_nomem_err(void)
+{
+       nfs_error(_("%s: no memory available to parse devname"), progname);
+       return 0;
+}
+
+static int nfs_pdn_missing_brace_err(void)
+{
+       nfs_error(_("%s: closing bracket missing from server address"),
+                               progname);
+       return 0;
+}
+
+/*
+ * Standard hostname:path format
+ */
+static int nfs_parse_simple_hostname(const char *dev,
+                                    char **hostname, char **pathname)
+{
+       size_t host_len, path_len;
+       char *colon, *comma;
+
+       /* Must have a colon */
+       colon = strchr(dev, ':');
+       if (colon == NULL)
+               return nfs_pdn_bad_format_err();
+       *colon = '\0';
+       host_len = colon - dev;
+
+       if (host_len > NFS_MAXHOSTNAME)
+               return nfs_pdn_hostname_too_long_err();
+
+       /* If there's a comma before the colon, take only the
+        * first name in list */
+       comma = strchr(dev, ',');
+       if (comma != NULL) {
+               *comma = '\0';
+               host_len = comma - dev;
+               nfs_error(_("%s: warning: multiple hostnames not supported"),
+                               progname);
+       } else
+
+       colon++;
+       path_len = strlen(colon);
+       if (path_len > NFS_MAXPATHNAME)
+               return nfs_pdn_pathname_too_long_err();
+
+       if (hostname) {
+               *hostname = strndup(dev, host_len);
+               if (*hostname == NULL)
+                       return nfs_pdn_nomem_err();
+       }
+       if (pathname) {
+               *pathname = strndup(colon, path_len);
+               if (*pathname == NULL) {
+                       free(*hostname);
+                       return nfs_pdn_nomem_err();
+               }
+       }
+       return 1;
+}
+
+/*
+ * To handle raw IPv6 addresses (which contain colons), the
+ * server's address is enclosed in square brackets.  Return
+ * what's between the brackets.
+ *
+ * There could be anything in between the brackets, but we'll
+ * let DNS resolution sort it out later.
+ */
+static int nfs_parse_square_bracket(const char *dev,
+                                   char **hostname, char **pathname)
+{
+       size_t host_len, path_len;
+       char *cbrace;
+
+       dev++;
+
+       /* Must have a closing square bracket */
+       cbrace = strchr(dev, ']');
+       if (cbrace == NULL)
+               return nfs_pdn_missing_brace_err();
+       *cbrace = '\0';
+       host_len = cbrace - dev;
+
+       /* Must have a colon just after the closing bracket */
+       cbrace++;
+       if (*cbrace != ':')
+               return nfs_pdn_bad_format_err();
+
+       if (host_len > NFS_MAXHOSTNAME)
+               return nfs_pdn_hostname_too_long_err();
+
+       cbrace++;
+       path_len = strlen(cbrace);
+       if (path_len > NFS_MAXPATHNAME)
+               return nfs_pdn_pathname_too_long_err();
+
+       if (hostname) {
+               *hostname = strndup(dev, host_len);
+               if (*hostname == NULL)
+                       return nfs_pdn_nomem_err();
+       }
+       if (pathname) {
+               *pathname = strndup(cbrace, path_len);
+               if (*pathname == NULL) {
+                       free(*hostname);
+                       return nfs_pdn_nomem_err();
+               }
+       }
+       return 1;
+}
+
+/*
+ * RFC 2224 says an NFS client must grok "public file handles" to
+ * support NFS URLs.  Linux doesn't do that yet.  Print a somewhat
+ * helpful error message in this case instead of pressing forward
+ * with the mount request and failing with a cryptic error message
+ * later.
+ */
+static int nfs_parse_nfs_url(const char *dev,
+                            char **hostname, char **pathname)
+{
+       nfs_error(_("%s: NFS URLs are not supported"), progname);
+       return 0;
+}
+
+/**
+ * nfs_parse_devname - Determine the server's hostname by looking at "devname".
+ * @devname: pointer to mounted device name (first argument of mount command)
+ * @hostname: OUT: pointer to server's hostname
+ * @pathname: OUT: pointer to export path on server
+ *
+ * Returns 1 if succesful, or zero if some error occurred.  On success,
+ * @hostname and @pathname point to dynamically allocated buffers containing
+ * the hostname of the server and the export pathname (both '\0'-terminated).
+ *
+ * @hostname or @pathname may be NULL if caller doesn't want a copy of those
+ * parts of @devname.
+ *
+ * Note that this will not work if @devname is a wide-character string.
+ */
+int nfs_parse_devname(const char *devname,
+                     char **hostname, char **pathname)
+{
+       char *dev;
+       int result;
+
+       if (devname == NULL)
+               return nfs_pdn_no_devname_err();
+
+       /* Parser is destructive, so operate on a copy of the device name. */
+       dev = strdup(devname);
+       if (dev == NULL)
+               return nfs_pdn_nomem_err();
+       if (*dev == '[')
+               result = nfs_parse_square_bracket(dev, hostname, pathname);
+       else if (strncmp(dev, "nfs://", 6) == 0)
+               result = nfs_parse_nfs_url(dev, hostname, pathname);
+       else
+               result = nfs_parse_simple_hostname(dev, hostname, pathname);
+
+       free(dev);
+       return result;
+}
 
 #include "network.h"
 #include "parse_opt.h"
 #include "version.h"
+#include "parse_dev.h"
 
 #ifdef HAVE_RPCSVC_NFS_PROT_H
 #include <rpcsvc/nfs_prot.h>
        sa_family_t             family;         /* supported address family */
 };
 
-static int nfs_parse_devname(struct nfsmount_info *mi)
-{
-       int ret = 0;
-       char *dev, *pathname, *s;
-
-       dev = xstrdup(mi->spec);
-
-       if (!(pathname = strchr(dev, ':'))) {
-               nfs_error(_("%s: remote share not in 'host:dir' format"),
-                               progname);
-               goto out;
-       }
-       *pathname = '\0';
-       pathname++;
-
-       /*
-        * We don't need a copy of the pathname, but let's
-        * sanity check it anyway.
-        */
-       if (strlen(pathname) > NFS_MAXPATHNAME) {
-               nfs_error(_("%s: export pathname is too long"),
-                               progname);
-               goto out;
-       }
-
-       /*
-        * Ignore all but first hostname in replicated mounts
-        * until they can be fully supported. (mack@sgi.com)
-        */
-       if ((s = strchr(dev, ','))) {
-               *s = '\0';
-               nfs_error(_("%s: warning: multiple hostnames not supported"),
-                               progname);
-               nfs_error(_("%s: ignoring hostnames that follow the first one"),
-                               progname);
-       }
-       mi->hostname = xstrdup(dev);
-       if (strlen(mi->hostname) > NFS_MAXHOSTNAME) {
-               nfs_error(_("%s: server hostname is too long"),
-                               progname);
-               free(mi->hostname);
-               mi->hostname = NULL;
-               goto out;
-       }
-
-       ret = 1;
-
-out:
-       free(dev);
-       return ret;
-}
-
 static int fill_ipv4_sockaddr(const char *hostname, struct sockaddr_in *addr)
 {
        struct hostent *hp;
  */
 static int nfs_validate_options(struct nfsmount_info *mi)
 {
-       struct sockaddr_in saddr;
+       struct sockaddr_storage dummy;
+       struct sockaddr *sap = (struct sockaddr *)&dummy;
+       socklen_t salen = sizeof(dummy);
 
-       if (!fill_ipv4_sockaddr(mi->hostname, &saddr))
+       if (!nfs_parse_devname(mi->spec, &mi->hostname, NULL))
+               return 0;
+
+       if (!nfs_name_to_address(mi->hostname, mi->family, sap, &salen))
                return 0;
 
        if (strncmp(mi->type, "nfs4", 4) == 0) {
-               if (!nfs_append_clientaddr_option((struct sockaddr *)&saddr,
-                                                 sizeof(saddr), mi->options))
+               if (!nfs_append_clientaddr_option(sap, salen, mi->options))
                        return 0;
        } else {
                if (!nfs_fix_mounthost_option(mi->family, mi->options))
        if (!nfs_append_sloppy_option(mi->options))
                return 0;
 
-       return nfs_append_addr_option((struct sockaddr *)&saddr,
-                                       sizeof(saddr), mi->options);
+       return nfs_append_addr_option(sap, salen, mi->options);
 }
 
 /*
        };
        int retval = EX_FAIL;
 
-       if (!nfs_parse_devname(&mi))
-               return retval;
-
        mi.options = po_split(*extra_opts);
        if (mi.options) {
                retval = nfsmount_start(&mi);