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