view usr/src/cmd/fs.d/nfs/mount/mount.c @ 13779:bf40125f4b37

3063 many instances of unlike enum comparison Reviewed by: Robert Mustacchi <rm@joyent.com> Approved by: Garrett D'Amore <garrett@damore.org>
author Richard Lowe <richlowe@richlowe.net>
date Sun, 08 Jul 2012 03:19:56 +0100
parents fcc1e406c13f
children
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * 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.
 */

/*
 * nfs mount
 */

#define	NFSCLIENT
#include <locale.h>
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <stdarg.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/param.h>
#include <rpc/rpc.h>
#include <errno.h>
#include <sys/stat.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 <syslog.h>
#include <fslib.h>
#include <deflt.h>
#include <sys/wait.h>
#include "replica.h"
#include <netinet/in.h>
#include <nfs/nfs_sec.h>
#include <rpcsvc/daemon_utils.h>
#include <priv.h>
#include <tsol/label.h>
#include "nfs_subr.h"
#include "webnfs.h"
#include <rpcsvc/nfs4_prot.h>
#include <limits.h>
#include <libscf.h>
#include <libshare.h>
#include "smfcfg.h"

#include <nfs/nfssys.h>
extern int _nfssys(enum nfssys_op, void *);

#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) \
	if (errst) { \
		(errst)->error_type = etype; \
		(errst)->error_value = eval; \
	}

/* 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 *);
extern char *nfs_get_qop_name();
extern AUTH * nfs_create_ah();
extern enum snego_stat nfs_sec_nego();

static void usage(void);
static int retry(struct mnttab *, int);
static int set_args(int *, struct nfs_args *, char *, struct mnttab *);
static int get_fh_via_pub(struct nfs_args *, char *, char *, bool_t, bool_t,
	int *, struct netconfig **, ushort_t);
static int get_fh(struct nfs_args *, char *, char *, int *, bool_t,
	struct netconfig **, ushort_t);
static int make_secure(struct nfs_args *, char *, struct netconfig *,
	bool_t, rpcvers_t);
static int mount_nfs(struct mnttab *, int, err_ret_t *);
static int getaddr_nfs(struct nfs_args *, char *, struct netconfig **,
		    bool_t, char *, ushort_t, err_ret_t *, bool_t);
static void pr_err(const char *fmt, ...);
static void usage(void);
static struct netbuf *get_addr(char *, rpcprog_t, rpcvers_t,
	struct netconfig **, char *, ushort_t, struct t_info *,
	caddr_t *, bool_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 *,
	bool_t, char *, err_ret_t *);

extern int self_check(char *);

static void read_default(void);

static char typename[64];

static int bg = 0;
static int backgrounded = 0;
static int posix = 0;
static int retries = BIGRETRY;
static ushort_t nfs_port = 0;
static char *nfs_proto = NULL;

static int mflg = 0;
static int Oflg = 0;	/* Overlay mounts */
static int qflg = 0;	/* quiet - don't print warnings on bad options */

static char *fstype = MNTTYPE_NFS;

static seconfig_t nfs_sec;
static int sec_opt = 0;	/* any security option ? */
static bool_t snego_done;
static void sigusr1(int);

extern void set_nfsv4_ephemeral_mount_to(void);

/*
 * list of support services needed
 */
static char	*service_list[] = { STATD, LOCKD, NULL };
static char	*service_list_v4[] = { STATD, LOCKD, NFS4CBD, NFSMAPID, NULL };

/*
 * These two variables control the NFS version number to be used.
 *
 * 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.
 *
 * 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.
 *
 * nfsretry_vers is the version number set when we retry the mount
 * command with the version decremented from nfsvers_to_use.
 * nfsretry_vers is set from nfsvers_to_use when we retry the mount
 * for errors other than RPC errors; it helps un know why we are
 * retrying. It is an indication that the retry is due to
 * non-RPC errors.
 */
static rpcvers_t nfsvers = 0;
static rpcvers_t nfsvers_to_use = 0;
static rpcvers_t nfsretry_vers = 0;

/*
 * There 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 and
 * these may be reset if NFS SMF is configured to do so.
 */
static rpcvers_t vers_max_default = NFS_VERSMAX_DEFAULT;
static rpcvers_t vers_min_default = NFS_VERSMIN_DEFAULT;

/*
 * This variable controls whether to try the public file handle.
 */
static bool_t public_opt;

int
main(int argc, char *argv[])
{
	struct mnttab mnt;
	extern char *optarg;
	extern int optind;
	char optbuf[MAX_MNTOPT_STR];
	int ro = 0;
	int r;
	int c;
	char *myname;
	err_ret_t retry_error;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN	"SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	myname = strrchr(argv[0], '/');
	myname = myname ? myname + 1 : argv[0];
	(void) snprintf(typename, sizeof (typename), "%s %s",
	    MNTTYPE_NFS, myname);
	argv[0] = typename;

	mnt.mnt_mntopts = optbuf;
	(void) strcpy(optbuf, "rw");

	/*
	 * Set options
	 */
	while ((c = getopt(argc, argv, "ro:mOq")) != EOF) {
		switch (c) {
		case 'r':
			ro++;
			break;
		case 'o':
			if (strlen(optarg) >= MAX_MNTOPT_STR) {
				pr_err(gettext("option string too long"));
				return (RET_ERR);
			}
			(void) strcpy(mnt.mnt_mntopts, optarg);
#ifdef LATER					/* XXX */
			if (strstr(optarg, MNTOPT_REMOUNT)) {
				/*
				 * If remount is specified, only rw is allowed.
				 */
				if ((strcmp(optarg, MNTOPT_REMOUNT) != 0) &&
				    (strcmp(optarg, "remount,rw") != 0) &&
				    (strcmp(optarg, "rw,remount") != 0)) {
					pr_err(gettext("Invalid options\n"));
					exit(RET_ERR);
				}
			}
#endif /* LATER */				/* XXX */
			break;
		case 'm':
			mflg++;
			break;
		case 'O':
			Oflg++;
			break;
		case 'q':
			qflg++;
			break;
		default:
			usage();
			exit(RET_ERR);
		}
	}
	if (argc - optind != 2) {
		usage();
		exit(RET_ERR);
	}

	mnt.mnt_special = argv[optind];
	mnt.mnt_mountp = argv[optind+1];

	if (!priv_ineffect(PRIV_SYS_MOUNT) ||
	    !priv_ineffect(PRIV_NET_PRIVADDR)) {
		pr_err(gettext("insufficient privileges\n"));
		exit(RET_ERR);
	}

	/*
	 * On a labeled system, allow read-down nfs mounts if privileged
	 * (PRIV_NET_MAC_AWARE) to do so.  Otherwise, ignore the error
	 * and "mount equal label only" behavior will result.
	 */
	if (is_system_labeled())
		(void) setpflags(NET_MAC_AWARE, 1);

	/*
	 * Read the NFS SMF defaults to see if the min/max versions have
	 * been set and therefore would override the encoded defaults.
	 * Then check to make sure that if they were set that the
	 * values are reasonable.
	 */
	read_default();
	if (vers_min_default > vers_max_default ||
	    vers_min_default < NFS_VERSMIN ||
	    vers_max_default > NFS_VERSMAX) {
		pr_err("%s\n%s %s\n",
		    gettext("Incorrect configuration of client\'s"),
		    gettext("client_versmin or client_versmax"),
		    gettext("is either out of range or overlaps."));
	}

	SET_ERR_RET(&retry_error, ERR_PROTO_NONE, 0);
	r = mount_nfs(&mnt, ro, &retry_error);
	if (r == RET_RETRY && 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
		 * nfsretry_vers set. It is set by decrementing nfsvers_to_use.
		 * If we are retrying with nfsretry_vers then we don't print any
		 * retry messages, since we are not retrying due to an RPC
		 * error.
		 */
		if (retry_error.error_type) {
			if (retry_error.error_type != ERR_RPCERROR) {
				nfsretry_vers = nfsvers_to_use =
				    nfsvers_to_use - 1;
				if (nfsretry_vers < NFS_VERSMIN)
					return (r);
			}
		}

		r = retry(&mnt, ro);
	}
	/*
	 * exit(r);
	 */
	return (r);
}

static void
pr_err(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	if (backgrounded != 0) {
		(void) vsyslog(LOG_ERR, fmt, ap);
	} else {
		(void) fprintf(stderr, "%s: ", typename);
		(void) vfprintf(stderr, fmt, ap);
		(void) fflush(stderr);
	}
	va_end(ap);
}

static void
usage()
{
	(void) fprintf(stderr,
	    gettext("Usage: nfs mount [-r] [-o opts] [server:]path dir\n"));
	exit(RET_ERR);
}

static int
mount_nfs(struct mnttab *mntp, int ro, err_ret_t *retry_error)
{
	struct nfs_args *args = NULL, *argp = NULL, *prev_argp = NULL;
	struct netconfig *nconf = NULL;
	struct replica *list = NULL;
	int mntflags = 0;
	int i, r, n;
	int oldvers = 0, vers = 0;
	int last_error = RET_OK;
	int replicated = 0;
	char *p;
	bool_t url;
	bool_t use_pubfh;
	char *special = NULL;
	char *oldpath = NULL;
	char *newpath = NULL;
	char *service;
	pid_t pi;
	struct flock f;
	char *saveopts = NULL;
	char **sl = NULL;

	mntp->mnt_fstype = MNTTYPE_NFS;

	if (ro) {
		mntflags |= MS_RDONLY;
		/* convert "rw"->"ro" */
		if (p = strstr(mntp->mnt_mntopts, "rw")) {
			if (*(p+2) == ',' || *(p+2) == '\0')
				*(p+1) = 'o';
		}
	}

	if (Oflg)
		mntflags |= MS_OVERLAY;

	list = parse_replica(mntp->mnt_special, &n);
	if (list == NULL) {
		if (n < 0)
			pr_err(gettext("nfs file system; use [host:]path\n"));
		else
			pr_err(gettext("no memory\n"));
		return (RET_ERR);
	}

	replicated = (n > 1);

	/*
	 * There are some free() calls at the bottom of this loop, so be
	 * careful about adding continue statements.
	 */
	for (i = 0; i < n; i++) {
		char *path;
		char *host;
		ushort_t port;

		argp = (struct nfs_args *)malloc(sizeof (*argp));
		if (argp == NULL) {
			pr_err(gettext("no memory\n"));
			last_error = RET_ERR;
			goto out;
		}
		memset(argp, 0, sizeof (*argp));

		memset(&nfs_sec, 0, sizeof (nfs_sec));
		sec_opt = 0;
		use_pubfh = FALSE;
		url = FALSE;
		port = 0;
		snego_done = FALSE;

		/*
		 * Looking for resources of the form
		 *	nfs://server_host[:port_number]/path_name
		 */
		if (strcmp(list[i].host, "nfs") == 0 && strncmp(list[i].path,
		    "//", 2) == 0) {
			char *sport, *cb;
			url = TRUE;
			oldpath = strdup(list[i].path);
			if (oldpath == NULL) {
				pr_err(gettext("memory allocation failure\n"));
				last_error = RET_ERR;
				goto out;
			}
			host = list[i].path+2;
			path = strchr(host, '/');

			if (path == NULL) {
				pr_err(gettext(
				    "illegal nfs url syntax\n"));
				last_error = RET_ERR;
				goto out;
			}

			*path = '\0';
			if (*host == '[') {
				cb = strchr(host, ']');
				if (cb == NULL) {
					pr_err(gettext(
					    "illegal nfs url syntax\n"));
					last_error = RET_ERR;
					goto out;
				} else {
					*cb = '\0';
					host++;
					cb++;
					if (*cb == ':')
						port = htons((ushort_t)
						    atoi(cb+1));
				}
			} else {
				sport = strchr(host, ':');

				if (sport != NULL && sport < path) {
					*sport = '\0';
					port = htons((ushort_t)atoi(sport+1));
				}
			}

			path++;
			if (*path == '\0')
				path = ".";

		} else {
			host = list[i].host;
			path = list[i].path;
		}

		if (r = set_args(&mntflags, argp, host, mntp)) {
			last_error = r;
			goto out;
		}

		if (public_opt == TRUE)
			use_pubfh = TRUE;

		if (port == 0) {
			port = nfs_port;
		} else if (nfs_port != 0 && nfs_port != port) {
			pr_err(gettext(
			    "port (%u) in nfs URL not the same"
			    " as port (%u) in port option\n"),
			    (unsigned int)ntohs(port),
			    (unsigned int)ntohs(nfs_port));
			last_error = RET_ERR;
			goto out;
		}


		if (replicated && !(mntflags & MS_RDONLY)) {
			pr_err(gettext(
			    "replicated mounts must be read-only\n"));
			last_error = RET_ERR;
			goto out;
		}

		if (replicated && (argp->flags & NFSMNT_SOFT)) {
			pr_err(gettext(
			    "replicated mounts must not be soft\n"));
			last_error = RET_ERR;
			goto out;
		}

		oldvers = vers;
		nconf = NULL;

		r = RET_ERR;

		/*
		 * If -o public was specified, and/or a URL was specified,
		 * then try the public file handle method.
		 */
		if ((use_pubfh == TRUE) || (url == TRUE)) {
			r = get_fh_via_pub(argp, host, path, url, use_pubfh,
			    &vers, &nconf, port);

			if (r != RET_OK) {
				/*
				 * If -o public was specified, then return the
				 * error now.
				 */
				if (use_pubfh == TRUE) {
					last_error = r;
					goto out;
				}
			} else
				use_pubfh = TRUE;
			argp->flags |= NFSMNT_PUBLIC;
		}

		if ((r != RET_OK) || (vers == NFS_V4)) {
			bool_t loud_on_mnt_err;

			/*
			 * This can happen if -o public is not specified,
			 * special is a URL, and server doesn't support
			 * public file handle.
			 */
			if (url) {
				URLparse(path);
			}

			/*
			 * If the path portion of the URL didn't have
			 * a leading / then there is good possibility
			 * that a mount without a leading slash will
			 * fail.
			 */
			if (url == TRUE && *path != '/')
				loud_on_mnt_err = FALSE;
			else
				loud_on_mnt_err = TRUE;

			r = get_fh(argp, host, path, &vers,
			    loud_on_mnt_err, &nconf, port);

			if (r != RET_OK) {

				/*
				 * If there was no leading / and the path was
				 * derived from a URL, then try again
				 * with a leading /.
				 */
				if ((r == RET_MNTERR) &&
				    (loud_on_mnt_err == FALSE)) {

					newpath = malloc(strlen(path)+2);

					if (newpath == NULL) {
						pr_err(gettext("memory "
						    "allocation failure\n"));
						last_error = RET_ERR;
						goto out;
					}

					strcpy(newpath, "/");
					strcat(newpath, path);

					r = get_fh(argp, host, newpath, &vers,
					    TRUE, &nconf, port);

					if (r == RET_OK)
						path = newpath;
				}

				/*
				 * map exit code back to RET_ERR.
				 */
				if (r == RET_MNTERR)
					r = RET_ERR;

				if (r != RET_OK) {

					if (replicated) {
						if (argp->fh)
							free(argp->fh);
						if (argp->pathconf)
							free(argp->pathconf);
						free(argp);
						goto cont;
					}

					last_error = r;
					goto out;
				}
			}
		}

		if (oldvers && vers != oldvers) {
			pr_err(
			    gettext("replicas must have the same version\n"));
			last_error = RET_ERR;
			goto out;
		}

		/*
		 * decide whether to use remote host's
		 * lockd or do local locking
		 */
		if (!(argp->flags & NFSMNT_LLOCK) && vers == NFS_VERSION &&
		    remote_lock(host, argp->fh)) {
			(void) fprintf(stderr, gettext(
			    "WARNING: No network locking on %s:%s:"),
			    host, path);
			(void) fprintf(stderr, gettext(
			    " contact admin to install server change\n"));
			argp->flags |= NFSMNT_LLOCK;
		}

		if (self_check(host))
			argp->flags |= NFSMNT_LOOPBACK;

		if (use_pubfh == FALSE) {
			/*
			 * 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;
				if (r = getaddr_nfs(argp, host, &nconf,
				    FALSE, path, port, retry_error,
				    TRUE)) {
						last_error = r;
						goto out;
				}
			}
		}

		if (make_secure(argp, host, nconf, use_pubfh, vers) < 0) {
			last_error = RET_ERR;
			goto out;
		}

		if ((url == TRUE) && (use_pubfh == FALSE)) {
			/*
			 * Convert the special from
			 *	nfs://host/path
			 * to
			 *	host:path
			 */
			if (convert_special(&special, host, oldpath, path,
			    mntp->mnt_special) == -1) {
				(void) fprintf(stderr, gettext(
				    "could not convert URL nfs:%s to %s:%s\n"),
				    oldpath, host, path);
				last_error = RET_ERR;
				goto out;
			} else {
				mntp->mnt_special = special;
			}
		}

		if (prev_argp == NULL)
			args = argp;
		else
			prev_argp->nfs_ext_u.nfs_extB.next = argp;
		prev_argp = argp;

cont:
		if (oldpath != NULL) {
			free(oldpath);
			oldpath = NULL;
		}

		if (newpath != NULL) {
			free(newpath);
			newpath = NULL;
		}
	}

	argp = NULL;

	if (args == NULL) {
		last_error = RET_RETRY;
		goto out;
	}

	/* Determine which services are appropriate for the NFS version */
	if (strcmp(fstype, MNTTYPE_NFS4) == 0)
		sl = service_list_v4;
	else
		sl = service_list;

	/*
	 * enable services as needed.
	 */
	_check_services(sl);

	mntflags |= MS_DATA | MS_OPTIONSTR;

	if (mflg)
		mntflags |= MS_NOMNTTAB;

	if (!qflg)
		saveopts = strdup(mntp->mnt_mntopts);

	/*
	 * And make sure that we have the ephemeral mount_to
	 * set for this zone.
	 */
	set_nfsv4_ephemeral_mount_to();

	if (mount(mntp->mnt_special, mntp->mnt_mountp, mntflags, fstype, args,
	    sizeof (*args), mntp->mnt_mntopts, MAX_MNTOPT_STR) < 0) {
		if (errno != ENOENT) {
			pr_err(gettext("mount: %s: %s\n"),
			    mntp->mnt_mountp, strerror(errno));
		} else {
			struct stat sb;
			if (stat(mntp->mnt_mountp, &sb) < 0 && errno == ENOENT)
				pr_err(gettext("mount: %s: %s\n"),
				    mntp->mnt_mountp, strerror(ENOENT));
			else
				pr_err("%s: %s\n", mntp->mnt_special,
				    strerror(ENOENT));
		}

		last_error = RET_ERR;
		goto out;
	}

	if (!qflg && saveopts != NULL) {
		cmp_requested_to_actual_options(saveopts, mntp->mnt_mntopts,
		    mntp->mnt_special, mntp->mnt_mountp);
	}

out:
	if (saveopts != NULL)
		free(saveopts);
	if (special != NULL)
		free(special);
	if (oldpath != NULL)
		free(oldpath);
	if (newpath != NULL)
		free(newpath);

	free_replica(list, n);

	if (argp != NULL) {
		/*
		 * If we had a new entry which was not added to the
		 * list yet, then add it now that it can be freed.
		 */
		if (prev_argp == NULL)
			args = argp;
		else
			prev_argp->nfs_ext_u.nfs_extB.next = argp;
	}
	argp = args;
	while (argp != NULL) {
		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);
		prev_argp = argp;
		argp = argp->nfs_ext_u.nfs_extB.next;
		free(prev_argp);
	}

	return (last_error);
}

/*
 * 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)
{
	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) {
		pr_err(gettext("option string too long"));
		return (RET_ERR);
	}
	newopts = malloc(optlen);
	if (opts == NULL || newopts == NULL) {
		pr_err(gettext("no memory"));
		if (opts)
			free(opts);
		if (newopts)
			free(newopts);
		return (RET_ERR);
	}
	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;
			nfs_port = htons((ushort_t)num);
			break;

		case OPT_SECURE:
			if (nfs_getseconfig_byname("dh", &nfs_sec)) {
				pr_err(gettext("can not get \"dh\" from %s\n"),
				    NFSSEC_CONF);
				goto badopt;
			}
			sec_opt++;
			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:
			bg++;
			break;
		case OPT_FG:
			bg = 0;
			break;
		case OPT_RETRY:
			if (convert_int(&retries, val) != 0)
				goto badopt;
			break;
		case OPT_LLOCK:
			args->flags |= NFSMNT_LLOCK;
			break;
		case OPT_POSIX:
			posix = 1;
			break;
		case OPT_VERS:
			if (convert_int(&num, val) != 0)
				goto badopt;
			nfsvers = (rpcvers_t)num;
			break;
		case OPT_PROTO:
			if (val == NULL)
				goto badopt;

			nfs_proto = (char *)malloc(strlen(val)+1);
			if (!nfs_proto) {
				pr_err(gettext("no memory"));
				return (RET_ERR);
			}

			(void) strncpy(nfs_proto, val, strlen(val)+1);
			break;

		case OPT_NOPRINT:
			args->flags |= NFSMNT_NOPRINT;
			break;

		case OPT_LARGEFILES:
			largefiles = 1;
			break;

		case OPT_NOLARGEFILES:
			pr_err(gettext("NFS can't support \"nolargefiles\"\n"));
			free(optstr);
			return (RET_ERR);

		case OPT_SEC:
			if (val == NULL) {
				pr_err(gettext(
				    "\"sec\" option requires argument\n"));
				return (RET_ERR);
			}
			if (nfs_getseconfig_byname(val, &nfs_sec)) {
				pr_err(gettext("can not get \"%s\" from %s\n"),
				    val, NFSSEC_CONF);
				return (RET_ERR);
			}
			sec_opt++;
			break;

		case OPT_PUBLIC:
			public_opt = TRUE;
			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:
			/*
			 * Note that this could be a valid OPT_* option so
			 * we can't use "val" but need to use "saveopt".
			 */
			if (fsisstdopt(saveopt))
				break;
			invalid = 1;
			if (!qflg)
				(void) fprintf(stderr, gettext(
				    "mount: %s on %s - WARNING unknown option"
				    " \"%s\"\n"), mnt->mnt_special,
				    mnt->mnt_mountp, saveopt);
			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 (sec_opt > 1) {
		pr_err(gettext("Security options conflict\n"));
		return (RET_ERR);
	}

	/* ensure that the user isn't trying to get large files over V2 */
	if (nfsvers == NFS_VERSION && largefiles) {
		pr_err(gettext("NFS V2 can't support \"largefiles\"\n"));
		return (RET_ERR);
	}

	if (nfsvers == NFS_V4 &&
	    nfs_proto != NULL &&
	    strncasecmp(nfs_proto, NC_UDP, strlen(NC_UDP)) == 0) {
		pr_err(gettext("NFS V4 does not support %s\n"), nfs_proto);
		return (RET_ERR);
	}

	return (RET_OK);

badopt:
	pr_err(gettext("invalid option: \"%s\"\n"), saveopt);
	free(optstr);
	return (RET_ERR);
}

static int
make_secure(struct nfs_args *args, char *hostname, struct netconfig *nconf,
	bool_t use_pubfh, rpcvers_t vers)
{
	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 (!snego_done && !sec_opt) {
		/*
		 * Get default security mode.
		 * AUTH_UNIX has been the default choice for a long time.
		 * The better NFS security service becomes, the better chance
		 * we will set stronger security service as the default NFS
		 * security mode.
		 */
		if (nfs_getseconfig_default(&nfs_sec)) {
			pr_err(gettext("error getting default"
			    " security entry\n"));
			return (-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.
	 * These information might be needed later in the kernel.
	 *
	 * Eventurally, we want to move this code to nfs_clnt_secdata()
	 * when autod_nfs.c and mount.c can share the same get_the_addr()
	 * routine.
	 */
	flags = 0;
	syncaddr = NULL;

	if (nfs_sec.sc_rpcnum == AUTH_DH || vers == NFS_V4) {
		/*
		 * If using the public fh or nfsv4, we will not contact the
		 * remote RPCBINDer, since it is possibly behind a firewall.
		 */
		if (use_pubfh == FALSE && vers != NFS_V4)
			syncaddr = get_the_addr(hostname, RPCBPROG, RPCBVERS,
			    nconf, 0, NULL, NULL, FALSE, NULL, NULL);

		if (syncaddr != NULL) {
			/* for flags in sec_data */
			flags |= AUTH_F_RPCTIMESYNC;
		} else {
			struct nd_hostserv hs;
			int error;

			hs.h_host = hostname;
			hs.h_serv = "timserver";

			error = netdir_getbyname(nconf, &hs, &retaddrs);

			if (error != ND_OK && (nfs_sec.sc_rpcnum == AUTH_DH)) {
				pr_err(gettext("%s: secure: no time service\n"),
				    hostname);
				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(&nfs_sec, hostname, args->knconf,
	    syncaddr, flags))) {
		pr_err(gettext("errors constructing security related data\n"));
		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, ulong_t prog, ulong_t vers,
	struct netconfig *nconf, ushort_t port, struct t_info *tinfo,
	caddr_t *fhp, bool_t get_pubfh, char *fspath, err_ret_t *error)
{
	struct netbuf *nb = NULL;
	struct t_bind *tbind = NULL;
	CLIENT *cl = NULL;
	struct timeval tv;
	int fd = -1;
	AUTH *ah = NULL;
	AUTH *new_ah = NULL;
	struct snego_t snego;

	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;

	/*
	 * In the case of public filehandle usage or NFSv4 we want to
	 * avoid use of the rpcbind/portmap protocol
	 */
	if ((get_pubfh == TRUE) || (vers == NFS_V4)) {
		struct nd_hostserv hs;
		struct nd_addrlist *retaddrs;
		int retval;
		hs.h_host = hostname;

		/* NFS where vers==4 does not support UDP */
		if (vers == NFS_V4 &&
		    strncasecmp(nconf->nc_proto, NC_UDP,
		    strlen(NC_UDP)) == 0) {
			SET_ERR_RET(error, ERR_PROTO_UNSUPP, 0);
			goto done;
		}

		if (port == 0)
			hs.h_serv = "nfs";
		else
			hs.h_serv = NULL;

		if ((retval = netdir_getbyname(nconf, &hs, &retaddrs))
		    != ND_OK) {
			/*
			 * Carefully set the error value here. Want to signify
			 * that the error was an unknown host.
			 */
			if (retval == ND_NOHOST) {
				SET_ERR_RET(error, ERR_NOHOST, retval);
			}

			goto done;
		}
		memcpy(tbind->addr.buf, retaddrs->n_addrs->buf,
		    retaddrs->n_addrs->len);
		tbind->addr.len = retaddrs->n_addrs->len;
		netdir_free((void *)retaddrs, ND_ADDRLIST);
		(void) netdir_options(nconf, ND_SET_RESERVEDPORT, fd, NULL);

	} else {
		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;

	tv.tv_sec = 5;
	tv.tv_usec = 0;

	(void) clnt_control(cl, CLSET_TIMEOUT, (char *)&tv);

	if ((get_pubfh == TRUE) && (vers != NFS_V4)) {
		enum snego_stat sec;

		if (!snego_done) {
			/*
			 * negotiate sec flavor.
			 */
			snego.cnt = 0;
			if ((sec = nfs_sec_nego(vers, cl, fspath, &snego)) ==
			    SNEGO_SUCCESS) {
				int jj;

				/*
				 * check if server supports the one
				 * specified in the sec= option.
				 */
				if (sec_opt) {
					for (jj = 0; jj < snego.cnt; jj++) {
						if (snego.array[jj] ==
						    nfs_sec.sc_nfsnum) {
							snego_done = TRUE;
							break;
						}
					}
				}

				/*
				 * find a common sec flavor
				 */
				if (!snego_done) {
					if (sec_opt) {
						pr_err(gettext(
						    "Server does not support"
						    " the security flavor"
						    " specified.\n"));
					}

					for (jj = 0; jj < snego.cnt; jj++) {
						if (!nfs_getseconfig_bynumber(
						    snego.array[jj],
						    &nfs_sec)) {
							snego_done = TRUE;
#define	EMSG80SUX "Security flavor %d was negotiated and will be used.\n"
							if (sec_opt)
								pr_err(gettext(
								    EMSG80SUX),
								    nfs_sec.
								    sc_nfsnum);
							break;
						}
					}
				}

				if (!snego_done)
					return (NULL);

				/*
				 * Now that the flavor has been
				 * negotiated, get the fh.
				 *
				 * First, create an auth handle using the
				 * negotiated sec flavor in the next lookup to
				 * fetch the filehandle.
				 */
				new_ah = nfs_create_ah(cl, hostname, &nfs_sec);
				if (new_ah == NULL)
					goto done;
				cl->cl_auth = new_ah;
			} else if (sec == SNEGO_ARRAY_TOO_SMALL || sec ==
			    SNEGO_FAILURE) {
				goto done;
			}

			/*
			 * Note that if sec == SNEGO_DEF_VALID
			 * default sec flavor is acceptable.
			 * Use it to get the filehandle.
			 */
		}

		if (vers == NFS_VERSION) {
			wnl_diropargs arg;
			wnl_diropres res;

			memset((char *)&arg.dir, 0, sizeof (wnl_fh));
			arg.name = fspath;
			memset((char *)&res, 0, sizeof (wnl_diropres));
			if (wnlproc_lookup_2(&arg, &res, cl) !=
			    RPC_SUCCESS || res.status != WNL_OK)
				goto done;

			*fhp = malloc(sizeof (wnl_fh));

			if (*fhp == NULL) {
				pr_err(gettext("no memory\n"));
				goto done;
			}

			memcpy((char *)*fhp,
			    (char *)&res.wnl_diropres_u.wnl_diropres.file,
			    sizeof (wnl_fh));
		} else {
			WNL_LOOKUP3args arg;
			WNL_LOOKUP3res res;
			nfs_fh3 *fh3p;

			memset((char *)&arg.what.dir, 0, sizeof (wnl_fh3));
			arg.what.name = fspath;
			memset((char *)&res, 0, sizeof (WNL_LOOKUP3res));
			if (wnlproc3_lookup_3(&arg, &res, cl) !=
			    RPC_SUCCESS || res.status != WNL3_OK)
				goto done;

			fh3p = (nfs_fh3 *)malloc(sizeof (*fh3p));

			if (fh3p == NULL) {
				pr_err(gettext("no memory\n"));
				goto done;
			}

			fh3p->fh3_length =
			    res.WNL_LOOKUP3res_u.res_ok.object.data.data_len;
			memcpy(fh3p->fh3_u.data,
			    res.WNL_LOOKUP3res_u.res_ok.object.data.data_val,
			    fh3p->fh3_length);

			*fhp = (caddr_t)fh3p;
		}
	} else {
		struct rpc_err r_err;
		enum clnt_stat rc;

		/*
		 * 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;
				}
			}
			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) {
		pr_err(gettext("no memory\n"));
		goto done;
	}
	*nb = tbind->addr;
	nb->buf = (char *)malloc(nb->maxlen);
	if (nb->buf == NULL) {
		pr_err(gettext("no memory\n"));
		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;
	int	valid_family;
	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);
}

/*
 * 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.
 *
 * A similar routine is also defined in ../../autofs/autod_nfs.c.
 * This is a potential routine to move to ../lib for common usage.
 *
 * "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, ulong_t prog, ulong_t vers, struct netconfig **nconfp,
	char *proto, ushort_t port, struct t_info *tinfo, caddr_t *fhp,
	bool_t get_pubfh, char *fspath, err_ret_t *error)
{
	struct netbuf *nb = NULL;
	struct netconfig *nconf = NULL;
	NCONF_HANDLE *nc = NULL;
	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, get_pubfh, fspath, error));
	/*
	 * No nconf passed in.
	 *
	 * Try to get a nconf from /etc/netconfig filtered by
	 * the NETPATH environment variable.
	 * 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 ((nc = setnetpath()) == NULL) {
		/* should only return an error if problems with NETPATH */
		/* In which case you are hosed */
		SET_ERR_RET(error, ERR_NETPATH, 0);
		goto done;
	}

	/*
	 * 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);

		while (nconf = getnetpath(nc)) {
			if (strcmp(nconf->nc_netid, proto))
				continue;

			/* may be unsupported */
			SET_ERR_RET(error, ERR_PROTO_UNSUPP, 0);

			if ((port != 0) &&
			    ((strcmp(nconf->nc_protofmly, NC_INET) == 0 ||
			    strcmp(nconf->nc_protofmly, NC_INET6) == 0) &&
			    (strcmp(nconf->nc_proto, NC_TCP) != 0 &&
			    strcmp(nconf->nc_proto, NC_UDP) != 0))) {
				continue;
			} else {
				nb = get_the_addr(hostname, prog,
				    vers, nconf, port, tinfo,
				    fhp, get_pubfh, 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 (nconf == NULL)
			goto done;

		if ((nb = get_the_addr(hostname, prog, vers, nconf, port,
		    tinfo, fhp, get_pubfh, fspath, error)) == NULL)
			goto done;
	} else {
retry:
		SET_ERR_RET(error, ERR_NETPATH, 0);
		while (nconf = getnetpath(nc)) {
			SET_ERR_RET(error, ERR_PROTO_NONE, 0);

			if (nconf->nc_flag & NC_VISIBLE) {
				int	valid_proto;

				if (check_nconf(nconf,
				    nthtry, &valid_proto)) {
					if (port == 0)
						break;

					if (valid_proto == TRUE)
						break;
				}
			}
		} /* while */
		if (nconf == NULL) {
			if (++nthtry <= MNT_PREF_LISTLEN) {
				endnetpath(nc);
				if ((nc = setnetpath()) == NULL)
					goto done;
				goto retry;
			} else
				goto done;
		} else {
			if ((nb = get_the_addr(hostname, prog, vers, nconf,
			    port, tinfo, fhp, get_pubfh, fspath, error))
			    == NULL) {
				/* 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 (nconf == NULL).
				 */
				goto retry;
			}
		}
	}
	SET_ERR_RET(error, ERR_PROTO_NONE, 0);

	/*
	 * Got nconf and nb.  Now dup the netconfig structure (nconf)
	 * and return it thru nconfp.
	 */
	*nconfp = getnetconfigent(nconf->nc_netid);
	if (*nconfp == NULL) {
		syslog(LOG_ERR, "no memory\n");
		free(nb);
		nb = NULL;
	}
done:
	if (nc)
		endnetpath(nc);

	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);
}

/*
 * Get a file handle usinging multi-component lookup with the public
 * file handle.
 */
static int
get_fh_via_pub(struct nfs_args *args, char *fshost, char *fspath, bool_t url,
	bool_t loud, int *versp, struct netconfig **nconfp, ushort_t port)
{
	uint_t vers_min;
	uint_t vers_max;
	int r;
	char *path;

	if (nfsvers != 0) {
		vers_max = vers_min = nfsvers;
	} else {
		vers_max = vers_max_default;
		vers_min = vers_min_default;
	}

	if (url == FALSE) {
		path = malloc(strlen(fspath) + 2);
		if (path == NULL) {
			if (loud == TRUE)
				pr_err(gettext("no memory\n"));
			return (RET_ERR);
		}

		path[0] = (char)WNL_NATIVEPATH;
		(void) strcpy(&path[1], fspath);

	} else  {
		path = fspath;
	}

	for (nfsvers_to_use = vers_max; nfsvers_to_use >= vers_min;
	    nfsvers_to_use--) {
		/*
		 * getaddr_nfs will also fill in the fh for us.
		 */
		r = getaddr_nfs(args, fshost, nconfp,
		    TRUE, path, port, NULL, FALSE);

		if (r == RET_OK) {
			/*
			 * Since we are using the public fh, and NLM is
			 * not firewall friendly, use local locking.
			 * Not the case for v4.
			 */
			*versp = nfsvers_to_use;
			switch (nfsvers_to_use) {
			case NFS_V4:
				fstype = MNTTYPE_NFS4;
				break;
			case NFS_V3:
				fstype = MNTTYPE_NFS3;
				/* fall through to pick up llock option */
			default:
				args->flags |= NFSMNT_LLOCK;
				break;
			}
			if (fspath != path)
				free(path);

			return (r);
		}
	}

	if (fspath != path)
		free(path);

	if (loud == TRUE) {
		pr_err(gettext("Could not use public filehandle in request to"
		    " server %s\n"), fshost);
	}

	return (r);
}

/*
 * get fhandle of remote path from server's mountd
 */
static int
get_fh(struct nfs_args *args, char *fshost, char *fspath, int *versp,
	bool_t loud_on_mnt_err, struct netconfig **nconfp, ushort_t port)
{
	static struct fhstatus fhs;
	static struct mountres3 mountres3;
	static 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;
	static int printed = 0;
	int count, i, *auths;
	char *msg;

	switch (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 (nfsretry_vers)
			vers_to_try = 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 = nfsvers_to_use;
		err_ret_t error;
		int retval;
		SET_ERR_RET(&error, ERR_PROTO_NONE, 0);

		/* Let's hope for the best */
		nfsvers_to_use = NFS_V4;
		retval = getaddr_nfs(args, fshost, nconfp, FALSE,
		    fspath, port, &error, vers_min == NFS_V4);

		if (retval == RET_OK) {
			*versp = nfsvers_to_use = NFS_V4;
			fstype = MNTTYPE_NFS4;
			args->fh = strdup(fspath);
			if (args->fh == NULL) {
				pr_err(gettext("no memory\n"));
				*versp = nfsvers_to_use = savevers;
				return (RET_ERR);
			}
			return (RET_OK);
		}
		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, "datagram_v")) == NULL) {
		if (rpc_createerr.cf_stat == RPC_UNKNOWNHOST) {
			pr_err(gettext("%s: %s\n"), fshost,
			    clnt_spcreateerror(""));
			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)) {
			pr_err(gettext("%s: %s\n"), fshost,
			    clnt_spcreateerror(""));
			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) {
				if (nfsvers == 0) {
					pr_err(gettext(
			"%s:%s: no applicable versions of NFS supported\n"),
					    fshost, fspath);
				} else {
					pr_err(gettext(
			"%s:%s: NFS Version %d not supported\n"),
					    fshost, fspath, nfsvers);
				}
				return (RET_ERR);
			}
			if (!printed) {
				pr_err(gettext("%s: %s\n"), fshost,
				    clnt_spcreateerror(""));
				printed = 1;
			}
			return (RET_RETRY);
		}
	}
	if (posix && outvers < MOUNTVERS_POSIX) {
		pr_err(gettext("%s: %s: no pathconf info\n"),
		    fshost, clnt_sperror(cl, ""));
		clnt_destroy(cl);
		return (RET_ERR);
	}

	if (__clnt_bindresvport(cl) < 0) {
		pr_err(gettext("Couldn't bind to reserved port\n"));
		clnt_destroy(cl);
		return (RET_RETRY);
	}

	if ((cl->cl_auth = authsys_create_default()) == NULL) {
		pr_err(
		    gettext("Couldn't create default authentication handle\n"));
		clnt_destroy(cl);
		return (RET_RETRY);
	}

	switch (outvers) {
	case MOUNTVERS:
	case MOUNTVERS_POSIX:
		*versp = 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) {
			pr_err(gettext("%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) {
			if (loud_on_mnt_err) {
				if (errno == EACCES) {
					pr_err(gettext(
					    "%s:%s: access denied\n"),
					    fshost, fspath);
				} else {
					pr_err(gettext("%s:%s: %s\n"), fshost,
					    fspath, errno >= 0 ?
					    strerror(errno) : "invalid error "
					    "returned by server");
				}
			}
			clnt_destroy(cl);
			return (RET_MNTERR);
		}
		args->fh = malloc(sizeof (fhs.fhstatus_u.fhs_fhandle));
		if (args->fh == NULL) {
			pr_err(gettext("no memory\n"));
			return (RET_ERR);
		}
		memcpy((caddr_t)args->fh, (caddr_t)&fhs.fhstatus_u.fhs_fhandle,
		    sizeof (fhs.fhstatus_u.fhs_fhandle));
		if (!errno && posix) {
			rpc_stat = clnt_call(cl, MOUNTPROC_PATHCONF,
			    xdr_dirpath, (caddr_t)&fspath, xdr_ppathcnf,
			    (caddr_t)&p, timeout);
			if (rpc_stat != RPC_SUCCESS) {
				pr_err(gettext(
				    "%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)) {
				pr_err(gettext(
				    "%s:%s: no pathconf info\n"),
				    fshost, fspath);
				free(args->fh);
				clnt_destroy(cl);
				return (RET_ERR);
			}
			args->flags |= NFSMNT_POSIX;
			args->pathconf = malloc(sizeof (p));
			if (args->pathconf == NULL) {
				pr_err(gettext("no memory\n"));
				free(args->fh);
				clnt_destroy(cl);
				return (RET_ERR);
			}
			memcpy((caddr_t)args->pathconf, (caddr_t)&p,
			    sizeof (p));
		}
		break;

	case MOUNTVERS3:
		*versp = 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) {
			pr_err(gettext("%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) {
			if (loud_on_mnt_err) {
				switch (errno) {
				case MNT3ERR_NAMETOOLONG:
					msg = "path name is too long";
					break;
				case MNT3ERR_NOTSUPP:
					msg = "operation not supported";
					break;
				case MNT3ERR_SERVERFAULT:
					msg = "server fault";
					break;
				default:
					if (errno >= 0)
						msg = strerror(errno);
					else
						msg = "invalid error returned "
						    "by server";
					break;
				}
				pr_err(gettext("%s:%s: %s\n"), fshost,
				    fspath, msg);
			}
			clnt_destroy(cl);
			return (RET_MNTERR);
		}

		fh3p = (nfs_fh3 *)malloc(sizeof (*fh3p));
		if (fh3p == NULL) {
			pr_err(gettext("no memory\n"));
			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;
		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) {
			pr_err(gettext(
			    "server %s did not return any security mode\n"),
			    fshost);
			clnt_destroy(cl);
			return (RET_ERR);
		}

		if (sec_opt) {
			for (i = 0; i < count; i++) {
				if (auths[i] == nfs_sec.sc_nfsnum)
					break;
			}
			if (i == count)
				goto autherr;
		} else {
			seconfig_t default_sec;

			/*
			 * Get client configured default auth.
			 */
			nfs_sec.sc_nfsnum = -1;
			default_sec.sc_nfsnum = -1;
			(void) nfs_getseconfig_default(&default_sec);

			/*
			 * Look for clients default auth in servers list.
			 */
			if (default_sec.sc_nfsnum != -1) {
				for (i = 0; i < count; i++) {
					if (auths[i] == default_sec.sc_nfsnum) {
						sec_opt++;
						nfs_sec = default_sec;
						break;
					}
				}
			}

			/*
			 * Could not find clients default auth in servers list.
			 * Pick the first auth from servers list that is
			 * also supported on the client.
			 */
			if (nfs_sec.sc_nfsnum == -1) {
				for (i = 0; i < count; i++) {
					if (!nfs_getseconfig_bynumber(auths[i],
					    &nfs_sec)) {
						sec_opt++;
						break;

					}
				}
			}

			if (i == count)
				goto autherr;
		}
		break;
	default:
		pr_err(gettext("%s:%s: Unknown MOUNT version %d\n"),
		    fshost, fspath, outvers);
		clnt_destroy(cl);
		return (RET_ERR);
	}

	clnt_destroy(cl);
	return (RET_OK);

autherr:
	pr_err(gettext(
	    "security mode does not match the server exporting %s:%s\n"),
	    fshost, fspath);
	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,
	    bool_t get_pubfh, char *fspath, ushort_t port, err_ret_t *error,
	    bool_t print_rpcerror)
{
	struct stat sb;
	struct netconfig *nconf;
	struct knetconfig *knconfp;
	static int printed = 0;
	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);

	if (nfs_proto) {
		/*
		 * If a proto is specified and its rdma try this. The kernel
		 * will later do the reachablity test and fail form there
		 * if rdma transport is not available to kernel rpc
		 */
		if (strcmp(nfs_proto, "rdma") == 0) {
			args->addr = get_addr(fshost, NFS_PROGRAM,
			    nfsvers_to_use, nconfp, NULL, port, &tinfo,
			    &args->fh, get_pubfh, fspath, &addr_error);

			args->flags |= NFSMNT_DORDMA;
		} else {
			args->addr = get_addr(fshost, NFS_PROGRAM,
			    nfsvers_to_use, nconfp, nfs_proto, port, &tinfo,
			    &args->fh, get_pubfh, fspath, &addr_error);
		}
	} else {
		args->addr = get_addr(fshost, NFS_PROGRAM, nfsvers_to_use,
		    nconfp, nfs_proto, port, &tinfo, &args->fh, get_pubfh,
		    fspath, &addr_error);
		/*
		 * If no proto is specified set this flag.
		 * Kernel mount code will try to use RDMA if its on the
		 * system, otherwise it will keep on using the protocol
		 * selected here, through the above get_addr call.
		 */
		if (nfs_proto == NULL)
			args->flags |= NFSMNT_TRYRDMA;
	}

	if (args->addr == NULL) {
		/*
		 * We could have failed because the server had no public
		 * file handle support. So don't print a message and don't
		 * retry.
		 */
		if (get_pubfh == TRUE)
			return (RET_ERR);

		if (!printed) {
			switch (addr_error.error_type) {
			case 0:
				printed = 1;
				break;
			case ERR_RPCERROR:
				if (!print_rpcerror)
					/* no error print at this time */
					break;
				pr_err(gettext("%s NFS service not"
				    " available %s\n"), fshost,
				    clnt_sperrno(addr_error.error_value));
				printed = 1;
				break;
			case ERR_NETPATH:
				pr_err(gettext("%s: Error in NETPATH.\n"),
				    fshost);
				printed = 1;
				break;
			case ERR_PROTO_INVALID:
				pr_err(gettext("%s: NFS service does not"
				    " recognize protocol: %s.\n"), fshost,
				    nfs_proto);
				printed = 1;
				break;
			case ERR_PROTO_UNSUPP:
				if (nfsvers || nfsvers_to_use == NFS_VERSMIN) {
					/*
					 * Don't set "printed" here. Since we
					 * have to keep checking here till we
					 * exhaust transport errors on all vers.
					 *
					 * Print this message if:
					 * 1. After we have tried all versions
					 *    of NFS and none support the asked
					 *    transport.
					 *
					 * 2. If a version is specified and it
					 *    does'nt support the asked
					 *    transport.
					 *
					 * Otherwise we decrement the version
					 * and retry below.
					 */
					pr_err(gettext("%s: NFS service does"
					    " not support protocol: %s.\n"),
					    fshost, nfs_proto);
				}
				break;
			case ERR_NOHOST:
				pr_err("%s: %s\n", fshost, "Unknown host");
				printed = 1;
				break;
			default:
				/* case ERR_PROTO_NONE falls through */
				pr_err(gettext("%s: NFS service not responding"
				    "\n"), fshost);
				printed = 1;
				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 (nfsvers == 0 && addr_error.error_type ==
		    ERR_PROTO_UNSUPP && 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) {
		pr_err(gettext("getaddr_nfs: couldn't stat: %s: %s\n"),
		    nconf->nc_device, strerror(errno));
		return (RET_ERR);
	}

	knconfp = (struct knetconfig *)malloc(sizeof (*knconfp));
	if (!knconfp) {
		pr_err(gettext("no memory\n"));
		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 ro)
{
	int delay = 5;
	int count = retries;
	int r;

	/*
	 * Please see comments on nfsretry_vers in the beginning of this file
	 * and in main() routine.
	 */

	if (bg) {
		if (fork() > 0)
			return (RET_OK);
		backgrounded = 1;
		pr_err(gettext("backgrounding: %s\n"), mntp->mnt_mountp);
	} else {
		if (!nfsretry_vers)
			pr_err(gettext("retrying: %s\n"), mntp->mnt_mountp);
	}

	while (count--) {
		if ((r = mount_nfs(mntp, ro, NULL)) == RET_OK) {
			pr_err(gettext("%s: mounted OK\n"), mntp->mnt_mountp);
			return (RET_OK);
		}
		if (r != RET_RETRY)
			break;

		if (count > 0) {
			(void) sleep(delay);
			delay *= 2;
			if (delay > 120)
				delay = 120;
		}
	}

	if (!nfsretry_vers)
		pr_err(gettext("giving up on: %s\n"), mntp->mnt_mountp);

	return (RET_ERR);
}

/*
 * Read the NFS SMF Parameters  to determine if the
 * client has been configured for a new min/max for the NFS version to
 * use.
 */
static void
read_default(void)
{
	char value[4];
	int errno;
	int tmp = 0, bufsz = 0, ret = 0;

	/* Maximum number of bytes expected. */
	bufsz = 4;
	ret = nfs_smf_get_prop("client_versmin", value, DEFAULT_INSTANCE,
	    SCF_TYPE_INTEGER, SVC_NFS_CLIENT, &bufsz);
	if (ret == SA_OK) {
		errno = 0;
		tmp = strtol(value, (char **)NULL, 10);
		if (errno == 0) {
			vers_min_default = tmp;
		}
	}

	/* Maximum number of bytes expected. */
	bufsz = 4;
	ret = nfs_smf_get_prop("client_versmax", value, DEFAULT_INSTANCE,
	    SCF_TYPE_INTEGER, SVC_NFS_CLIENT, &bufsz);
	if (ret == SA_OK) {
		errno = 0;
		tmp = strtol(value, (char **)NULL, 10);
		if (errno == 0) {
			vers_max_default = tmp;
		}
	}
}

static void
sigusr1(int s)
{
}