/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* 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. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * nfs mount */ #define NFSCLIENT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "replica.h" #include #include #include #include #include "nfs_subr.h" #include "webnfs.h" #include #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(); 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); /* * 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 /etc/default/nfs 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); } /* * Read the defaults file 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 %s\n%s %s\n", gettext("Incorrect configuration of client\'s"), NFSADMIN, gettext("NFS_CLIENT_VERSMIN or NFS_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); 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 }; #define bad(val) (val == NULL || !isdigit(*val)) static int set_args(int *mntflags, struct nfs_args *args, char *fshost, struct mnttab *mnt) { char *saveopt, *optstr, *opts, *newopts, *val; 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 (bad(val)) goto badopt; nfs_port = htons((ushort_t)atoi(val)); 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: args->flags |= NFSMNT_RSIZE; if (bad(val)) goto badopt; args->rsize = atoi(val); break; case OPT_WSIZE: args->flags |= NFSMNT_WSIZE; if (bad(val)) goto badopt; args->wsize = atoi(val); break; case OPT_TIMEO: args->flags |= NFSMNT_TIMEO; if (bad(val)) goto badopt; args->timeo = atoi(val); break; case OPT_RETRANS: args->flags |= NFSMNT_RETRANS; if (bad(val)) goto badopt; args->retrans = atoi(val); break; case OPT_ACTIMEO: args->flags |= NFSMNT_ACDIRMAX; args->flags |= NFSMNT_ACREGMAX; args->flags |= NFSMNT_ACDIRMIN; args->flags |= NFSMNT_ACREGMIN; if (bad(val)) goto badopt; args->acdirmin = args->acregmin = args->acdirmax = args->acregmax = atoi(val); break; case OPT_ACREGMIN: args->flags |= NFSMNT_ACREGMIN; if (bad(val)) goto badopt; args->acregmin = atoi(val); break; case OPT_ACREGMAX: args->flags |= NFSMNT_ACREGMAX; if (bad(val)) goto badopt; args->acregmax = atoi(val); break; case OPT_ACDIRMIN: args->flags |= NFSMNT_ACDIRMIN; if (bad(val)) goto badopt; args->acdirmin = atoi(val); break; case OPT_ACDIRMAX: args->flags |= NFSMNT_ACDIRMAX; if (bad(val)) goto badopt; args->acdirmax = atoi(val); break; case OPT_BG: bg++; break; case OPT_FG: bg = 0; break; case OPT_RETRY: if (bad(val)) goto badopt; retries = atoi(val); break; case OPT_LLOCK: args->flags |= NFSMNT_LLOCK; break; case OPT_POSIX: posix = 1; break; case OPT_VERS: if (bad(val)) goto badopt; nfsvers = (rpcvers_t)atoi(val); 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 (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; if (sec_opt) { pr_err(gettext( "Security flavor %d was negotiated and" " will be used.\n"), 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; res = wnlproc_lookup_2(&arg, cl); if (res == NULL || res->status != NFS_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; res = wnlproc3_lookup_3(&arg, cl); if (res == NULL || res->status != NFS3_OK) goto done; fh3p = (nfs_fh3 *)malloc(sizeof (*fh3p)); if (fh3p == NULL) { pr_err(gettext("no memory\n")); CLNT_FREERES(cl, xdr_WNL_LOOKUP3res, (char *)res); 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; CLNT_FREERES(cl, xdr_WNL_LOOKUP3res, (char *)res); } } else { void *res; struct rpc_err r_err; if (vers == NFS_VERSION) res = wnlproc_null_2(NULL, cl); else if (vers == NFS_V3) res = wnlproc3_null_3(NULL, cl); else res = wnlproc4_null_4(NULL, cl); if (res == NULL) { 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); } /* * 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) { if (nthtry == FIRST_TRY) { if ((nconf->nc_semantics == NC_TPI_COTS_ORD) || (nconf->nc_semantics == NC_TPI_COTS)) { if (port == 0) break; if ((strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf-> nc_protofmly, NC_INET6) == 0) && (strcmp(nconf->nc_proto, NC_TCP) == 0)) break; } } if (nthtry == SECOND_TRY) { if (nconf->nc_semantics == NC_TPI_CLTS) { if (port == 0) break; if ((strcmp(nconf->nc_protofmly, NC_INET) == 0 || strcmp(nconf-> nc_protofmly, NC_INET6) == 0) && (strcmp( nconf->nc_proto, NC_UDP) == 0)) 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, strerror(errno)); } } 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: msg = strerror(errno); 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 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 (sec_opt) { for (i = 0; i < count; i++) { if (auths[i] == nfs_sec.sc_nfsnum) break; } if (i >= count) { goto autherr; } } else { if (count > 0) { 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); pr_err(gettext("backgrounding: %s\n"), mntp->mnt_mountp); backgrounded = 1; } 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 /etc/default/nfs configuration file 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 *defval; int errno; int tmp; /* Fail silently if error in opening the default nfs config file */ if ((defopen(NFSADMIN)) == 0) { if ((defval = defread("NFS_CLIENT_VERSMIN=")) != NULL) { errno = 0; tmp = strtol(defval, (char **)NULL, 10); if (errno == 0) { vers_min_default = tmp; } } if ((defval = defread("NFS_CLIENT_VERSMAX=")) != NULL) { errno = 0; tmp = strtol(defval, (char **)NULL, 10); if (errno == 0) { vers_max_default = tmp; } } /* close defaults file */ defopen(NULL); } } static void sigusr1(int s) { }