xref: /titanic_51/usr/src/stand/lib/fs/nfs/mount.c (revision 19449258028e6813f0b7a606b554b2fa37a390ec)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1988 AT&T */
28 /*	All Rights Reserved */
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 #include <sys/utsname.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <netinet/in.h>
36 #include <rpc/types.h>
37 #include <rpc/auth.h>
38 #include <sys/t_lock.h>
39 #include <netdb.h>
40 #include "clnt.h"
41 #include <rpc/xdr.h>
42 #include <rpc/rpc_msg.h>
43 #include <rpc/rpc.h>
44 #include "brpc.h"
45 #include "auth_inet.h"
46 #include "pmap.h"
47 #include <rpcsvc/nfs_prot.h>
48 #include <rpcsvc/nfs4_prot.h>
49 #include "nfs_inet.h"
50 #include <rpcsvc/bootparam.h>
51 #include <dhcp_impl.h>
52 #include <rpcsvc/mount.h>
53 #include <sys/promif.h>
54 #include <sys/salib.h>
55 #include "socket_inet.h"
56 #include "ipv4.h"
57 #include "mac.h"
58 #include <sys/bootdebug.h>
59 #include <errno.h>
60 #include "dhcpv4.h"
61 #include <sys/mntent.h>
62 
63 /* ARP timeout in milliseconds for BOOTP/RARP */
64 #define	ARP_INETBOOT_TIMEOUT 1000
65 
66 struct nfs_file		roothandle;			/* root file handle */
67 static char		root_hostname[SYS_NMLN];	/* server hostname */
68 static char		my_hostname[MAXHOSTNAMELEN];
69 static char		root_pathbuf[NFS_MAXPATHLEN];	/* the root's path */
70 static char		root_boot_file[NFS_MAXPATHLEN];	/* optional boot file */
71 static struct sockaddr_in root_to;			/* server sock ip */
72 							/* in network order */
73 CLIENT			*root_CLIENT = NULL;		/* CLIENT handle */
74 int dontroute = FALSE;	/* In case rarp/bootparams was selected */
75 char			rootopts[MAX_PATH_LEN];
76 static gid_t		fake_gids = 1;	/* fake gids list for auth_unix */
77 
78 extern void set_default_filename(char *);	/* boot.c */
79 
80 /*
81  * xdr routines used by mount.
82  */
83 
84 bool_t
85 xdr_fhstatus(XDR *xdrs, struct fhstatus *fhsp)
86 {
87 	if (!xdr_int(xdrs, (int *)&fhsp->fhs_status))
88 		return (FALSE);
89 	if (fhsp->fhs_status == 0) {
90 		return (xdr_fhandle(xdrs, fhsp->fhstatus_u.fhs_fhandle));
91 	}
92 	return (TRUE);
93 }
94 
95 bool_t
96 xdr_fhandle(XDR *xdrs, fhandle fhp)
97 {
98 	return (xdr_opaque(xdrs, (char *)fhp, NFS_FHSIZE));
99 }
100 
101 bool_t
102 xdr_path(XDR *xdrs, char **pathp)
103 {
104 	return (xdr_string(xdrs, pathp, MNTPATHLEN));
105 }
106 
107 bool_t
108 xdr_fhandle3(XDR *xdrs, fhandle3 *objp)
109 {
110 	return (xdr_bytes(xdrs, (char **)&objp->fhandle3_val,
111 				(uint_t *)&objp->fhandle3_len, FHSIZE3));
112 }
113 
114 bool_t
115 xdr_mountstat3(XDR *xdrs, mountstat3 *objp)
116 {
117 	return (xdr_enum(xdrs, (enum_t *)objp));
118 }
119 
120 bool_t
121 xdr_mountres3_ok(XDR *xdrs, mountres3_ok *objp)
122 {
123 	if (!xdr_fhandle3(xdrs, &objp->fhandle))
124 		return (FALSE);
125 	return (xdr_array(xdrs, (char **)&objp->auth_flavors.auth_flavors_val,
126 			(uint_t *)&objp->auth_flavors.auth_flavors_len, ~0,
127 			sizeof (int), (xdrproc_t)xdr_int));
128 }
129 
130 bool_t
131 xdr_mountres3(XDR *xdrs, mountres3 *objp)
132 {
133 	if (!xdr_mountstat3(xdrs, &objp->fhs_status))
134 		return (FALSE);
135 	if (objp->fhs_status == MNT_OK)
136 		return (xdr_mountres3_ok(xdrs, &objp->mountres3_u.mountinfo));
137 	return (TRUE);
138 }
139 
140 static int
141 nfsmountroot(char *path, struct nfs_file *filep)
142 {
143 	int		rexmit;
144 	int		resp_wait;
145 	enum clnt_stat	status;
146 	struct fhstatus	root_tmp;			/* to pass to rpc/xdr */
147 
148 	/*
149 	 * Wait up to 16 secs for first response, retransmitting expon.
150 	 */
151 	rexmit = 0;	/* default retransmission interval */
152 	resp_wait = 16;
153 
154 	do {
155 		status = brpc_call((rpcprog_t)MOUNTPROG, (rpcvers_t)MOUNTVERS,
156 		    (rpcproc_t)MOUNTPROC_MNT, xdr_path, (caddr_t)&path,
157 		    xdr_fhstatus, (caddr_t)&(root_tmp), rexmit, resp_wait,
158 		    &root_to, NULL, AUTH_UNIX);
159 		if (status == RPC_TIMEDOUT) {
160 			dprintf("boot: %s:%s mount server not responding.\n",
161 			    root_hostname, path);
162 		}
163 		rexmit = resp_wait;
164 		resp_wait = 0;	/* use default wait time. */
165 	} while (status == RPC_TIMEDOUT);
166 
167 	if ((status != RPC_SUCCESS) || (root_tmp.fhs_status != 0)) {
168 		nfs_error(root_tmp.fhs_status);
169 		root_to.sin_port = 0;
170 		return (-1);
171 	}
172 
173 	/*
174 	 * Since the mount succeeded, we'll mark the filep's
175 	 * status as NFS_OK, and its type as NFDIR. If these
176 	 * points aren't the case, then we wouldn't be here.
177 	 */
178 	bcopy(&root_tmp.fhstatus_u.fhs_fhandle, &filep->fh.fh2, FHSIZE);
179 	filep->ftype.type2 = NFDIR;
180 	filep->version = NFS_VERSION;
181 	nfs_readsize = nfs_readsize <  NFS_MAXDATA ? nfs_readsize : NFS_MAXDATA;
182 	/*
183 	 * Set a reasonable lower limit on readsize
184 	 */
185 	nfs_readsize = (nfs_readsize != 0 && nfs_readsize < 512) ?
186 							512 : nfs_readsize;
187 	return (0);
188 }
189 
190 int
191 setup_root_vars(void)
192 {
193 	size_t		buflen;
194 	uint16_t	readsize;
195 
196 	/*
197 	 * Root server name. Required.
198 	 */
199 	buflen = sizeof (root_hostname);
200 	if (dhcp_getinfo(DSYM_VENDOR, VS_NFSMNT_ROOTSRVR_NAME, 0,
201 	    root_hostname, &buflen)) {
202 		root_hostname[buflen] = '\0';
203 	} else {
204 		dprintf("BOUND: Missing Root Server Name Option\n");
205 		errno = EINVAL;
206 		return (-1);
207 	}
208 
209 	/*
210 	 * Root server IP. Required.
211 	 */
212 	buflen = sizeof (root_to.sin_addr);
213 	if (!dhcp_getinfo(DSYM_VENDOR, VS_NFSMNT_ROOTSRVR_IP, 0,
214 	    &root_to.sin_addr, &buflen)) {
215 		dprintf("BOUND: Missing Root Server IP Option\n");
216 		errno = EINVAL;
217 		return (-1);
218 	}
219 
220 	/*
221 	 * Root path Required.
222 	 */
223 	buflen = sizeof (root_pathbuf);
224 	if (dhcp_getinfo(DSYM_VENDOR, VS_NFSMNT_ROOTPATH, 0,
225 	    root_pathbuf, &buflen)) {
226 		root_pathbuf[buflen] = '\0';
227 	} else {
228 		dprintf("BOUND: Missing Root Path Option\n");
229 		errno = EINVAL;
230 		return (-1);
231 	}
232 
233 	/*
234 	 * Optional Bootfile path.
235 	 */
236 	buflen = sizeof (root_boot_file);
237 	if (dhcp_getinfo(DSYM_VENDOR, VS_NFSMNT_BOOTFILE, 0,
238 		    root_boot_file, &buflen)) {
239 		root_boot_file[buflen] = '\0';
240 		dprintf("BOUND: Optional Boot File is: %s\n", root_boot_file);
241 	}
242 
243 	/* if we got a boot file name, use it as the default */
244 	if (root_boot_file[0] != '\0')
245 		set_default_filename(root_boot_file);
246 
247 	/*
248 	 * Set the NFS read size. The mount code will adjust it to
249 	 * the maximum size.
250 	 */
251 	buflen = sizeof (readsize);
252 	if (dhcp_getinfo(DSYM_VENDOR, VS_BOOT_NFS_READSIZE, 0,
253 	    &readsize, &buflen)) {
254 		nfs_readsize = ntohs(readsize);
255 		if (boothowto & RB_VERBOSE) {
256 			printf("Boot NFS read size: %d\n", nfs_readsize);
257 		}
258 	}
259 
260 	/*
261 	 * Optional rootopts.
262 	 */
263 	buflen = sizeof (rootopts);
264 	if (dhcp_getinfo(DSYM_VENDOR, VS_NFSMNT_ROOTOPTS, 0,
265 	    rootopts, &buflen)) {
266 		rootopts[buflen] = '\0';
267 		dprintf("BOUND: Optional Rootopts is: %s\n", rootopts);
268 	}
269 
270 	return (0);
271 }
272 
273 static void
274 mnt3_error(enum mountstat3 status)
275 {
276 	if (!(boothowto & RB_DEBUG))
277 		return;
278 
279 	switch (status) {
280 	case MNT_OK:
281 		printf("Mount: No error.\n");
282 		break;
283 	case MNT3ERR_PERM:
284 		printf("Mount: Not owner.\n");
285 		break;
286 	case MNT3ERR_NOENT:
287 		printf("Mount: No such file or directory.\n");
288 		break;
289 	case MNT3ERR_IO:
290 		printf("Mount: I/O error.\n");
291 		break;
292 	case MNT3ERR_ACCES:
293 		printf("Mount: Permission denied.\n");
294 		break;
295 	case MNT3ERR_NOTDIR:
296 		printf("Mount: Not a directory.\n");
297 		break;
298 	case MNT3ERR_INVAL:
299 		printf("Mount: Invalid argument.\n");
300 		break;
301 	case MNT3ERR_NAMETOOLONG:
302 		printf("Mount: File name too long.\n");
303 		break;
304 	case MNT3ERR_NOTSUPP:
305 		printf("Mount: Operation not supported.\n");
306 		break;
307 	case MNT3ERR_SERVERFAULT:
308 		printf("Mount: Server fault.\n");
309 		break;
310 	default:
311 		printf("Mount: unknown error.\n");
312 		break;
313 	}
314 }
315 
316 static int
317 nfs3mountroot(char *path, struct nfs_file *filep)
318 {
319 	int		rexmit;
320 	int		resp_wait;
321 	struct mountres3 res3;
322 	enum clnt_stat	status;
323 
324 	/*
325 	 * Wait up to 16 secs for first response, retransmitting expon.
326 	 */
327 	rexmit = 0;	/* default retransmission interval */
328 	resp_wait = 16;
329 
330 	/*
331 	 * Try to mount using V3
332 	 */
333 	do {
334 		bzero(&res3, sizeof (struct mountres3));
335 
336 		status = brpc_call((rpcprog_t)MOUNTPROG, (rpcvers_t)MOUNTVERS3,
337 		    (rpcproc_t)MOUNTPROC_MNT, xdr_path, (caddr_t)&path,
338 		    xdr_mountres3, (caddr_t)&res3, rexmit, resp_wait,
339 		    &root_to, NULL, AUTH_UNIX);
340 
341 		if (status != RPC_TIMEDOUT)
342 			break;
343 
344 		dprintf("boot: %s:%s mount server not responding.\n",
345 			    root_hostname, path);
346 
347 		rexmit = resp_wait;
348 		resp_wait = 0;	/* use default wait time. */
349 
350 		xdr_free(xdr_mountres3, (caddr_t)&res3);
351 	} while (status == RPC_TIMEDOUT);
352 
353 	if ((status != RPC_SUCCESS) || (res3.fhs_status != MNT_OK)) {
354 		mnt3_error(res3.fhs_status);
355 		root_to.sin_port = 0;
356 		return (-1);
357 	}
358 
359 	/*
360 	 * Since the mount succeeded, we'll mark the filep's
361 	 * status as NFS_OK, and its type as NF3DIR. If these
362 	 * points aren't the case, then we wouldn't be here.
363 	 */
364 	filep->fh.fh3.len = res3.mountres3_u.mountinfo.fhandle.fhandle3_len;
365 	bcopy(res3.mountres3_u.mountinfo.fhandle.fhandle3_val,
366 			filep->fh.fh3.data,
367 			filep->fh.fh3.len);
368 	filep->ftype.type3 = NF3DIR;
369 	filep->version = NFS_V3;
370 	/*
371 	 * Hardwire in a known reasonable upper limit of 32K
372 	 */
373 	nfs_readsize = nfs_readsize <  32 * 1024 ? nfs_readsize : 32 * 1024;
374 	/*
375 	 * Set a reasonable lower limit on readsize
376 	 */
377 	nfs_readsize = (nfs_readsize != 0 && nfs_readsize < 512) ?
378 							512 : nfs_readsize;
379 	xdr_free(xdr_mountres3, (caddr_t)&res3);
380 	return (0);
381 }
382 
383 /*
384  * Setup v4 client for inetboot
385  */
386 static int
387 nfs4init(char *path, uint16_t nfs_port)
388 {
389 	struct timeval	wait;
390 	int		fd = -1;
391 	int		error = 0;
392 	enum clnt_stat	rpc_stat;
393 	struct nfs_file	rootpath;
394 
395 	wait.tv_sec = RPC_RCVWAIT_MSEC / 1000;
396 	wait.tv_usec = 0;
397 
398 	/*
399 	 * If we haven't explicitly set the port number, set to the standard
400 	 * 2049 and don't cause a rpcbind request.
401 	 */
402 	if (nfs_port == 0)
403 		nfs_port = 2049;
404 
405 	root_to.sin_port = htons(nfs_port);
406 
407 	/*
408 	 * Support TCP only
409 	 */
410 	root_CLIENT = clntbtcp_create(&root_to, NFS_PROGRAM,
411 					NFS_V4, wait, &fd,
412 					NFS4BUF_SIZE, NFS4BUF_SIZE);
413 
414 	if (root_CLIENT == NULL) {
415 		root_to.sin_port = 0;
416 		return (-1);
417 	}
418 
419 	root_CLIENT->cl_auth =
420 			authunix_create(my_hostname, 0, 1, 1, &fake_gids);
421 
422 	/*
423 	 * Send NULL proc the server first to see if V4 exists
424 	 */
425 	rpc_stat = CLNT_CALL(root_CLIENT, NFSPROC4_NULL, xdr_void, NULL,
426 				xdr_void, NULL, wait);
427 
428 	if (rpc_stat != RPC_SUCCESS) {
429 		dprintf("boot: NULL proc failed NFSv4 service not available\n");
430 		AUTH_DESTROY(root_CLIENT->cl_auth);
431 		CLNT_DESTROY(root_CLIENT);
432 		root_to.sin_port = 0;
433 		return (-1);
434 	}
435 
436 	/*
437 	 * Do a lookup to get to the root_path.  This is nice since it can
438 	 * handle multicomponent lookups.
439 	 */
440 	roothandle.version = NFS_V4;
441 	roothandle.ftype.type4 = NF4DIR;
442 	roothandle.fh.fh4.len = 0;		/* Force a PUTROOTFH */
443 	roothandle.offset = (uint_t)0;		/* it's a directory! */
444 	error = lookup(path, &rootpath, TRUE);
445 
446 	if (error) {
447 		printf("boot: lookup %s failed\n", path);
448 		return (-1);
449 	}
450 	roothandle = rootpath;	/* structure copy */
451 
452 	/*
453 	 * Hardwire in a known reasonable upper limit of 32K
454 	 */
455 	nfs_readsize = nfs_readsize <  32 * 1024 ? nfs_readsize : 32 * 1024;
456 	/*
457 	 * Set a reasonable lower limit on readsize
458 	 */
459 	nfs_readsize = (nfs_readsize != 0 && nfs_readsize < 512) ?
460 							512 : nfs_readsize;
461 
462 	return (0);
463 }
464 
465 static int
466 atoi(const char *p)
467 {
468 	int n;
469 	int c, neg = 0;
470 
471 	if (!isdigit(c = *p)) {
472 		while (c == ' ' || c == '\t' || c == '\n')
473 			c = *++p;
474 		switch (c) {
475 		case '-':
476 			neg++;
477 			/* FALLTHROUGH */
478 		case '+':
479 			c = *++p;
480 		}
481 		if (!isdigit(c))
482 			return (0);
483 	}
484 	for (n = '0' - c; isdigit(c = *++p); ) {
485 		n *= 10; /* two steps to avoid unnecessary overflow */
486 		n += '0' - c; /* accum neg to avoid surprises at MAX */
487 	}
488 	return (neg ? n : -n);
489 }
490 
491 /*
492  * Parse suboptions from a string.
493  * Same as getsubopt(3C).
494  */
495 static int
496 getsubopt(char **optionsp, char * const *tokens, char **valuep)
497 {
498 	char *s = *optionsp, *p;
499 	int i;
500 	size_t optlen;
501 
502 	*valuep = NULL;
503 	if (*s == '\0')
504 		return (-1);
505 	p = strchr(s, ',');		/* find next option */
506 	if (p == NULL) {
507 		p = s + strlen(s);
508 	} else {
509 		*p++ = '\0';		/* mark end and point to next */
510 	}
511 	*optionsp = p;			/* point to next option */
512 	p = strchr(s, '=');		/* find value */
513 	if (p == NULL) {
514 		optlen = strlen(s);
515 		*valuep = NULL;
516 	} else {
517 		optlen = p - s;
518 		*valuep = ++p;
519 	}
520 	for (i = 0; tokens[i] != NULL; i++) {
521 		if ((optlen == strlen(tokens[i])) &&
522 		    (strncmp(s, tokens[i], optlen) == 0))
523 			return (i);
524 	}
525 	/* no match, point value at option and return error */
526 	*valuep = s;
527 	return (-1);
528 }
529 
530 /*
531  * The only interesting NFS mount options for initiating the kernel
532  * all others are ignored.
533  */
534 static char *optlist[] = {
535 #define	OPT_RSIZE	0
536 	MNTOPT_RSIZE,
537 #define	OPT_TIMEO	1
538 	MNTOPT_TIMEO,
539 #define	OPT_VERS	2
540 	MNTOPT_VERS,
541 #define	OPT_PROTO	3
542 	MNTOPT_PROTO,
543 #define	OPT_PORT	4
544 	MNTOPT_PORT,
545 	NULL
546 };
547 
548 /*
549  * This routine will open a device as it is known by the V2 OBP. It
550  * then goes thru the stuff necessary to initialize the network device,
551  * get our network parameters, (using DHCP or rarp/bootparams), and
552  * finally actually go and get the root filehandle. Sound like fun?
553  * Suuurrrree. Take a look.
554  *
555  * Returns 0 if things worked. -1 if we crashed and burned.
556  */
557 int
558 boot_nfs_mountroot(char *str)
559 {
560 	int		status;
561 	enum clnt_stat	rpc_stat;
562 	char		*root_path = &root_pathbuf[0];	/* to make XDR happy */
563 	struct timeval	wait;
564 	int		fd;
565 	int		bufsize;
566 	char		*opts, *val;
567 	int		nfs_version = 0;
568 	int		istcp = 1;
569 	int		nfs_port = 0;	/* Cause pmap to get port */
570 	struct sockaddr_in tmp_addr;	/* throw away */
571 
572 	if (root_CLIENT != NULL) {
573 		AUTH_DESTROY(root_CLIENT->cl_auth);
574 		CLNT_DESTROY(root_CLIENT);
575 		root_CLIENT = NULL;
576 	}
577 
578 	root_to.sin_family = AF_INET;
579 	root_to.sin_addr.s_addr = htonl(INADDR_ANY);
580 	root_to.sin_port = htons(0);
581 
582 	mac_init(str);
583 
584 	(void) ipv4_setpromiscuous(TRUE);
585 
586 	if (get_netconfig_strategy() == NCT_BOOTP_DHCP) {
587 		if (boothowto & RB_VERBOSE)
588 			printf("Using BOOTP/DHCP...\n");
589 		if (dhcp() != 0 || setup_root_vars() != 0) {
590 			(void) ipv4_setpromiscuous(FALSE);
591 			if (boothowto & RB_VERBOSE)
592 				printf("BOOTP/DHCP configuration failed!\n");
593 			return (-1);
594 		}
595 
596 		/* now that we have an IP address, turn off promiscuous mode */
597 		(void) ipv4_setpromiscuous(FALSE);
598 	} else {
599 		/* Use RARP/BOOTPARAMS. RARP will try forever... */
600 		if (boothowto & RB_VERBOSE)
601 			printf("Using RARP/BOOTPARAMS...\n");
602 		mac_call_rarp();
603 
604 		/*
605 		 * Since there is no way to determine our netmask, and therefore
606 		 * figure out if the router we got is useful, we assume all
607 		 * services are local. Use DHCP if this bothers you.
608 		 */
609 		dontroute = TRUE;
610 		/*
611 		 * We are trying to keep the ARP response
612 		 * timeout on the lower side with BOOTP/RARP.
613 		 * We are doing this for BOOTP/RARP where policy
614 		 * doesn't allow to route the packets outside
615 		 * the subnet as it has no idea about the
616 		 * netmask. By doing so, we are reducing
617 		 * ARP response timeout for any packet destined
618 		 * for outside booting clients subnet. Client can
619 		 * not expect such ARP replies and will finally
620 		 * timeout after a long delay. This would cause
621 		 * booting client to get stalled for a longer
622 		 * time. We can not avoid accepting any outside
623 		 * subnet packets accidentally destined for the
624 		 * booting client.
625 		 */
626 		mac_set_arp_timeout(ARP_INETBOOT_TIMEOUT);
627 
628 		/* now that we have an IP address, turn off promiscuous mode */
629 		(void) ipv4_setpromiscuous(FALSE);
630 
631 		/* get our hostname */
632 		if (whoami() == FALSE)
633 			return (-1);
634 
635 		/* get our bootparams. */
636 		if (getfile("root", root_hostname, &root_to.sin_addr,
637 		    root_pathbuf) == FALSE)
638 			return (-1);
639 
640 		/* get our rootopts. */
641 		(void) getfile("rootopts", root_hostname, &tmp_addr.sin_addr,
642 		    rootopts);
643 	}
644 
645 	/* mount root */
646 	if (boothowto & RB_VERBOSE) {
647 		printf("root server: %s (%s)\n", root_hostname,
648 		    inet_ntoa(root_to.sin_addr));
649 		printf("root directory: %s\n", root_pathbuf);
650 	}
651 
652 	/*
653 	 * Assumes we've configured the stack and thus know our
654 	 * IP address/hostname, either by using DHCP or rarp/bootparams.
655 	 */
656 	gethostname(my_hostname, sizeof (my_hostname));
657 
658 	wait.tv_sec = RPC_RCVWAIT_MSEC / 1000;
659 	wait.tv_usec = 0;
660 
661 	/*
662 	 * Parse out the interesting root options, if an invalid
663 	 * or unknown option is provided, silently ignore it and
664 	 * use the defaults.
665 	 */
666 	opts = rootopts;
667 	while (*opts) {
668 		int ival;
669 		switch (getsubopt(&opts, optlist, &val)) {
670 		case OPT_RSIZE:
671 			if (val == NULL || !isdigit(*val))
672 				break;
673 			nfs_readsize = atoi(val);
674 			break;
675 		case OPT_TIMEO:
676 			if (val == NULL || !isdigit(*val))
677 				break;
678 			ival = atoi(val);
679 			wait.tv_sec = ival / 10;
680 			wait.tv_usec = (ival % 10) * 100000;
681 			break;
682 		case OPT_VERS:
683 			if (val == NULL || !isdigit(*val))
684 				break;
685 			nfs_version = atoi(val);
686 			break;
687 		case OPT_PROTO:
688 			if (val == NULL || isdigit(*val))
689 				break;
690 			if ((strncmp(val, "udp", 3) == 0))
691 				istcp = 0;
692 			else
693 				istcp = 1;	/* must be tcp */
694 			break;
695 		case OPT_PORT:
696 			if (val == NULL || !isdigit(*val))
697 				break;
698 			nfs_port = atoi(val);
699 
700 			/*
701 			 * Currently nfs_dlinet.c doesn't support setting
702 			 * the root NFS port. Delete this when it does.
703 			 */
704 			nfs_port = 0;
705 			break;
706 		default:
707 			/*
708 			 * Unknown options are silently ignored
709 			 */
710 			break;
711 		}
712 	}
713 
714 	/*
715 	 * If version is set, then try that version first.
716 	 */
717 	switch (nfs_version) {
718 	case NFS_VERSION:
719 		if (nfsmountroot(root_path, &roothandle) == 0)
720 			goto domount;
721 		break;
722 	case NFS_V3:
723 		if (nfs3mountroot(root_path, &roothandle) == 0)
724 			goto domount;
725 		break;
726 	case NFS_V4:
727 		/*
728 		 * With v4 we skip the mount and go straight to
729 		 * setting the root filehandle.  Because of this we
730 		 * do things slightly differently and obtain our
731 		 * client handle first.
732 		 */
733 		if (istcp && nfs4init(root_path, nfs_port) == 0) {
734 			/*
735 			 * If v4 init succeeded then we are done.  Just return.
736 			 */
737 			return (0);
738 		}
739 	}
740 
741 	/*
742 	 * If there was no chosen version or the chosen version failed
743 	 * try all versions in order, this may still fail to boot
744 	 * at the kernel level if the options are not right, but be
745 	 * generous at this early stage.
746 	 */
747 	if (istcp && nfs4init(root_path, nfs_port) == 0) {
748 		/*
749 		 * If v4 init succeeded then we are done.  Just return.
750 		 */
751 		return (0);
752 	}
753 
754 	if (nfs3mountroot(root_path, &roothandle) == 0)
755 		goto domount;
756 
757 	if ((status = nfsmountroot(root_path, &roothandle)) != 0)
758 		return (status);
759 
760 domount:
761 	/*
762 	 * Only v2 and v3 go on from here.
763 	 */
764 	roothandle.offset = (uint_t)0;		/* it's a directory! */
765 	root_to.sin_port = htons(nfs_port);	/* NFS is next after mount */
766 
767 	/*
768 	 * Create the CLIENT handle for NFS operations
769 	 */
770 	if (roothandle.version == NFS_VERSION)
771 		bufsize = NFSBUF_SIZE;
772 	else
773 		bufsize = NFS3BUF_SIZE;
774 
775 	/*
776 	 * First try TCP then UDP (unless UDP asked for explicitly), if mountd
777 	 * alows this version but neither transport is available we are stuck.
778 	 */
779 	if (istcp) {
780 		fd = -1;
781 		root_CLIENT = clntbtcp_create(&root_to, NFS_PROGRAM,
782 			roothandle.version, wait, &fd, bufsize, bufsize);
783 		if (root_CLIENT != NULL) {
784 			root_CLIENT->cl_auth =
785 			    authunix_create(my_hostname, 0, 1, 1, &fake_gids);
786 			/*
787 			 * Send NULL proc, check if the server really exists
788 			 */
789 			rpc_stat = CLNT_CALL(root_CLIENT, 0,
790 					xdr_void, NULL, xdr_void, NULL, wait);
791 
792 			if (rpc_stat == RPC_SUCCESS)
793 				return (0);
794 
795 			AUTH_DESTROY(root_CLIENT->cl_auth);
796 			CLNT_DESTROY(root_CLIENT);
797 			root_CLIENT = NULL;
798 		}
799 		/* Fall through to UDP case */
800 	}
801 
802 	fd = -1;
803 	root_CLIENT = clntbudp_bufcreate(&root_to, NFS_PROGRAM,
804 			roothandle.version, wait, &fd, bufsize, bufsize);
805 	if (root_CLIENT == NULL)
806 		return (-1);
807 
808 	root_CLIENT->cl_auth =
809 			    authunix_create(my_hostname, 0, 1, 1, &fake_gids);
810 	/*
811 	 * Send NULL proc, check if the server really exists
812 	 */
813 	rpc_stat = CLNT_CALL(root_CLIENT, 0,
814 				xdr_void, NULL, xdr_void, NULL, wait);
815 
816 	if (rpc_stat == RPC_SUCCESS)
817 		return (0);
818 
819 	AUTH_DESTROY(root_CLIENT->cl_auth);
820 	CLNT_DESTROY(root_CLIENT);
821 	root_CLIENT = NULL;
822 	return (-1);
823 }
824