changeset 18409:9febfcb4d9b7

OS-3812 lxbrand nfs mounting fails
author Jerry Jelinek <jerry.jelinek@joyent.com>
date Wed, 15 Apr 2015 19:38:41 +0000
parents a2ff6d3af7f6
children ef99357ef952
files usr/src/lib/brand/lx/lx_brand/Makefile.com usr/src/lib/brand/lx/lx_brand/common/mount.c usr/src/lib/brand/lx/lx_brand/common/mount_nfs.c usr/src/lib/brand/lx/lx_brand/sys/lx_mount.h usr/src/lib/libnsl/common/mapfile-vers usr/src/lib/libnsl/netselect/netselect.c usr/src/uts/common/sys/netconfig.h
diffstat 7 files changed, 2285 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/lib/brand/lx/lx_brand/Makefile.com	Fri Apr 10 19:26:51 2015 +0000
+++ b/usr/src/lib/brand/lx/lx_brand/Makefile.com	Wed Apr 15 19:38:41 2015 +0000
@@ -45,6 +45,7 @@
 		misc.o			\
 		module.o		\
 		mount.o			\
+		mount_nfs.o		\
 		pgrp.o			\
 		poll_select.o		\
 		priority.o		\
@@ -79,7 +80,7 @@
 UTSBASE	=	../../../../../uts
 
 LIBS =		$(DYNLIB)
-LDLIBS +=	-lc -lsocket -lmapmalloc -lproc -lrtld_db
+LDLIBS +=	-lc -lsocket -lmapmalloc -lproc -lrtld_db -lrpcsvc -lnsl
 DYNFLAGS +=	$(DYNFLAGS_$(CLASS))
 DYNFLAGS +=	$(BLOCAL) $(ZNOVERSION) -Wl,-e_start -M../common/mapfile
 CFLAGS +=	$(CCVERBOSE)
--- a/usr/src/lib/brand/lx/lx_brand/common/mount.c	Fri Apr 10 19:26:51 2015 +0000
+++ b/usr/src/lib/brand/lx/lx_brand/common/mount.c	Wed Apr 15 19:38:41 2015 +0000
@@ -606,8 +606,9 @@
 	const void		*datap = (const void *)p5;
 
 	/* Variables needed for all mounts. */
-	char			source[MAXPATHLEN], target[MAXPATHLEN];
-	char			fstype[MAXPATHLEN], options[MAXPATHLEN];
+	char			source[MAXPATHLEN + LX_NMD_MAXHOSTNAMELEN + 1];
+	char			target[MAXPATHLEN];
+	char			fstype[MAXPATHLEN], options[MAX_MNTOPT_STR];
 	int			sflags, rv;
 
 	/* Variables needed for nfs mounts. */
@@ -736,23 +737,67 @@
 	} else if (strcmp(fstype, "nfs") == 0) {
 
 		/*
-		 * Copy in Linux mount options.  Note that for Linux
-		 * nfs mounts the mount options pointer (which normally
-		 * points to a string) points to a structure.
+		 * Copy in Linux mount options. Note that for older Linux
+		 * kernels (pre 2.6.23) the mount options pointer (which
+		 * normally points to a string) points to a structure which
+		 * is populated by the user-level code after it has done the
+		 * preliminary RPCs (similar to how our NFS mount cmd works).
+		 * For newer kernels the options pointer is just a string of
+		 * options. We're unlikely to actually emulate a kernel that
+		 * uses the old style but support is kept and handled in
+		 * i_make_nfs_args(). The new style handling is implemented in
+		 * nfs_pre_mount(). The user-level mount caller is in charge of
+		 * determining the format in which it passes the data parameter.
 		 */
-		if (uucopy((void *)datap, &lx_nmd, sizeof (lx_nmd)) < 0)
+		int vers;
+
+		if (uucopy((void *)datap, &vers, sizeof (int)) < 0)
 			return (-errno);
 
 		/*
-		 * For Solaris nfs mounts, the kernel expects a special
-		 * strucutre, but a pointer to this structure is passed
-		 * in via an extra parameter (sdataptr below.)
+		 * As described above, the data parameter might be a versioned
+		 * lx_nmd structure or (most likely) it is just a string.
 		 */
-		if ((rv = i_make_nfs_args(&lx_nmd, &nfs_args,
-		    &nfs_args_addr, &nfs_args_knconf, &nfs_args_fh,
-		    &nfs_args_secdata, fstype,
-		    options, sizeof (options))) != 0)
-			return (rv);
+		switch (vers) {
+		case 1:
+		case 2:
+		case 3:
+		case 5:
+		case 6:
+			lx_unsupported("unsupported nfs mount request "
+			    "version: %d\n", vers);
+			return (-ENOTSUP);
+
+		case 4:
+			if (uucopy((void *)datap, &lx_nmd, sizeof (lx_nmd)) < 0)
+				return (-errno);
+
+			/*
+			 * For Illumos nfs mounts, the kernel expects a special
+			 * structure, but a pointer to this structure is passed
+			 * in via an extra parameter (sdataptr below.)
+			 */
+			if ((rv = i_make_nfs_args(&lx_nmd, &nfs_args,
+			    &nfs_args_addr, &nfs_args_knconf, &nfs_args_fh,
+			    &nfs_args_secdata, fstype, options,
+			    sizeof (options))) != 0)
+				return (rv);
+
+			break;
+
+		default:
+			/*
+			 * Handle new style with options as a string, make
+			 * the preliminary RPC calls and do the native mount
+			 * all within lx_nfs_mount().
+			 */
+			if (uucopystr((void *)datap, options,
+			    sizeof (options)) < 0)
+				return (-errno);
+			return (lx_nfs_mount(source, target, fstype, flags,
+			    options));
+			break;
+		}
 
 		/*
 		 * For nfs mounts we need to tell the mount system call
@@ -766,7 +811,7 @@
 		return (-ENODEV);
 	}
 
-	/* Convert some Linux flags to Solaris flags. */
+	/* Convert some Linux flags to Illumos flags. */
 	if (flags & LX_MS_RDONLY)
 		sflags |= MS_RDONLY;
 	if (flags & LX_MS_NOSUID)
@@ -775,7 +820,7 @@
 		sflags |= MS_REMOUNT;
 
 	/*
-	 * Convert some Linux flags to Solaris option strings.
+	 * Convert some Linux flags to Illumos option strings.
 	 */
 	if (flags & LX_MS_STRICTATIME) {
 		/*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/brand/lx/lx_brand/common/mount_nfs.c	Wed Apr 15 19:38:41 2015 +0000
@@ -0,0 +1,2155 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * NFS mount syscall support
+ *
+ * All of the Linux NFS mount RPC support is handled within the kernel whereas
+ * on Illumos the NFS mount command performs the initial RPC calls to contact
+ * the server's mountd, get the file handle, and negotiate security before
+ * making the actual 'mount' syscall. Thus we emulate the Linux in-kernel
+ * RPC behavior here using code that is partially based on the code from our
+ * user-level NFS mount command. This code also includes the nullproc RPC
+ * function calls
+ *
+ * In addition to the code described above we also have brand-specific code to
+ * convert the Linux mount arguments into our native format.
+ *
+ * Because libnsl (which we need to make RPCs) depends on the netconfig table
+ * (which won't exist inside an lx zone) we provide a built-in default
+ * netconfig table which we hook into libnsl via the brand callbacks.
+ *
+ * Finally, in most of the functions below, when the code refers to the
+ * hostname we're really working with the IP addr that the Linux user-level
+ * mount command passed in to us.
+ */
+
+/*
+ * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
+/*	  All Rights Reserved  	*/
+
+/*
+ * University Copyright- Copyright (c) 1982, 1986, 1988
+ * The Regents of the University of California
+ * All Rights Reserved
+ *
+ * University Acknowledgment- Portions of this document are derived from
+ * software developed by the University of California, Berkeley, and its
+ * contributors.
+ */
+
+/*
+ * Copyright 2015 Joyent, Inc.
+ */
+
+#define	NFSCLIENT
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include <rpc/rpc.h>
+#include <errno.h>
+#include <netdb.h>
+#include <sys/mount.h>
+#include <sys/mntent.h>
+#include <sys/mnttab.h>
+#include <nfs/nfs.h>
+#include <nfs/mount.h>
+#include <rpcsvc/mount.h>
+#include <sys/pathconf.h>
+#include <netdir.h>
+#include <netconfig.h>
+#include <sys/sockio.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <nfs/nfs_sec.h>
+#include <rpcsvc/daemon_utils.h>
+#include <rpcsvc/nfs4_prot.h>
+#include <limits.h>
+#include <nfs/nfssys.h>
+#include <strings.h>
+#include <sys/lx_mount.h>
+
+#ifndef	NFS_VERSMAX
+#define	NFS_VERSMAX	4
+#endif
+#ifndef	NFS_VERSMIN
+#define	NFS_VERSMIN	2
+#endif
+
+#define	RET_OK		0
+#define	RET_RETRY	32
+#define	RET_ERR		33
+#define	RET_MNTERR	1000
+#define	ERR_PROTO_NONE		0
+#define	ERR_PROTO_INVALID	901
+#define	ERR_PROTO_UNSUPP	902
+#define	ERR_NETPATH		903
+#define	ERR_NOHOST		904
+#define	ERR_RPCERROR		905
+
+typedef struct err_ret {
+	int error_type;
+	int error_value;
+} err_ret_t;
+
+#define	SET_ERR_RET(errst, etype, eval) \
+	(errst)->error_type = etype; \
+	(errst)->error_value = eval;
+
+/*
+ * Built-in netconfig table.
+ */
+#define	N_NETCONF_ENTS	4
+static struct netconfig nca[N_NETCONF_ENTS] = {
+	{"udp6", NC_TPI_CLTS,     1, "inet6", "udp", "/dev/udp6", 0, NULL},
+	{"tcp6", NC_TPI_COTS_ORD, 1, "inet6", "tcp", "/dev/tcp6", 0, NULL},
+	{"udp",  NC_TPI_CLTS,     1, "inet",  "udp", "/dev/udp", 0, NULL},
+	{"tcp",  NC_TPI_COTS_ORD, 1, "inet",  "tcp", "/dev/tcp", 0, NULL}
+};
+
+/*
+ * Mapping table of Linux NFS mount options to the corresponding Illumos
+ * option. The nmo_argtyp field tells us how to handle the argument.
+ */
+typedef enum map_mount_opt_type {
+	MOUNT_OPT_INVALID	= 0,
+	MOUNT_OPT_PASTHRU	= 1,
+	MOUNT_OPT_IGNORE	= 2,
+	MOUNT_OPT_TOKEN		= 3,
+	MOUNT_OPT_HAS_ARG	= 4
+} map_mount_opt_type_t;
+
+typedef struct nfs_map_opt {
+	char			*nmo_lx_opt;
+	char			*nmo_il_opt;
+	map_mount_opt_type_t	nmo_argtyp;
+} nfs_map_opt_t;
+
+static nfs_map_opt_t nmo_tab[] = {
+	{"ac",		NULL,		MOUNT_OPT_IGNORE},
+	{"acdirmax",	NULL,		MOUNT_OPT_PASTHRU},
+	{"acdirmin",	NULL,		MOUNT_OPT_PASTHRU},
+	{"acl",		NULL,		MOUNT_OPT_INVALID},
+	{"acregmax",	NULL,		MOUNT_OPT_PASTHRU},
+	{"acregmin",	NULL,		MOUNT_OPT_PASTHRU},
+	{"actimeo",	NULL,		MOUNT_OPT_PASTHRU},
+	{"bg",		NULL,		MOUNT_OPT_PASTHRU},
+	{"cto",		NULL,		MOUNT_OPT_IGNORE},
+	{"fg",		NULL,		MOUNT_OPT_PASTHRU},
+	{"fsc",		NULL,		MOUNT_OPT_IGNORE},
+	{"hard",	NULL,		MOUNT_OPT_PASTHRU},
+	{"intr",	NULL,		MOUNT_OPT_PASTHRU},
+	{"lock",	NULL,		MOUNT_OPT_IGNORE},
+	{"lookupcache",	NULL,		MOUNT_OPT_INVALID},
+	{"local_lock=%s", NULL,		MOUNT_OPT_INVALID },
+	{"migration",	NULL,		MOUNT_OPT_INVALID},
+	{"minorversion", NULL,		MOUNT_OPT_INVALID},
+	{"mountaddr",	NULL,		MOUNT_OPT_INVALID},
+	{"mounthost",	NULL,		MOUNT_OPT_INVALID},
+	{"mountport",	NULL,		MOUNT_OPT_INVALID},
+	{"mountproto",	NULL,		MOUNT_OPT_INVALID},
+	{"mountvers",	NULL,		MOUNT_OPT_INVALID},
+	{"namlen",	NULL,		MOUNT_OPT_INVALID},
+	{"nfsvers",	NULL,		MOUNT_OPT_INVALID},
+	{"noac",	NULL,		MOUNT_OPT_PASTHRU},
+	{"noacl",	NULL,		MOUNT_OPT_INVALID},
+	{"nocto",	NULL,		MOUNT_OPT_PASTHRU},
+	{"nofsc",	NULL,		MOUNT_OPT_IGNORE},
+	{"nointr",	NULL,		MOUNT_OPT_PASTHRU},
+	{"nolock",	"llock",	MOUNT_OPT_TOKEN},
+	{"nomigration",	NULL,		MOUNT_OPT_INVALID},
+	{"noposix",	NULL,		MOUNT_OPT_IGNORE},
+	{"nordirplus",	NULL,		MOUNT_OPT_IGNORE},
+	{"noresvport",	NULL,		MOUNT_OPT_INVALID},
+	{"nosharecache", NULL,		MOUNT_OPT_IGNORE},
+	{"port",	NULL,		MOUNT_OPT_PASTHRU},
+	{"posix",	NULL,		MOUNT_OPT_PASTHRU},
+	{"proto",	NULL,		MOUNT_OPT_PASTHRU},
+	{"rdirplus",	NULL,		MOUNT_OPT_IGNORE},
+	{"rdma",	"proto=rdma",	MOUNT_OPT_TOKEN},
+	{"resvport",	NULL,		MOUNT_OPT_IGNORE},
+	{"retrans",	NULL,		MOUNT_OPT_PASTHRU},
+	{"retry",	NULL,		MOUNT_OPT_PASTHRU},
+	{"rsize",	NULL,		MOUNT_OPT_PASTHRU},
+	{"sec",		NULL,		MOUNT_OPT_PASTHRU},
+	{"sharecache",	NULL,		MOUNT_OPT_IGNORE},
+	{"sloppy",	NULL,		MOUNT_OPT_INVALID},
+	{"soft",	NULL,		MOUNT_OPT_PASTHRU},
+	{"tcp",		"proto=tcp",	MOUNT_OPT_TOKEN},
+	{"timeo",	NULL,		MOUNT_OPT_PASTHRU},
+	{"udp",		"proto=udp",	MOUNT_OPT_TOKEN},
+	{"vers",	NULL,		MOUNT_OPT_PASTHRU},
+	{"wsize",	NULL,		MOUNT_OPT_PASTHRU},
+	{NULL,		NULL,		MOUNT_OPT_INVALID}
+};
+
+/*
+ * This struct is used to keep track of misc. variables which are set deep
+ * in one function then referenced someplace else. We pass this around to
+ * avoid the use of global variables as is done the the NFS mount command.
+ *
+ * The nfsvers variables control the NFS version number to be used.
+ *
+ * nmd_nfsvers defaults to 0 which means to use the highest number that
+ * both the client and the server support.  It can also be set to
+ * a particular value, either 2, 3, or 4 to indicate the version
+ * number of choice.  If the server (or the client) do not support
+ * the version indicated, then the mount attempt will be failed.
+ *
+ * nmd_nfsvers_to_use is the actual version number found to use.  It
+ * is determined in get_fh by pinging the various versions of the
+ * NFS service on the server to see which responds positively.
+ *
+ * nmd_nfsretry_vers is the version number set when we retry the mount
+ * command with the version decremented from nmd_nfsvers_to_use.
+ * nmd_nfsretry_vers is set from nmd_nfsvers_to_use when we retry the mount
+ * for errors other than RPC errors; it helps us know why we are
+ * retrying. It is an indication that the retry is due to non-RPC errors.
+ */
+typedef struct nfs_mnt_data {
+	int		nmd_bg;
+	int		nmd_posix;
+	int		nmd_retries;
+	ushort_t	nmd_nfs_port;
+	char		*nmd_nfs_proto;
+	char		*nmd_fstype;
+	seconfig_t	nmd_nfs_sec;
+	int		nmd_sec_opt;	/* any security option ? */
+	rpcvers_t	nmd_nfsvers;
+	rpcvers_t	nmd_nfsvers_to_use;
+	rpcvers_t	nmd_nfsretry_vers;
+} nfs_mnt_data_t;
+
+/* number of transports to try */
+#define	MNT_PREF_LISTLEN	2
+#define	FIRST_TRY		1
+#define	SECOND_TRY		2
+
+#define	BIGRETRY	10000
+
+/* maximum length of RPC header for NFS messages */
+#define	NFS_RPC_HDR	432
+
+#define	NFS_ARGS_EXTB_secdata(args, secdata) \
+	{ (args)->nfs_args_ext = NFS_ARGS_EXTB, \
+	(args)->nfs_ext_u.nfs_extB.secdata = secdata; }
+
+extern int __clnt_bindresvport(CLIENT *);
+
+static int retry(struct mnttab *, int, nfs_mnt_data_t *);
+static int set_args(int *, struct nfs_args *, char *, struct mnttab *,
+    nfs_mnt_data_t *);
+static int get_fh(struct nfs_args *, char *, char *, int *,
+	struct netconfig **, ushort_t, nfs_mnt_data_t *);
+static int make_secure(struct nfs_args *, char *, struct netconfig *,
+	rpcvers_t, nfs_mnt_data_t *);
+static int mount_nfs(struct mnttab *, int, err_ret_t *, nfs_mnt_data_t *);
+static int getaddr_nfs(struct nfs_args *, char *, struct netconfig **,
+	char *, ushort_t, err_ret_t *, bool_t, nfs_mnt_data_t *);
+static struct netbuf *get_addr(char *, rpcprog_t, rpcvers_t,
+	struct netconfig **, char *, ushort_t, struct t_info *,
+	caddr_t *, char *, err_ret_t *);
+static struct netbuf *get_the_addr(char *, rpcprog_t, rpcvers_t,
+	struct netconfig *, ushort_t, struct t_info *, caddr_t *,
+	char *, err_ret_t *);
+
+static int lx_nsl_set_sz_func(void);
+static struct netconfig *lx_get_ent_func(int);
+
+/*
+ * These are the defaults (range) for the client when determining
+ * which NFS version to use when probing the server (see above).
+ * These will only be used when the vers mount option is not used.
+ */
+#define	vers_max_default NFS_VERSMAX_DEFAULT
+#define	vers_min_default NFS_VERSMIN_DEFAULT
+
+/*
+ * The wnl/WNL* definitions come from cmd/fs.d/nfs/mount/webnfs.h. We
+ * incorporate those here since the cmd src tree hierarchy is not built when
+ * we're compiling the lib portion of the src tree and since these definitions
+ * are a fundamental part of the protocol spec, there is no risk of these
+ * changing (i.e. we're just like the Linux kernel here, which has these
+ * built-in). We only need the bare minimum set of definitions to make RPCs to
+ * the NFS server to negotiate the mount.
+ */
+
+/* The timeout for our mount null proc pings is always 5 seconds. */
+static struct timeval TIMEOUT = { 5, 0 };
+#define	WNLPROC_NULL	0
+#define	WNLPROC3_NULL	0
+#define	WNLPROC4_NULL	0
+#define	WNL_FHSIZE	32
+
+static enum clnt_stat
+wnlproc_null_2(void *argp, void *clnt_res, CLIENT *clnt)
+{
+	return (clnt_call(clnt, WNLPROC_NULL, (xdrproc_t)xdr_void,
+	    (caddr_t)argp, (xdrproc_t)xdr_void, (caddr_t)clnt_res, TIMEOUT));
+}
+
+static enum clnt_stat
+wnlproc3_null_3(void *argp, void *clnt_res, CLIENT *clnt)
+{
+	return (clnt_call(clnt, WNLPROC3_NULL, (xdrproc_t)xdr_void,
+	    (caddr_t)argp, (xdrproc_t)xdr_void, (caddr_t)clnt_res, TIMEOUT));
+}
+
+static enum clnt_stat
+wnlproc4_null_4(void *argp, void *clnt_res, CLIENT *clnt)
+{
+	return (clnt_call(clnt, WNLPROC4_NULL, (xdrproc_t)xdr_void,
+	    (caddr_t)argp, (xdrproc_t)xdr_void, (caddr_t)clnt_res, TIMEOUT));
+}
+
+static void
+log_err(const char *fmt, ...)
+{
+	va_list ap;
+	char buf[128];
+	int fd;
+
+	va_start(ap, fmt);
+	(void) vsnprintf(buf, sizeof (buf), fmt, ap);
+	va_end(ap);
+
+	if ((fd = open("/dev/conslog", O_WRONLY)) != -1) {
+		(void) write(fd, buf, strlen(buf));
+		(void) close(fd);
+	}
+}
+
+static int
+i_add_option(char *option, char *buf, size_t buf_size)
+{
+	int len;
+	char *fmt_str = NULL;
+
+	if (buf[0] == '\0') {
+		fmt_str = "%s";
+	} else {
+		fmt_str = ",%s";
+	}
+
+	len = strlen(buf);
+	buf_size -= len;
+	buf += len;
+
+	/*LINTED*/
+	if (snprintf(buf, buf_size, fmt_str, option) > (buf_size - 1))
+		return (-EOVERFLOW);
+	return (0);
+}
+
+/*
+ * We can return a negative error value which is the errno we need to return to
+ * lx or we can return a positive error value which is primarily used to
+ * indicate retry (otherwise we map to -EINVAL in the caller). Returning 0
+ * indicates success.
+ */
+static int
+mount_nfs(struct mnttab *mntp, int mntflags, err_ret_t *retry_error,
+    nfs_mnt_data_t *nmdp)
+{
+	struct nfs_args *argp = NULL;
+	struct netconfig *nconf = NULL;
+	int vers = 0;
+	int r = 0;
+	ushort_t port;
+	char *colonp;
+	char *path;
+	char *host;
+	char spec_buf[MAXPATHLEN + LX_NMD_MAXHOSTNAMELEN + 1];
+
+	mntp->mnt_fstype = MNTTYPE_NFS;
+
+	(void) strlcpy(spec_buf, mntp->mnt_special, sizeof (spec_buf));
+	colonp = strchr(spec_buf, ':');
+	if (colonp == NULL)
+		return (-EINVAL);
+
+	*colonp = '\0';
+	host = spec_buf;
+	path = colonp + 1;
+
+	argp = (struct nfs_args *)malloc(sizeof (*argp));
+	if (argp == NULL)
+		return (-ENOMEM);
+
+	memset(argp, 0, sizeof (*argp));
+	memset(&nmdp->nmd_nfs_sec, 0, sizeof (seconfig_t));
+	nmdp->nmd_sec_opt = 0;
+	port = 0;
+
+	if ((r = set_args(&mntflags, argp, host, mntp, nmdp)) != 0)
+		goto out;
+
+	if (port == 0) {
+		port = nmdp->nmd_nfs_port;
+	} else if (nmdp->nmd_nfs_port != 0 && nmdp->nmd_nfs_port != port) {
+		r = -EINVAL;
+		goto out;
+	}
+
+	r = get_fh(argp, host, path, &vers, &nconf, port, nmdp);
+	if (r != 0) {
+		/* All attempts failed */
+		if (r == RET_MNTERR) {
+			r = -EREMOTE;
+		} else if (r != RET_RETRY) {
+			r = -EAGAIN;
+		}
+		goto out;
+	}
+
+	/*
+	 * Call to get_fh() above may have obtained the netconfig info and NULL
+	 * proc'd the server. This would be the case with v4
+	 */
+	if (!(argp->flags & NFSMNT_KNCONF)) {
+		nconf = NULL;
+		r = getaddr_nfs(argp, host, &nconf, path, port,
+		    retry_error, TRUE, nmdp);
+		if (r != 0) {
+			if (r != RET_RETRY)
+				r = -EAGAIN;
+			goto out;
+		}
+	}
+
+	if (make_secure(argp, host, nconf, vers, nmdp) < 0) {
+		r = -EAGAIN;
+		goto out;
+	}
+
+	mntflags |= MS_DATA | MS_OPTIONSTR;
+
+	r = mount(mntp->mnt_special, mntp->mnt_mountp, mntflags,
+	    nmdp->nmd_fstype, argp, sizeof (*argp), mntp->mnt_mntopts,
+	    MAX_MNTOPT_STR);
+	if (r != 0)
+		r = -r;
+out:
+	if (argp->fh)
+		free(argp->fh);
+	if (argp->pathconf)
+		free(argp->pathconf);
+	if (argp->knconf)
+		free(argp->knconf);
+	if (argp->addr) {
+		free(argp->addr->buf);
+		free(argp->addr);
+	}
+	nfs_free_secdata(argp->nfs_ext_u.nfs_extB.secdata);
+	if (argp->syncaddr) {
+		free(argp->syncaddr->buf);
+		free(argp->syncaddr);
+	}
+	if (argp->netname)
+		free(argp->netname);
+	free(argp);
+
+	return (r);
+}
+
+/*
+ * These options are duplicated in uts/common/fs/nfs/nfs_dlinet.c
+ * Changes must be made to both lists.
+ */
+static char *optlist[] = {
+#define	OPT_RO		0
+	MNTOPT_RO,
+#define	OPT_RW		1
+	MNTOPT_RW,
+#define	OPT_QUOTA	2
+	MNTOPT_QUOTA,
+#define	OPT_NOQUOTA	3
+	MNTOPT_NOQUOTA,
+#define	OPT_SOFT	4
+	MNTOPT_SOFT,
+#define	OPT_HARD	5
+	MNTOPT_HARD,
+#define	OPT_SUID	6
+	MNTOPT_SUID,
+#define	OPT_NOSUID	7
+	MNTOPT_NOSUID,
+#define	OPT_GRPID	8
+	MNTOPT_GRPID,
+#define	OPT_REMOUNT	9
+	MNTOPT_REMOUNT,
+#define	OPT_NOSUB	10
+	MNTOPT_NOSUB,
+#define	OPT_INTR	11
+	MNTOPT_INTR,
+#define	OPT_NOINTR	12
+	MNTOPT_NOINTR,
+#define	OPT_PORT	13
+	MNTOPT_PORT,
+#define	OPT_SECURE	14
+	MNTOPT_SECURE,
+#define	OPT_RSIZE	15
+	MNTOPT_RSIZE,
+#define	OPT_WSIZE	16
+	MNTOPT_WSIZE,
+#define	OPT_TIMEO	17
+	MNTOPT_TIMEO,
+#define	OPT_RETRANS	18
+	MNTOPT_RETRANS,
+#define	OPT_ACTIMEO	19
+	MNTOPT_ACTIMEO,
+#define	OPT_ACREGMIN	20
+	MNTOPT_ACREGMIN,
+#define	OPT_ACREGMAX	21
+	MNTOPT_ACREGMAX,
+#define	OPT_ACDIRMIN	22
+	MNTOPT_ACDIRMIN,
+#define	OPT_ACDIRMAX	23
+	MNTOPT_ACDIRMAX,
+#define	OPT_BG		24
+	MNTOPT_BG,
+#define	OPT_FG		25
+	MNTOPT_FG,
+#define	OPT_RETRY	26
+	MNTOPT_RETRY,
+#define	OPT_NOAC	27
+	MNTOPT_NOAC,
+#define	OPT_NOCTO	28
+	MNTOPT_NOCTO,
+#define	OPT_LLOCK	29
+	MNTOPT_LLOCK,
+#define	OPT_POSIX	30
+	MNTOPT_POSIX,
+#define	OPT_VERS	31
+	MNTOPT_VERS,
+#define	OPT_PROTO	32
+	MNTOPT_PROTO,
+#define	OPT_SEMISOFT	33
+	MNTOPT_SEMISOFT,
+#define	OPT_NOPRINT	34
+	MNTOPT_NOPRINT,
+#define	OPT_SEC		35
+	MNTOPT_SEC,
+#define	OPT_LARGEFILES	36
+	MNTOPT_LARGEFILES,
+#define	OPT_NOLARGEFILES 37
+	MNTOPT_NOLARGEFILES,
+#define	OPT_PUBLIC	38
+	MNTOPT_PUBLIC,
+#define	OPT_DIRECTIO	39
+	MNTOPT_FORCEDIRECTIO,
+#define	OPT_NODIRECTIO	40
+	MNTOPT_NOFORCEDIRECTIO,
+#define	OPT_XATTR	41
+	MNTOPT_XATTR,
+#define	OPT_NOXATTR	42
+	MNTOPT_NOXATTR,
+#define	OPT_DEVICES	43
+	MNTOPT_DEVICES,
+#define	OPT_NODEVICES	44
+	MNTOPT_NODEVICES,
+#define	OPT_SETUID	45
+	MNTOPT_SETUID,
+#define	OPT_NOSETUID	46
+	MNTOPT_NOSETUID,
+#define	OPT_EXEC	47
+	MNTOPT_EXEC,
+#define	OPT_NOEXEC	48
+	MNTOPT_NOEXEC,
+	NULL
+};
+
+static int
+convert_int(int *val, char *str)
+{
+	long lval;
+
+	if (str == NULL || !isdigit(*str))
+		return (-1);
+
+	lval = strtol(str, &str, 10);
+	if (*str != '\0' || lval > INT_MAX)
+		return (-2);
+
+	*val = (int)lval;
+	return (0);
+}
+
+static int
+set_args(int *mntflags, struct nfs_args *args, char *fshost, struct mnttab *mnt,
+    nfs_mnt_data_t *nmdp)
+{
+	char *saveopt, *optstr, *opts, *newopts, *val;
+	int num;
+	int largefiles = 0;
+	int invalid = 0;
+	int attrpref = 0;
+	int optlen;
+
+	args->flags = NFSMNT_INT;	/* default is "intr" */
+	args->flags |= NFSMNT_HOSTNAME;
+	args->flags |= NFSMNT_NEWARGS;	/* using extented nfs_args structure */
+	args->hostname = fshost;
+
+	optstr = opts = strdup(mnt->mnt_mntopts);
+	/* sizeof (MNTOPT_XXX) includes one extra byte we may need for "," */
+	optlen = strlen(mnt->mnt_mntopts) + sizeof (MNTOPT_XATTR) + 1;
+	if (optlen > MAX_MNTOPT_STR)
+		return (-EINVAL);
+
+	newopts = malloc(optlen);
+	if (opts == NULL || newopts == NULL) {
+		if (opts)
+			free(opts);
+		if (newopts)
+			free(newopts);
+		return (-EINVAL);
+	}
+	newopts[0] = '\0';
+
+	while (*opts) {
+		invalid = 0;
+		saveopt = opts;
+		switch (getsubopt(&opts, optlist, &val)) {
+		case OPT_RO:
+			*mntflags |= MS_RDONLY;
+			break;
+		case OPT_RW:
+			*mntflags &= ~(MS_RDONLY);
+			break;
+		case OPT_QUOTA:
+		case OPT_NOQUOTA:
+			break;
+		case OPT_SOFT:
+			args->flags |= NFSMNT_SOFT;
+			args->flags &= ~(NFSMNT_SEMISOFT);
+			break;
+		case OPT_SEMISOFT:
+			args->flags |= NFSMNT_SOFT;
+			args->flags |= NFSMNT_SEMISOFT;
+			break;
+		case OPT_HARD:
+			args->flags &= ~(NFSMNT_SOFT);
+			args->flags &= ~(NFSMNT_SEMISOFT);
+			break;
+		case OPT_SUID:
+			*mntflags &= ~(MS_NOSUID);
+			break;
+		case OPT_NOSUID:
+			*mntflags |= MS_NOSUID;
+			break;
+		case OPT_GRPID:
+			args->flags |= NFSMNT_GRPID;
+			break;
+		case OPT_REMOUNT:
+			*mntflags |= MS_REMOUNT;
+			break;
+		case OPT_INTR:
+			args->flags |= NFSMNT_INT;
+			break;
+		case OPT_NOINTR:
+			args->flags &= ~(NFSMNT_INT);
+			break;
+		case OPT_NOAC:
+			args->flags |= NFSMNT_NOAC;
+			break;
+		case OPT_PORT:
+			if (convert_int(&num, val) != 0)
+				goto badopt;
+			nmdp->nmd_nfs_port = htons((ushort_t)num);
+			break;
+
+		case OPT_NOCTO:
+			args->flags |= NFSMNT_NOCTO;
+			break;
+
+		case OPT_RSIZE:
+			if (convert_int(&args->rsize, val) != 0)
+				goto badopt;
+			args->flags |= NFSMNT_RSIZE;
+			break;
+		case OPT_WSIZE:
+			if (convert_int(&args->wsize, val) != 0)
+				goto badopt;
+			args->flags |= NFSMNT_WSIZE;
+			break;
+		case OPT_TIMEO:
+			if (convert_int(&args->timeo, val) != 0)
+				goto badopt;
+			args->flags |= NFSMNT_TIMEO;
+			break;
+		case OPT_RETRANS:
+			if (convert_int(&args->retrans, val) != 0)
+				goto badopt;
+			args->flags |= NFSMNT_RETRANS;
+			break;
+		case OPT_ACTIMEO:
+			if (convert_int(&args->acregmax, val) != 0)
+				goto badopt;
+			args->acdirmin = args->acregmin = args->acdirmax
+			    = args->acregmax;
+			args->flags |= NFSMNT_ACDIRMAX;
+			args->flags |= NFSMNT_ACREGMAX;
+			args->flags |= NFSMNT_ACDIRMIN;
+			args->flags |= NFSMNT_ACREGMIN;
+			break;
+		case OPT_ACREGMIN:
+			if (convert_int(&args->acregmin, val) != 0)
+				goto badopt;
+			args->flags |= NFSMNT_ACREGMIN;
+			break;
+		case OPT_ACREGMAX:
+			if (convert_int(&args->acregmax, val) != 0)
+				goto badopt;
+			args->flags |= NFSMNT_ACREGMAX;
+			break;
+		case OPT_ACDIRMIN:
+			if (convert_int(&args->acdirmin, val) != 0)
+				goto badopt;
+			args->flags |= NFSMNT_ACDIRMIN;
+			break;
+		case OPT_ACDIRMAX:
+			if (convert_int(&args->acdirmax, val) != 0)
+				goto badopt;
+			args->flags |= NFSMNT_ACDIRMAX;
+			break;
+		case OPT_BG:
+			nmdp->nmd_bg++;
+			break;
+		case OPT_FG:
+			nmdp->nmd_bg = 0;
+			break;
+		case OPT_RETRY:
+			if (convert_int(&nmdp->nmd_retries, val) != 0)
+				goto badopt;
+			break;
+		case OPT_LLOCK:
+			args->flags |= NFSMNT_LLOCK;
+			break;
+		case OPT_POSIX:
+			nmdp->nmd_posix = 1;
+			break;
+		case OPT_VERS:
+			if (convert_int(&num, val) != 0)
+				goto badopt;
+			nmdp->nmd_nfsvers = (rpcvers_t)num;
+			break;
+		case OPT_PROTO:
+			if (val == NULL)
+				goto badopt;
+
+			nmdp->nmd_nfs_proto = (char *)malloc(strlen(val)+1);
+			if (!nmdp->nmd_nfs_proto)
+				return (-EINVAL);
+
+			(void) strncpy(nmdp->nmd_nfs_proto, val, strlen(val)+1);
+			break;
+
+		case OPT_NOPRINT:
+			args->flags |= NFSMNT_NOPRINT;
+			break;
+
+		case OPT_LARGEFILES:
+			largefiles = 1;
+			break;
+
+		case OPT_NOLARGEFILES:
+			free(optstr);
+			return (-EINVAL);
+
+		case OPT_SEC:
+			if (val == NULL)
+				return (-EINVAL);
+			/*
+			 * We initialize the nfs_sec struct as if we had the
+			 * basic /etc/nffssec.conf file.
+			 */
+			if (strcmp(val, "none") == 0) {
+				(void) strlcpy(nmdp->nmd_nfs_sec.sc_name,
+				    "none", MAX_NAME_LEN);
+				nmdp->nmd_nfs_sec.sc_nfsnum =
+				    nmdp->nmd_nfs_sec.sc_rpcnum = 0;
+			} else if (strcmp(val, "sys") == 0) {
+				(void) strlcpy(nmdp->nmd_nfs_sec.sc_name,
+				    "sys", MAX_NAME_LEN);
+				nmdp->nmd_nfs_sec.sc_nfsnum =
+				    nmdp->nmd_nfs_sec.sc_rpcnum = 1;
+			} else if (strcmp(val, "dh") == 0) {
+				(void) strlcpy(nmdp->nmd_nfs_sec.sc_name,
+				    "dh", MAX_NAME_LEN);
+				nmdp->nmd_nfs_sec.sc_nfsnum =
+				    nmdp->nmd_nfs_sec.sc_rpcnum = 3;
+			} else {
+				return (-EINVAL);
+			}
+			nmdp->nmd_sec_opt++;
+			break;
+
+		case OPT_DIRECTIO:
+			args->flags |= NFSMNT_DIRECTIO;
+			break;
+
+		case OPT_NODIRECTIO:
+			args->flags &= ~(NFSMNT_DIRECTIO);
+			break;
+
+		case OPT_XATTR:
+		case OPT_NOXATTR:
+			/*
+			 * VFS options; just need to get them into the
+			 * new mount option string and note we've seen them
+			 */
+			attrpref = 1;
+			break;
+		default:
+			invalid = 1;
+			break;
+		}
+		if (!invalid) {
+			if (newopts[0])
+				strcat(newopts, ",");
+			strcat(newopts, saveopt);
+		}
+	}
+	/* Default is to turn extended attrs on */
+	if (!attrpref) {
+		if (newopts[0])
+			strcat(newopts, ",");
+		strcat(newopts, MNTOPT_XATTR);
+	}
+	strcpy(mnt->mnt_mntopts, newopts);
+	free(newopts);
+	free(optstr);
+
+	/* ensure that only one secure mode is requested */
+	if (nmdp->nmd_sec_opt > 1)
+		return (-EINVAL);
+
+	/* ensure that the user isn't trying to get large files over V2 */
+	if (nmdp->nmd_nfsvers == NFS_VERSION && largefiles)
+		return (-EINVAL);
+
+	if (nmdp->nmd_nfsvers == NFS_V4 && nmdp->nmd_nfs_proto != NULL &&
+	    strncasecmp(nmdp->nmd_nfs_proto, NC_UDP, strlen(NC_UDP)) == 0)
+		return (-EINVAL);
+
+	return (0);
+
+badopt:
+	free(optstr);
+	return (-EINVAL);
+}
+
+/*
+ *  NFS project private API.
+ *
+ *  Free an sec_data structure.
+ *  Free the parts that nfs_clnt_secdata allocates.
+ */
+void
+nfs_free_secdata(sec_data_t *secdata)
+{
+	dh_k4_clntdata_t *dkdata;
+
+	if (!secdata)
+		return;
+
+	switch (secdata->rpcflavor) {
+		case AUTH_UNIX:
+		case AUTH_NONE:
+			break;
+
+		case AUTH_DES:
+			/* LINTED pointer alignment */
+			dkdata = (dh_k4_clntdata_t *)secdata->data;
+			if (dkdata) {
+				if (dkdata->netname)
+					free(dkdata->netname);
+				if (dkdata->syncaddr.buf)
+					free(dkdata->syncaddr.buf);
+				free(dkdata);
+			}
+			break;
+
+		default:
+			break;
+	}
+
+	free(secdata);
+}
+
+/*
+ *  Make an client side sec_data structure and fill in appropriate value
+ *  based on its rpc security flavor.
+ *
+ *  It is caller's responsibility to allocate space for seconfig_t,
+ *  and this routine will allocate space for the sec_data structure
+ *  and related data field.
+ *
+ *  Return the sec_data_t on success.
+ *  If fail, return NULL pointer.
+ */
+sec_data_t *
+nfs_clnt_secdata(seconfig_t *secp, char *hostname, struct knetconfig *knconf,
+    struct netbuf *syncaddr, int flags)
+{
+	char netname[MAXNETNAMELEN+1];
+	sec_data_t *secdata;
+	dh_k4_clntdata_t *dkdata;
+
+	secdata = malloc(sizeof (sec_data_t));
+	if (!secdata)
+		return (NULL);
+
+	(void) memset(secdata, 0, sizeof (sec_data_t));
+
+	secdata->secmod = secp->sc_nfsnum;
+	secdata->rpcflavor = secp->sc_rpcnum;
+	secdata->uid = secp->sc_uid;
+	secdata->flags = flags;
+
+	/*
+	 *  Now, fill in the information for client side secdata :
+	 *
+	 *  For AUTH_UNIX, AUTH_DES
+	 *  hostname can be in the form of
+	 *    nodename or
+	 *    nodename.domain
+	 */
+	switch (secp->sc_rpcnum) {
+		case AUTH_UNIX:
+		case AUTH_NONE:
+			secdata->data = NULL;
+			break;
+
+		case AUTH_DES:
+			if (!host2netname(netname, hostname, NULL))
+				goto err_out;
+
+			dkdata = malloc(sizeof (dh_k4_clntdata_t));
+			if (!dkdata)
+				goto err_out;
+
+			(void) memset((char *)dkdata, 0,
+			    sizeof (dh_k4_clntdata_t));
+			if ((dkdata->netname = strdup(netname)) == NULL)
+				goto err_out;
+
+			dkdata->netnamelen = strlen(netname);
+			dkdata->knconf = knconf;
+			dkdata->syncaddr = *syncaddr;
+			dkdata->syncaddr.buf = malloc(syncaddr->len);
+			if (dkdata->syncaddr.buf == NULL)
+				goto err_out;
+
+			(void) memcpy(dkdata->syncaddr.buf, syncaddr->buf,
+			    syncaddr->len);
+			secdata->data = (caddr_t)dkdata;
+			break;
+
+		default:
+			goto err_out;
+	}
+
+	return (secdata);
+
+err_out:
+	free(secdata);
+	return (NULL);
+}
+
+static int
+make_secure(struct nfs_args *args, char *hostname, struct netconfig *nconf,
+    rpcvers_t vers, nfs_mnt_data_t *nmdp)
+{
+	sec_data_t *secdata;
+	int flags;
+	struct netbuf *syncaddr = NULL;
+	struct nd_addrlist *retaddrs = NULL;
+	char netname[MAXNETNAMELEN+1];
+
+	/*
+	 * check to see if any secure mode is requested.
+	 * if not, use default security mode.
+	 */
+	if (!nmdp->nmd_sec_opt) {
+		/* AUTH_UNIX is the default. */
+		strlcpy(nmdp->nmd_nfs_sec.sc_name, "sys", MAX_NAME_LEN);
+		nmdp->nmd_nfs_sec.sc_nfsnum = 1;
+		args->flags |= NFSMNT_SECDEFAULT;
+	}
+
+	/*
+	 * Get the network address for the time service on the server.
+	 * If an RPC based time service is not available then try the
+	 * IP time service.
+	 *
+	 * This is for AUTH_DH processing. We will also pass down syncaddr
+	 * and netname for NFS V4 even if AUTH_DH is not requested right now.
+	 * NFS V4 does security negotiation in the kernel via SECINFO.
+	 * This information might be needed later in the kernel.
+	 */
+	flags = 0;
+	syncaddr = NULL;
+
+	if (nmdp->nmd_nfs_sec.sc_rpcnum == AUTH_DH || vers == NFS_V4) {
+		/*
+		 * If using nfsv4, we will not contact the remote RPCBINDer,
+		 * since it is possibly behind a firewall.
+		 */
+		if (vers != NFS_V4)
+			syncaddr = get_the_addr(hostname, RPCBPROG, RPCBVERS,
+			    nconf, 0, NULL, NULL, NULL, NULL);
+
+		if (syncaddr != NULL) {
+			/* for flags in sec_data */
+			flags |= AUTH_F_RPCTIMESYNC;
+		} else {
+			/*
+			 * TBD netdir_getbyname wants to lookup the timeserver
+			 * entry in the /etc/services file (but our libnsl to do
+			 * this won't work in Linux). That entry is:
+			 *	timed    525/udp    timeserver
+			 * Since we haven't implemented the emulation for
+			 * netdir_getbyname yet, we'll simply return an error.
+			 */
+			struct nd_hostserv hs;
+			int error;
+
+			hs.h_host = hostname;
+			hs.h_serv = "timserver";
+
+			/* XXX */
+			if (1)
+				return (-1);
+
+			error = netdir_getbyname(nconf, &hs, &retaddrs);
+
+			if (error != ND_OK &&
+			    (nmdp->nmd_nfs_sec.sc_rpcnum == AUTH_DH))
+				return (-1);
+
+			if (error == ND_OK)
+				syncaddr = retaddrs->n_addrs;
+
+			/*
+			 * For NFS_V4 if AUTH_DH is negotiated later in the
+			 * kernel thru SECINFO, it will need syncaddr
+			 * and netname data.
+			 */
+			if (vers == NFS_V4 && syncaddr &&
+			    host2netname(netname, hostname, NULL)) {
+				args->syncaddr = malloc(sizeof (struct netbuf));
+				args->syncaddr->buf = malloc(syncaddr->len);
+				(void) memcpy(args->syncaddr->buf,
+				    syncaddr->buf, syncaddr->len);
+				args->syncaddr->len = syncaddr->len;
+				args->syncaddr->maxlen = syncaddr->maxlen;
+				args->netname = strdup(netname);
+				args->flags |= NFSMNT_SECURE;
+			}
+		}
+	}
+
+	/*
+	 * For the initial chosen flavor (any flavor defined in nfssec.conf),
+	 * the data will be stored in the sec_data structure via
+	 * nfs_clnt_secdata() and be passed to the kernel via nfs_args_*
+	 * extended data structure.
+	 */
+	if (!(secdata = nfs_clnt_secdata(&nmdp->nmd_nfs_sec, hostname,
+	    args->knconf, syncaddr, flags))) {
+		if (flags & AUTH_F_RPCTIMESYNC) {
+			free(syncaddr->buf);
+			free(syncaddr);
+		} else if (retaddrs)
+			netdir_free((void *)retaddrs, ND_ADDRLIST);
+		return (-1);
+	}
+
+	NFS_ARGS_EXTB_secdata(args, secdata);
+	if (flags & AUTH_F_RPCTIMESYNC) {
+		free(syncaddr->buf);
+		free(syncaddr);
+	} else if (retaddrs)
+		netdir_free((void *)retaddrs, ND_ADDRLIST);
+	return (0);
+}
+
+/*
+ * Get the network address on "hostname" for program "prog"
+ * with version "vers" by using the nconf configuration data
+ * passed in.
+ *
+ * If the address of a netconfig pointer is null then
+ * information is not sufficient and no netbuf will be returned.
+ *
+ * Finally, ping the null procedure of that service.
+ *
+ * A similar routine is also defined in ../../autofs/autod_nfs.c.
+ * This is a potential routine to move to ../lib for common usage.
+ */
+static struct netbuf *
+get_the_addr(char *hostname, rpcprog_t prog, rpcvers_t vers,
+	struct netconfig *nconf, ushort_t port, struct t_info *tinfo,
+	caddr_t *fhp, char *fspath, err_ret_t *error)
+{
+	struct netbuf *nb = NULL;
+	struct t_bind *tbind = NULL;
+	CLIENT *cl = NULL;
+	int fd = -1;
+	AUTH *ah = NULL;
+	AUTH *new_ah = NULL;
+	struct rpc_err r_err;
+	enum clnt_stat rc;
+
+	if (nconf == NULL)
+		return (NULL);
+
+	if ((fd = t_open(nconf->nc_device, O_RDWR, tinfo)) == -1)
+		goto done;
+
+	/* LINTED pointer alignment */
+	if ((tbind = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR)) == NULL)
+		goto done;
+
+	if (vers == NFS_V4) {
+		/* SET_ERR_RET(error, ERR_NOHOST, 1); */
+		goto done;
+	}
+
+	if (rpcb_getaddr(prog, vers, nconf, &tbind->addr,
+	    hostname) == FALSE) {
+		goto done;
+	}
+
+	if (port) {
+		/* LINTED pointer alignment */
+		if (strcmp(nconf->nc_protofmly, NC_INET) == 0)
+			((struct sockaddr_in *)tbind->addr.buf)->sin_port
+			    = port;
+		else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0)
+			((struct sockaddr_in6 *)tbind->addr.buf)->sin6_port
+			    = port;
+
+	}
+
+	cl = clnt_tli_create(fd, nconf, &tbind->addr, prog, vers, 0, 0);
+	if (cl == NULL) {
+		/*
+		 * clnt_tli_create() returns either RPC_SYSTEMERROR,
+		 * RPC_UNKNOWNPROTO or RPC_TLIERROR. The RPC_TLIERROR translates
+		 * to "Misc. TLI error". This is not too helpful. Most likely
+		 * the connection to the remote server timed out, so this
+		 * error is at least less perplexing.
+		 * See: usr/src/cmd/rpcinfo/rpcinfo.c
+		 */
+		if (rpc_createerr.cf_stat == RPC_TLIERROR) {
+			SET_ERR_RET(error, ERR_RPCERROR, RPC_PMAPFAILURE);
+		} else {
+			SET_ERR_RET(error, ERR_RPCERROR, rpc_createerr.cf_stat);
+		}
+		goto done;
+	}
+
+	ah = authsys_create_default();
+	if (ah != NULL)
+		cl->cl_auth = ah;
+
+	/*
+	 * NULL procedures need not have an argument or
+	 * result param.
+	 */
+	if (vers == NFS_VERSION)
+		rc = wnlproc_null_2(NULL, NULL, cl);
+	else if (vers == NFS_V3)
+		rc = wnlproc3_null_3(NULL, NULL, cl);
+	else
+		rc = wnlproc4_null_4(NULL, NULL, cl);
+
+	if (rc != RPC_SUCCESS) {
+		clnt_geterr(cl, &r_err);
+		if (strcmp(nconf->nc_protofmly, NC_LOOPBACK) == 0) {
+			switch (r_err.re_status) {
+			case RPC_TLIERROR:
+			case RPC_CANTRECV:
+			case RPC_CANTSEND:
+				r_err.re_status = RPC_PROGVERSMISMATCH;
+				break;
+			default:
+				break;
+			}
+		}
+		SET_ERR_RET(error, ERR_RPCERROR, r_err.re_status);
+		goto done;
+	}
+
+	/*
+	 * Make a copy of the netbuf to return
+	 */
+	nb = (struct netbuf *)malloc(sizeof (*nb));
+	if (nb == NULL)
+		goto done;
+
+	*nb = tbind->addr;
+	nb->buf = (char *)malloc(nb->maxlen);
+	if (nb->buf == NULL) {
+		free(nb);
+		nb = NULL;
+		goto done;
+	}
+	(void) memcpy(nb->buf, tbind->addr.buf, tbind->addr.len);
+
+done:
+	if (cl) {
+		if (ah != NULL) {
+			if (new_ah != NULL)
+				AUTH_DESTROY(ah);
+			AUTH_DESTROY(cl->cl_auth);
+			cl->cl_auth = NULL;
+		}
+		clnt_destroy(cl);
+		cl = NULL;
+	}
+	if (tbind) {
+		t_free((char *)tbind, T_BIND);
+		tbind = NULL;
+	}
+	if (fd >= 0)
+		(void) t_close(fd);
+	return (nb);
+}
+
+static int
+check_nconf(struct netconfig *nconf, int nthtry, int *valid_proto)
+{
+	int	try_test = 0;
+	char	*proto = NULL;
+
+	if (nthtry == FIRST_TRY) {
+		try_test = ((nconf->nc_semantics == NC_TPI_COTS_ORD) ||
+		    (nconf->nc_semantics == NC_TPI_COTS));
+		proto = NC_TCP;
+	} else if (nthtry == SECOND_TRY) {
+		try_test = (nconf->nc_semantics == NC_TPI_CLTS);
+		proto = NC_UDP;
+	}
+
+	if (proto &&
+	    (strcmp(nconf->nc_protofmly, NC_INET) == 0 ||
+	    strcmp(nconf->nc_protofmly, NC_INET6) == 0) &&
+	    (strcmp(nconf->nc_proto, proto) == 0))
+		*valid_proto = TRUE;
+	else
+		*valid_proto = FALSE;
+
+	return (try_test);
+}
+
+static struct netconfig *
+netconfig_dup(struct netconfig *netconfigp)
+{
+	struct netconfig *nconf;
+
+	nconf = calloc(1, sizeof (struct netconfig));
+	if (nconf == NULL)
+		goto nomem;
+
+	if ((nconf->nc_netid = strdup(netconfigp->nc_netid)) == NULL)
+		goto nomem;
+
+	if ((nconf->nc_protofmly = strdup(netconfigp->nc_protofmly)) == NULL)
+		goto nomem;
+
+	if ((nconf->nc_proto = strdup(netconfigp->nc_proto)) == NULL)
+		goto nomem;
+
+	if ((nconf->nc_device = strdup(netconfigp->nc_device)) == NULL)
+		goto nomem;
+
+	nconf->nc_lookups = NULL;
+	nconf->nc_nlookups = netconfigp->nc_nlookups;
+	nconf->nc_flag = netconfigp->nc_flag;
+	nconf->nc_semantics = netconfigp->nc_semantics;
+	return (nconf);
+
+nomem:
+	if (nconf != NULL) {
+		free(nconf->nc_netid);
+		free(nconf->nc_protofmly);
+		free(nconf->nc_proto);
+		free(nconf->nc_device);
+		free(nconf);
+	}
+	return (NULL);
+}
+
+/*
+ * Get a network address on "hostname" for program "prog"
+ * with version "vers".  If the port number is specified (non zero)
+ * then try for a TCP/UDP transport and set the port number of the
+ * resulting IP address.
+ *
+ * If the address of a netconfig pointer was passed and
+ * if it's not null, use it as the netconfig otherwise
+ * assign the address of the netconfig that was used to
+ * establish contact with the service.
+ *
+ * "error" refers to a more descriptive term when get_addr fails
+ * and returns NULL: ERR_PROTO_NONE if no error introduced by
+ * -o proto option, ERR_NETPATH if error found in NETPATH
+ * environment variable, ERR_PROTO_INVALID if an unrecognized
+ * protocol is specified by user, and ERR_PROTO_UNSUPP for a
+ * recognized but invalid protocol (eg. ticlts, ticots, etc.).
+ * "error" is ignored if get_addr returns non-NULL result.
+ *
+ */
+static struct netbuf *
+get_addr(char *hostname, rpcprog_t prog, rpcvers_t vers,
+    struct netconfig **nconfp, char *proto, ushort_t port,
+    struct t_info *tinfo, caddr_t *fhp, char *fspath, err_ret_t *error)
+{
+	struct netbuf *nb = NULL;
+	struct netconfig *nconf = NULL;
+	int nci;
+	int nthtry = FIRST_TRY;
+	err_ret_t errsave_nohost, errsave_rpcerr;
+
+	SET_ERR_RET(&errsave_nohost, ERR_PROTO_NONE, 0);
+	SET_ERR_RET(&errsave_rpcerr, ERR_PROTO_NONE, 0);
+
+	SET_ERR_RET(error, ERR_PROTO_NONE, 0);
+
+	if (nconfp && *nconfp)
+		return (get_the_addr(hostname, prog, vers, *nconfp, port,
+		    tinfo, fhp, fspath, error));
+	/*
+	 * No nconf passed in.
+	 *
+	 * First search for COTS, second for CLTS unless proto
+	 * is specified.  When we retry, we reset the
+	 * netconfig list so that we would search the whole list
+	 * all over again.
+	 */
+
+	/*
+	 * If proto is specified, then only search for the match,
+	 * otherwise try COTS first, if failed, try CLTS.
+	 */
+	if (proto) {
+		/* no matching proto name */
+		SET_ERR_RET(error, ERR_PROTO_INVALID, 0);
+
+		for (nci = 0; nci < N_NETCONF_ENTS; nci++) {
+			nconf = &nca[nci];
+			if (strcmp(nconf->nc_netid, proto) != 0)
+				continue;
+
+			/* may be unsupported */
+			SET_ERR_RET(error, ERR_PROTO_UNSUPP, 0);
+
+			nb = get_the_addr(hostname, prog, vers, nconf, port,
+			    tinfo, fhp, fspath, error);
+			if (nb != NULL)
+				break;
+
+			/* nb is NULL - deal with errors */
+			if (error) {
+				if (error->error_type == ERR_NOHOST)
+					SET_ERR_RET(&errsave_nohost,
+					    error->error_type,
+					    error->error_value);
+				if (error->error_type == ERR_RPCERROR)
+					SET_ERR_RET(&errsave_rpcerr,
+					    error->error_type,
+					    error->error_value);
+			}
+
+			/* continue with same protocol selection */
+			continue;
+		} /* end of while */
+
+		if (nci >= N_NETCONF_ENTS)
+			goto done;
+
+		if (nb == NULL &&
+		    (nb = get_the_addr(hostname, prog, vers, nconf, port,
+		    tinfo, fhp, fspath, error)) == NULL)
+			goto done;
+	} else {
+retry:
+		SET_ERR_RET(error, ERR_NETPATH, 0);
+		for (nci = 0; nci < N_NETCONF_ENTS; nci++) {
+			int	valid_proto;
+
+			nconf = &nca[nci];
+			SET_ERR_RET(error, ERR_PROTO_NONE, 0);
+
+			if (check_nconf(nconf, nthtry, &valid_proto)) {
+				if (port != 0 && valid_proto != TRUE)
+					continue;
+
+				nb = get_the_addr(hostname, prog, vers, nconf,
+				    port, tinfo, fhp, fspath, error);
+				if (nb != NULL)
+					break;
+
+				/* nb is NULL - deal with errors */
+				if (error) {
+					if (error->error_type == ERR_NOHOST)
+						SET_ERR_RET(&errsave_nohost,
+						    error->error_type,
+						    error->error_value);
+
+					if (error->error_type == ERR_RPCERROR)
+						SET_ERR_RET(&errsave_rpcerr,
+						    error->error_type,
+						    error->error_value);
+				}
+
+				/*
+				 * Continue the same search path in the
+				 * netconfig db until no more matched
+				 * nconf.
+				 */
+			}
+		}
+
+		if (nci >= N_NETCONF_ENTS) {
+			if (++nthtry <= MNT_PREF_LISTLEN)
+				goto retry;
+			goto done;
+		}
+
+	}
+	SET_ERR_RET(error, ERR_PROTO_NONE, 0);
+
+	/*
+	 * Got nconf and nb. Now dup the netconfig structure
+	 * and return it thru nconfp.
+	 */
+	*nconfp = netconfig_dup(nconf);
+	if (*nconfp == NULL) {
+		free(nb);
+		nb = NULL;
+	}
+done:
+	if (nb == NULL) {
+		/*
+		 * Check the saved errors. The RPC error has *
+		 * precedence over the no host error.
+		 */
+		if (errsave_nohost.error_type != ERR_PROTO_NONE)
+			SET_ERR_RET(error, errsave_nohost.error_type,
+			    errsave_nohost.error_value);
+
+		if (errsave_rpcerr.error_type != ERR_PROTO_NONE)
+			SET_ERR_RET(error, errsave_rpcerr.error_type,
+			    errsave_rpcerr.error_value);
+	}
+
+	return (nb);
+}
+
+static int
+lx_nsl_set_sz_func()
+{
+	return (N_NETCONF_ENTS);
+}
+
+static struct netconfig *
+lx_get_ent_func(int pos)
+{
+	struct netconfig *nconf;
+
+	if (pos < 0 || pos >= N_NETCONF_ENTS)
+		return (NULL);
+
+	nconf = &nca[pos];
+	return (nconf);
+}
+
+/*
+ * get fhandle of remote path from server's mountd
+ */
+static int
+get_fh(struct nfs_args *args, char *fshost, char *fspath, int *versp,
+    struct netconfig **nconfp, ushort_t port, nfs_mnt_data_t *nmdp)
+{
+	struct fhstatus fhs;
+	struct mountres3 mountres3;
+	struct pathcnf p;
+	nfs_fh3 *fh3p;
+	struct timeval timeout = { 25, 0};
+	CLIENT *cl;
+	enum clnt_stat rpc_stat;
+	rpcvers_t outvers = 0;
+	rpcvers_t vers_to_try;
+	rpcvers_t vers_min = vers_min_default;
+	int count, i, *auths;
+
+	bzero(&fhs, sizeof (fhs));
+	bzero(&mountres3, sizeof (mountres3));
+	bzero(&p, sizeof (p));
+
+	switch (nmdp->nmd_nfsvers) {
+	case 2: /* version 2 specified try that only */
+		vers_to_try = MOUNTVERS_POSIX;
+		vers_min = MOUNTVERS;
+		break;
+	case 3: /* version 3 specified try that only */
+		vers_to_try = MOUNTVERS3;
+		vers_min = MOUNTVERS3;
+		break;
+	case 4: /* version 4 specified try that only */
+		/*
+		 * This assignment is in the wrong version sequence.
+		 * The above are MOUNT program and this is NFS
+		 * program.  However, it happens to work out since the
+		 * two don't collide for NFSv4.
+		 */
+		vers_to_try = NFS_V4;
+		vers_min = NFS_V4;
+		break;
+	default: /* no version specified, start with default */
+		/*
+		 * If the retry version is set, use that. This will
+		 * be set if the last mount attempt returned any other
+		 * besides an RPC error.
+		 */
+		if (nmdp->nmd_nfsretry_vers)
+			vers_to_try = nmdp->nmd_nfsretry_vers;
+		else {
+			vers_to_try = vers_max_default;
+			vers_min = vers_min_default;
+		}
+
+		break;
+	}
+
+	/*
+	 * In the case of version 4, just NULL proc the server since
+	 * there is no MOUNT program.  If this fails, then decrease
+	 * vers_to_try and continue on with regular MOUNT program
+	 * processing.
+	 */
+	if (vers_to_try == NFS_V4) {
+		int savevers = nmdp->nmd_nfsvers_to_use;
+		err_ret_t error;
+		int retval;
+		SET_ERR_RET(&error, ERR_PROTO_NONE, 0);
+
+		/* Let's hope for the best */
+		nmdp->nmd_nfsvers_to_use = NFS_V4;
+		retval = getaddr_nfs(args, fshost, nconfp,
+		    fspath, port, &error, vers_min == NFS_V4, nmdp);
+
+		if (retval == RET_OK) {
+			*versp = nmdp->nmd_nfsvers_to_use = NFS_V4;
+			nmdp->nmd_fstype = MNTTYPE_NFS4;
+			args->fh = strdup(fspath);
+			if (args->fh == NULL) {
+				*versp = nmdp->nmd_nfsvers_to_use = savevers;
+				return (RET_ERR);
+			}
+			return (RET_OK);
+		}
+		nmdp->nmd_nfsvers_to_use = savevers;
+
+		vers_to_try--;
+		/* If no more versions to try, let the user know. */
+		if (vers_to_try < vers_min)
+			return (retval);
+
+		/*
+		 * If we are here, there are more versions to try but
+		 * there has been an error of some sort.  If it is not
+		 * an RPC error (e.g. host unknown), we just stop and
+		 * return the error since the other versions would see
+		 * the same error as well.
+		 */
+		if (retval == RET_ERR && error.error_type != ERR_RPCERROR)
+			return (retval);
+	}
+
+	while ((cl = clnt_create_vers(fshost, MOUNTPROG, &outvers,
+	    vers_min, vers_to_try, NULL)) == NULL) {
+		if (rpc_createerr.cf_stat == RPC_UNKNOWNHOST)
+			return (RET_ERR);
+
+		/*
+		 * We don't want to downgrade version on lost packets
+		 */
+		if ((rpc_createerr.cf_stat == RPC_TIMEDOUT) ||
+		    (rpc_createerr.cf_stat == RPC_PMAPFAILURE))
+			return (RET_RETRY);
+
+		/*
+		 * back off and try the previous version - patch to the
+		 * problem of version numbers not being contigous and
+		 * clnt_create_vers failing (SunOS4.1 clients & SGI servers)
+		 * The problem happens with most non-Sun servers who
+		 * don't support mountd protocol #2. So, in case the
+		 * call fails, we re-try the call anyway.
+		 */
+		vers_to_try--;
+		if (vers_to_try < vers_min) {
+			if (rpc_createerr.cf_stat == RPC_PROGVERSMISMATCH)
+				return (RET_ERR);
+
+			return (RET_RETRY);
+		}
+	}
+	if (nmdp->nmd_posix && outvers < MOUNTVERS_POSIX) {
+		clnt_destroy(cl);
+		return (RET_ERR);
+	}
+
+	if (__clnt_bindresvport(cl) < 0) {
+		clnt_destroy(cl);
+		return (RET_RETRY);
+	}
+
+	if ((cl->cl_auth = authsys_create_default()) == NULL) {
+		clnt_destroy(cl);
+		return (RET_RETRY);
+	}
+
+	switch (outvers) {
+	case MOUNTVERS:
+	case MOUNTVERS_POSIX:
+		*versp = nmdp->nmd_nfsvers_to_use = NFS_VERSION;
+		rpc_stat = clnt_call(cl, MOUNTPROC_MNT, xdr_dirpath,
+		    (caddr_t)&fspath, xdr_fhstatus, (caddr_t)&fhs, timeout);
+		if (rpc_stat != RPC_SUCCESS) {
+			log_err("%s:%s: server not responding %s\n",
+			    fshost, fspath, clnt_sperror(cl, ""));
+			clnt_destroy(cl);
+			return (RET_RETRY);
+		}
+
+		if ((errno = fhs.fhs_status) != MNT_OK) {
+			clnt_destroy(cl);
+			return (RET_MNTERR);
+		}
+		args->fh = malloc(sizeof (fhs.fhstatus_u.fhs_fhandle));
+		if (args->fh == NULL)
+			return (RET_ERR);
+
+		memcpy((caddr_t)args->fh, (caddr_t)&fhs.fhstatus_u.fhs_fhandle,
+		    sizeof (fhs.fhstatus_u.fhs_fhandle));
+		if (!errno && nmdp->nmd_posix) {
+			rpc_stat = clnt_call(cl, MOUNTPROC_PATHCONF,
+			    xdr_dirpath, (caddr_t)&fspath, xdr_ppathcnf,
+			    (caddr_t)&p, timeout);
+			if (rpc_stat != RPC_SUCCESS) {
+				log_err("%s:%s: server not responding %s\n",
+				    fshost, fspath, clnt_sperror(cl, ""));
+				free(args->fh);
+				clnt_destroy(cl);
+				return (RET_RETRY);
+			}
+			if (_PC_ISSET(_PC_ERROR, p.pc_mask)) {
+				free(args->fh);
+				clnt_destroy(cl);
+				return (RET_ERR);
+			}
+			args->flags |= NFSMNT_POSIX;
+			args->pathconf = malloc(sizeof (p));
+			if (args->pathconf == NULL) {
+				free(args->fh);
+				clnt_destroy(cl);
+				return (RET_ERR);
+			}
+			memcpy((caddr_t)args->pathconf, (caddr_t)&p,
+			    sizeof (p));
+		}
+		break;
+
+	case MOUNTVERS3:
+		*versp = nmdp->nmd_nfsvers_to_use = NFS_V3;
+		rpc_stat = clnt_call(cl, MOUNTPROC_MNT, xdr_dirpath,
+		    (caddr_t)&fspath, xdr_mountres3, (caddr_t)&mountres3,
+		    timeout);
+		if (rpc_stat != RPC_SUCCESS) {
+			log_err("%s:%s: server not responding %s\n",
+			    fshost, fspath, clnt_sperror(cl, ""));
+			clnt_destroy(cl);
+			return (RET_RETRY);
+		}
+
+		/*
+		 * Assume here that most of the MNT3ERR_*
+		 * codes map into E* errors.
+		 */
+		if ((errno = mountres3.fhs_status) != MNT_OK) {
+			clnt_destroy(cl);
+			return (RET_MNTERR);
+		}
+
+		fh3p = (nfs_fh3 *)malloc(sizeof (*fh3p));
+		if (fh3p == NULL)
+			return (RET_ERR);
+
+		fh3p->fh3_length =
+		    mountres3.mountres3_u.mountinfo.fhandle.fhandle3_len;
+		(void) memcpy(fh3p->fh3_u.data,
+		    mountres3.mountres3_u.mountinfo.fhandle.fhandle3_val,
+		    fh3p->fh3_length);
+		args->fh = (caddr_t)fh3p;
+		nmdp->nmd_fstype = MNTTYPE_NFS3;
+
+		/*
+		 * Check the security flavor to be used.
+		 *
+		 * If "secure" or "sec=flavor" is a mount
+		 * option, check if the server supports the "flavor".
+		 * If the server does not support the flavor, return
+		 * error.
+		 *
+		 * If no mount option is given then look for default auth
+		 * (default auth entry in /etc/nfssec.conf) in the auth list
+		 * returned from server. If default auth not found, then use
+		 * the first supported security flavor (by the client) in the
+		 * auth list returned from the server.
+		 *
+		 */
+		auths =
+		    mountres3.mountres3_u.mountinfo.auth_flavors
+		    .auth_flavors_val;
+		count =
+		    mountres3.mountres3_u.mountinfo.auth_flavors
+		    .auth_flavors_len;
+
+		if (count <= 0) {
+			clnt_destroy(cl);
+			return (RET_ERR);
+		}
+
+		if (nmdp->nmd_sec_opt) {
+			for (i = 0; i < count; i++) {
+				if (auths[i] == nmdp->nmd_nfs_sec.sc_nfsnum)
+					break;
+			}
+			if (i == count)
+				goto autherr;
+		} else {
+			/* AUTH_UNIX is the default. */
+			strlcpy(nmdp->nmd_nfs_sec.sc_name, "sys", MAX_NAME_LEN);
+			nmdp->nmd_nfs_sec.sc_nfsnum = 1;
+		}
+		break;
+	default:
+		clnt_destroy(cl);
+		return (RET_ERR);
+	}
+
+	clnt_destroy(cl);
+	return (RET_OK);
+
+autherr:
+	clnt_destroy(cl);
+	return (RET_ERR);
+}
+
+/*
+ * Fill in the address for the server's NFS service and
+ * fill in a knetconfig structure for the transport that
+ * the service is available on.
+ */
+static int
+getaddr_nfs(struct nfs_args *args, char *fshost, struct netconfig **nconfp,
+    char *fspath, ushort_t port, err_ret_t *error, bool_t print_rpcerror,
+    nfs_mnt_data_t *nmdp)
+{
+	struct stat sb;
+	struct netconfig *nconf;
+	struct knetconfig *knconfp;
+	struct t_info tinfo;
+	err_ret_t addr_error;
+
+	SET_ERR_RET(error, ERR_PROTO_NONE, 0);
+	SET_ERR_RET(&addr_error, ERR_PROTO_NONE, 0);
+
+	args->addr = get_addr(fshost, NFS_PROGRAM, nmdp->nmd_nfsvers_to_use,
+	    nconfp, nmdp->nmd_nfs_proto, port, &tinfo, &args->fh, fspath,
+	    &addr_error);
+
+	if (args->addr == NULL) {
+		switch (addr_error.error_type) {
+		case 0:
+			break;
+		case ERR_RPCERROR:
+			if (!print_rpcerror)
+				/* no error print at this time */
+				break;
+			log_err("%s NFS service not available %s\n", fshost,
+			    clnt_sperrno(addr_error.error_value));
+			break;
+		case ERR_NETPATH:
+			log_err("%s: Error in NETPATH.\n", fshost);
+			break;
+		case ERR_PROTO_INVALID:
+			log_err("%s: NFS service does not recognize "
+			    "protocol: %s.\n", fshost, nmdp->nmd_nfs_proto);
+			break;
+		case ERR_PROTO_UNSUPP:
+			if (nmdp->nmd_nfsvers ||
+			    nmdp->nmd_nfsvers_to_use == NFS_VERSMIN) {
+				log_err("%s: NFS service does"
+				    " not support protocol: %s.\n",
+				    fshost, nmdp->nmd_nfs_proto);
+			}
+			break;
+		case ERR_NOHOST:
+			log_err("%s: %s\n", fshost, "Unknown host");
+			break;
+		default:
+			/* case ERR_PROTO_NONE falls through */
+			log_err("%s: NFS service not responding\n", fshost);
+			break;
+		}
+
+		SET_ERR_RET(error,
+		    addr_error.error_type, addr_error.error_value);
+		if (addr_error.error_type == ERR_PROTO_NONE)
+			return (RET_RETRY);
+		else if (addr_error.error_type == ERR_RPCERROR &&
+		    !IS_UNRECOVERABLE_RPC(addr_error.error_value)) {
+			return (RET_RETRY);
+		} else if (nmdp->nmd_nfsvers == 0 &&
+		    addr_error.error_type == ERR_PROTO_UNSUPP &&
+		    nmdp->nmd_nfsvers_to_use != NFS_VERSMIN) {
+			/*
+			 * If no version is specified, and the error is due
+			 * to an unsupported transport, then decrement the
+			 * version and retry.
+			 */
+			return (RET_RETRY);
+		} else
+			return (RET_ERR);
+	}
+	nconf = *nconfp;
+
+	if (stat(nconf->nc_device, &sb) < 0)
+		return (RET_ERR);
+
+	knconfp = (struct knetconfig *)malloc(sizeof (*knconfp));
+	if (!knconfp)
+		return (RET_ERR);
+
+	knconfp->knc_semantics = nconf->nc_semantics;
+	knconfp->knc_protofmly = nconf->nc_protofmly;
+	knconfp->knc_proto = nconf->nc_proto;
+	knconfp->knc_rdev = sb.st_rdev;
+
+	/* make sure we don't overload the transport */
+	if (tinfo.tsdu > 0 && tinfo.tsdu < NFS_MAXDATA + NFS_RPC_HDR) {
+		args->flags |= (NFSMNT_RSIZE | NFSMNT_WSIZE);
+		if (args->rsize == 0 || args->rsize > tinfo.tsdu - NFS_RPC_HDR)
+			args->rsize = tinfo.tsdu - NFS_RPC_HDR;
+		if (args->wsize == 0 || args->wsize > tinfo.tsdu - NFS_RPC_HDR)
+			args->wsize = tinfo.tsdu - NFS_RPC_HDR;
+	}
+
+	args->flags |= NFSMNT_KNCONF;
+	args->knconf = knconfp;
+	return (RET_OK);
+}
+
+static int
+retry(struct mnttab *mntp, int mntflags, nfs_mnt_data_t *nmdp)
+{
+	int delay = 5;
+	int count = nmdp->nmd_retries;
+	int r = -EAGAIN;
+	char *p;
+
+	if (nmdp->nmd_bg) {
+		if (fork() > 0)
+			return (0);
+	} else {
+		p = strchr(mntp->mnt_special, ':');
+		if (p != NULL)
+			*p = '\0';
+		log_err("%s: server not responding\n", mntp->mnt_special);
+		if (p != NULL)
+			*p = ':';
+	}
+
+	while (count--) {
+		err_ret_t retry_error;
+
+		if ((r = mount_nfs(mntp, mntflags, &retry_error, nmdp)) == 0)
+			return (0);
+
+		if (r != RET_RETRY)
+			break;
+
+		if (count > 0) {
+			(void) sleep(delay);
+			delay *= 2;
+			if (delay > 120)
+				delay = 120;
+		}
+		p = strchr(mntp->mnt_special, ':');
+		if (p != NULL)
+			*p = '\0';
+		log_err("%s: server not responding\n", mntp->mnt_special);
+		if (p != NULL)
+			*p = ':';
+	}
+
+	if (!nmdp->nmd_nfsretry_vers)
+		log_err("giving up on: %s\n", mntp->mnt_mountp);
+
+	if (r > 0)
+		r = -EAGAIN;
+	return (r);
+}
+
+static int
+append_opt(char *optstr, int len, char *k, char *v)
+{
+	int i;
+
+	for (i = 0; nmo_tab[i].nmo_lx_opt != NULL; i++) {
+		if (strcmp(k, nmo_tab[i].nmo_lx_opt) == 0) {
+			switch (nmo_tab[i].nmo_argtyp) {
+			case MOUNT_OPT_INVALID:
+				return (-EINVAL);
+
+			case MOUNT_OPT_PASTHRU:
+				if (*optstr != '\0')
+					(void) strlcat(optstr, ",", len);
+				if (v == NULL) {
+					(void) strlcat(optstr, k, len);
+				} else {
+					(void) strlcat(optstr, k, len);
+					(void) strlcat(optstr, "=", len);
+					(void) strlcat(optstr, v, len);
+				}
+				break;
+
+			case MOUNT_OPT_IGNORE:
+				break;
+
+			case MOUNT_OPT_TOKEN:
+				if (*optstr != '\0')
+					(void) strlcat(optstr, ",", len);
+				(void) strlcat(optstr,
+				    nmo_tab[i].nmo_il_opt, len);
+				break;
+
+			case MOUNT_OPT_HAS_ARG:
+				if (*optstr != '\0')
+					(void) strlcat(optstr, ",", len);
+				(void) strlcat(optstr,
+				    nmo_tab[i].nmo_il_opt, len);
+				(void) strlcat(optstr, "=", len);
+				(void) strlcat(optstr, v, len);
+				break;
+			}
+			break;
+		}
+	}
+
+	return (0);
+}
+
+static int
+get_nfs_kv(char *vs, char **kp, char **vp)
+{
+	char *p;
+
+	p = strchr(vs, '=');
+	if (p == NULL) {
+		*kp = vs;
+		return (1);
+	}
+
+	*vp = p + 1;
+	*p = '\0';
+	*kp = vs;
+	return (0);
+}
+
+/*
+ * Convert the Linux-specific opt string into an Illumos opt string. We also
+ * fix up the special string (host:/path) to use the address that the
+ * user-level mount code has looked up. This overwrites both the srcp special
+ * string and the mntopts string.
+ *
+ * example input string, given 'nolock' as the only user-level option:
+ *     nolock,vers=4,addr=127.0.0.1,clientaddr=0.0.0.0
+ */
+static int
+convert_nfs_arg_str(char *srcp, char *mntopts)
+{
+	char *key, *val, *p;
+	char tmpbuf[MAX_MNTOPT_STR];
+	char *tbp = tmpbuf;
+
+	(void) strlcpy(tmpbuf, mntopts, sizeof (tmpbuf));
+	*mntopts = '\0';
+
+	while ((p = strsep(&tbp, ",")) != NULL) {
+		int tok;
+
+		tok = get_nfs_kv(p, &key, &val);
+
+		if (tok == 0) {
+			if (strcmp(key, "addr") == 0) {
+				/*
+				 * The Linux user-level code looked up the
+				 * address of the NFS server. We need to
+				 * substitute that into the special string.
+				 */
+				char *pp;
+				char spec[MAXPATHLEN + LX_NMD_MAXHOSTNAMELEN
+				    + 1];
+
+				strlcpy(spec, srcp, sizeof (spec));
+				pp = strchr(spec, ':');
+				if (pp == NULL)
+					return (-EINVAL);
+
+				pp++;
+				(void) snprintf(srcp,
+				    MAXPATHLEN + LX_NMD_MAXHOSTNAMELEN + 1,
+				    "%s:%s", val, pp);
+			} else if (strcmp(key, "clientaddr") == 0) {
+				/*
+				 * Ignore, this is an artifact of the
+				 * user-level lx mount code.
+				 */
+			} else if (strcmp(key, "vers") == 0) {
+				/*
+				 * This should always be passed and is the
+				 * only vers we want to support.
+				 */
+				if (atoi(val) != 4)
+					return (-EINVAL);
+			} else {
+				int r;
+
+				r = append_opt(mntopts, MAX_MNTOPT_STR,
+				    key, val);
+				if (r != 0)
+					return (r);
+			}
+		} else {
+			int r;
+
+			r = append_opt(mntopts, MAX_MNTOPT_STR, key, val);
+			if (r != 0)
+				return (r);
+		}
+	}
+
+	return (0);
+}
+
+int
+lx_nfs_mount(char *srcp, char *mntp, char *fst, int lx_flags, char *opts)
+{
+	struct mnttab mnt;
+	int r;
+	int il_flags = 0;
+	err_ret_t retry_error;
+	nfs_mnt_data_t nmd;
+
+	_nsl_brand_set_hooks(lx_nsl_set_sz_func, lx_get_ent_func);
+
+	bzero(&nmd, sizeof (nmd));
+	nmd.nmd_retries = BIGRETRY;
+	nmd.nmd_fstype = MNTTYPE_NFS;
+
+	/*
+	 * This will modify the special string so that the hostname passed
+	 * in will be replaced with the host address that the user-land code
+	 * looked up. Thus the rest of the code down the mount_nfs path will
+	 * be working with that IP address in places were it references the
+	 * 'hostname'. This also converts the opts string so that we'll be
+	 * dealing with Illumos options after this.
+	 */
+	convert_nfs_arg_str(srcp, opts);
+
+	/* Convert some Linux flags to Illumos flags. */
+	if (lx_flags & LX_MS_RDONLY)
+		il_flags |= MS_RDONLY;
+	if (lx_flags & LX_MS_NOSUID)
+		il_flags |= MS_NOSUID;
+	if (lx_flags & LX_MS_REMOUNT)
+		il_flags |= MS_REMOUNT;
+
+	/*
+	 * Convert some Linux flags to Illumos option strings.
+	 */
+	if (lx_flags & LX_MS_STRICTATIME) {
+		/*
+		 * The "strictatime" mount option ensures that none of the
+		 * weaker atime-related mode options are in effect.
+		 */
+		lx_flags &= ~(LX_MS_RELATIME | LX_MS_NOATIME);
+	}
+	if ((lx_flags & LX_MS_NODEV) &&
+	    ((r = i_add_option("nodev", opts, MAX_MNTOPT_STR)) != 0))
+		return (r);
+	if ((lx_flags & LX_MS_NOEXEC) &&
+	    ((r = i_add_option("noexec", opts, MAX_MNTOPT_STR)) != 0))
+		return (r);
+	if ((lx_flags & LX_MS_NOATIME) &&
+	    ((r = i_add_option("noatime", opts, MAX_MNTOPT_STR)) != 0))
+		return (r);
+
+	mnt.mnt_special = srcp;
+	mnt.mnt_mountp = mntp;
+	mnt.mnt_mntopts = opts;
+
+	SET_ERR_RET(&retry_error, ERR_PROTO_NONE, 0);
+	r = mount_nfs(&mnt, il_flags, &retry_error, &nmd);
+
+	/* A negative errno return means we're done. */
+	if (r < 0)
+		return (r);
+
+	if (r == RET_RETRY && nmd.nmd_retries) {
+		/*
+		 * Check the error code from the last mount attempt. If it was
+		 * an RPC error, then retry as is. Otherwise we retry with the
+		 * nmd_nfsretry_vers set. It is set by decrementing
+		 * nmd_nfsvers_to_use.
+		 */
+		if (retry_error.error_type) {
+			if (retry_error.error_type != ERR_RPCERROR) {
+				nmd.nmd_nfsretry_vers =
+				    nmd.nmd_nfsvers_to_use =
+				    nmd.nmd_nfsvers_to_use - 1;
+				if (nmd.nmd_nfsretry_vers < NFS_VERSMIN)
+					return (-EAGAIN);
+			}
+		}
+
+		r = retry(&mnt, il_flags, &nmd);
+	}
+
+	/* Convert any positve error into a valid errno. */
+	if (r > 0)
+		return (-EAGAIN);
+
+	return (0);
+}
--- a/usr/src/lib/brand/lx/lx_brand/sys/lx_mount.h	Fri Apr 10 19:26:51 2015 +0000
+++ b/usr/src/lib/brand/lx/lx_brand/sys/lx_mount.h	Wed Apr 15 19:38:41 2015 +0000
@@ -35,6 +35,8 @@
 #include <rpc/rpc.h>
 #include <nfs/nfs.h>
 
+extern int lx_nfs_mount(char *, char *, char *, int, char *);
+
 /*
  * mount() is significantly different between Linux and Solaris.  The main
  * difference is between the set of flags.  Some flags on Linux can be
--- a/usr/src/lib/libnsl/common/mapfile-vers	Fri Apr 10 19:26:51 2015 +0000
+++ b/usr/src/lib/libnsl/common/mapfile-vers	Wed Apr 15 19:38:41 2015 +0000
@@ -21,6 +21,7 @@
 #
 # Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 # Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+# Copyright 2015 Joyent, Inc.
 #
 
 #
@@ -467,6 +468,7 @@
 
 SYMBOL_VERSION SUNWprivate_1.5 {
     global:
+ 	_nsl_brand_set_hooks;
 	clnt_create_service_timed;
 	inet_matchaddr;
 } SUNWprivate_1.4;
--- a/usr/src/lib/libnsl/netselect/netselect.c	Fri Apr 10 19:26:51 2015 +0000
+++ b/usr/src/lib/libnsl/netselect/netselect.c	Wed Apr 15 19:38:41 2015 +0000
@@ -22,6 +22,7 @@
 /*
  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
+ * Copyright 2015 Joyent, Inc.
  */
 
 /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
@@ -32,8 +33,6 @@
  * under license from the Regents of the University of California.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #include "mt.h"
 #include "../rpc/rpc_mt.h"		/* for MT declarations only */
 #include <rpc/types.h>
@@ -70,6 +69,9 @@
 
 extern const char __nsl_dom[];
 
+static int (*brand_get_sz)(void) = NULL;
+static struct netconfig *(*brand_get_net_ent)(int) = NULL;
+
 /*
  *	Static global variables used by the library procedures:
  *
@@ -255,6 +257,14 @@
 	netconfig_free(netp);
 }
 
+void
+_nsl_brand_set_hooks(int (*set_sz_func)(void),
+    struct netconfig *(*get_ent_func)(int))
+{
+	brand_get_sz = set_sz_func;
+	brand_get_net_ent = get_ent_func;
+}
+
 /*
  *	getnetlist() reads the netconfig file and creates a
  *	NULL-terminated list of entries.
@@ -265,52 +275,73 @@
 static struct netconfig **
 getnetlist(void)
 {
-	char line[BUFSIZ];	/* holds each line of NETCONFIG */
-	FILE *fp;		/* file stream for NETCONFIG */
+	FILE *fp = NULL;	/* file stream for NETCONFIG */
 	struct netconfig **listpp; /* the beginning of the netconfig list */
 	struct netconfig **tpp;	/* used to traverse the netconfig list */
 	int count;		/* the number of entries in file */
 
-	if ((fp = fopen(NETCONFIG, "rF")) == NULL) {
-		nc_error = NC_OPENFAIL;
-		return (NULL);
-	}
+	if (brand_get_sz != NULL) {
+		count = brand_get_sz();
+	} else {
+		char line[BUFSIZ];	/* holds each line of NETCONFIG */
 
-	count = 0;
-	while (fgets(line, BUFSIZ, fp)) {
-		if (!(blank(line) || comment(line))) {
-			++count;
+		if ((fp = fopen(NETCONFIG, "rF")) == NULL) {
+			nc_error = NC_OPENFAIL;
+			return (NULL);
 		}
+
+		count = 0;
+		while (fgets(line, BUFSIZ, fp)) {
+			if (!(blank(line) || comment(line))) {
+				++count;
+			}
+		}
+		rewind(fp);
 	}
-	rewind(fp);
 
 	if (count == 0) {
 		nc_error = NC_NOTFOUND;
-		(void) fclose(fp);
+		if (fp != NULL)
+			(void) fclose(fp);
 		return (NULL);
 	}
+
 	if ((listpp = malloc((count + 1) *
 	    sizeof (struct netconfig *))) == NULL) {
 		nc_error = NC_NOMEM;
-		(void) fclose(fp);
+		if (fp != NULL)
+			(void) fclose(fp);
 		return (NULL);
 	}
 
-	/*
-	 *	The following loop fills in the list (loops until
-	 *	fgetnetconfig() returns a NULL) and counts the
-	 *	number of entries placed in the list.  Note that
-	 *	when the loop is completed, the last entry in the
-	 *	list will contain a NULL (signifying the end of
-	 *	the list).
-	 */
-	linenum = 0;
-	for (tpp = listpp; *tpp = fgetnetconfig(fp, NULL); tpp++)
-		;
-	(void) fclose(fp);
+	if (brand_get_net_ent != NULL) {
+		int i;
 
-	if (nc_error != NC_NOMOREENTRIES) /* Something is screwed up */
-		netlist_free(&listpp);
+		tpp = listpp;
+		for (i = 0; i < count; i++) {
+			*tpp = brand_get_net_ent(i);
+			tpp++;
+		}
+		*tpp = NULL;
+		nc_error = NC_NOMOREENTRIES;
+	} else {
+		/*
+		 *	The following loop fills in the list (loops until
+		 *	fgetnetconfig() returns a NULL) and counts the
+		 *	number of entries placed in the list.  Note that
+		 *	when the loop is completed, the last entry in the
+		 *	list will contain a NULL (signifying the end of
+		 *	the list).
+		 */
+		linenum = 0;
+		for (tpp = listpp; *tpp = fgetnetconfig(fp, NULL); tpp++)
+			;
+		(void) fclose(fp);
+
+		if (nc_error != NC_NOMOREENTRIES) /* Something is screwed up */
+			netlist_free(&listpp);
+	}
+
 	return (listpp);
 }
 
--- a/usr/src/uts/common/sys/netconfig.h	Fri Apr 10 19:26:51 2015 +0000
+++ b/usr/src/uts/common/sys/netconfig.h	Wed Apr 15 19:38:41 2015 +0000
@@ -28,6 +28,7 @@
  *
  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
+ * Copyright 2015 Joyent, Inc.
  */
 
 #ifndef	_SYS_NETCONFIG_H
@@ -147,6 +148,8 @@
 extern struct netconfig *getnetpath(void *);
 extern void		nc_perror(const char *);
 extern char		*nc_sperror(void);
+extern void		_nsl_brand_set_hooks(int (*)(void),
+			    struct netconfig *(*)(int));
 
 #ifdef	__cplusplus
 }