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
64struct nfs_file		roothandle;			/* root file handle */
65static char		root_hostname[SYS_NMLN];	/* server hostname */
66static char		my_hostname[MAXHOSTNAMELEN];
67static char		root_pathbuf[NFS_MAXPATHLEN];	/* the root's path */
68static char		root_boot_file[NFS_MAXPATHLEN];	/* optional boot file */
69static struct sockaddr_in root_to;			/* server sock ip */
70							/* in network order */
71CLIENT			*root_CLIENT = NULL;		/* CLIENT handle */
72int dontroute = FALSE;	/* In case rarp/bootparams was selected */
73char			rootopts[MAX_PATH_LEN];
74static gid_t		fake_gids = 1;	/* fake gids list for auth_unix */
75
76extern void set_default_filename(char *);	/* boot.c */
77
78/*
79 * xdr routines used by mount.
80 */
81
82bool_t
83xdr_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
93bool_t
94xdr_fhandle(XDR *xdrs, fhandle fhp)
95{
96	return (xdr_opaque(xdrs, (char *)fhp, NFS_FHSIZE));
97}
98
99bool_t
100xdr_path(XDR *xdrs, char **pathp)
101{
102	return (xdr_string(xdrs, pathp, MNTPATHLEN));
103}
104
105bool_t
106xdr_fhandle3(XDR *xdrs, fhandle3 *objp)
107{
108	return (xdr_bytes(xdrs, (char **)&objp->fhandle3_val,
109				(uint_t *)&objp->fhandle3_len, FHSIZE3));
110}
111
112bool_t
113xdr_mountstat3(XDR *xdrs, mountstat3 *objp)
114{
115	return (xdr_enum(xdrs, (enum_t *)objp));
116}
117
118bool_t
119xdr_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
128bool_t
129xdr_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
138static int
139nfsmountroot(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
188int
189setup_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
271static void
272mnt3_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
314static int
315nfs3mountroot(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 */
384static int
385nfs4init(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
463static int
464atoi(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 */
493static int
494getsubopt(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 */
532static 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 */
555int
556boot_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
758domount:
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