1/*	$NetBSD: nfs_boot.c,v 1.89 2022/09/20 02:23:37 knakahara Exp $	*/
2
3/*-
4 * Copyright (c) 1995, 1997 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Adam Glass and Gordon W. Ross.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/*
33 * Support for NFS diskless booting, specifically getting information
34 * about where to mount root from, what pathnames, etc.
35 */
36
37#include <sys/cdefs.h>
38__KERNEL_RCSID(0, "$NetBSD: nfs_boot.c,v 1.89 2022/09/20 02:23:37 knakahara Exp $");
39
40#ifdef _KERNEL_OPT
41#include "opt_nfs.h"
42#include "opt_tftproot.h"
43#include "opt_nfs_boot.h"
44#endif
45
46#ifdef NFS_BOOT_TCP
47#undef NFS_BOOT_UDP
48#endif
49
50#include <sys/param.h>
51#include <sys/systm.h>
52#include <sys/kernel.h>
53#include <sys/device.h>
54#include <sys/ioctl.h>
55#include <sys/proc.h>
56#include <sys/mount.h>
57#include <sys/mbuf.h>
58#include <sys/reboot.h>
59#include <sys/socket.h>
60#include <sys/socketvar.h>
61
62#include <net/if.h>
63#include <net/route.h>
64#include <net/if_ether.h>
65#include <net/if_types.h>
66
67#include <netinet/in.h>
68#include <netinet/if_inarp.h>
69
70#include <nfs/rpcv2.h>
71#include <nfs/krpc.h>
72#include <nfs/xdr_subs.h>
73
74#include <nfs/nfsproto.h>
75#include <nfs/nfs.h>
76#include <nfs/nfsmount.h>
77#include <nfs/nfsdiskless.h>
78
79/*
80 * There are three implementations of NFS diskless boot.
81 * One implementation uses BOOTP (RFC951, RFC1048),
82 * Sun RPC/bootparams or static configuration.  See the
83 * files:
84 *    nfs_bootdhcp.c:   BOOTP (RFC951, RFC1048)
85 *    nfs_bootparam.c:  Sun RPC/bootparams
86 *    nfs_bootstatic.c: honour config(1) description
87 */
88#if defined(NFS_BOOT_BOOTP) || defined(NFS_BOOT_DHCP)
89int nfs_boot_rfc951 = 1; /* BOOTP enabled (default) */
90#endif
91#ifdef NFS_BOOT_BOOTPARAM
92int nfs_boot_bootparam = 1; /* BOOTPARAM enabled (default) */
93#endif
94#ifdef NFS_BOOT_BOOTSTATIC
95int nfs_boot_bootstatic = 1; /* BOOTSTATIC enabled (default) */
96#endif
97
98#define IP_MIN_MTU 576
99
100/* mountd RPC */
101static int md_mount(struct sockaddr_in *mdsin, char *path,
102	struct nfs_args *argp, struct lwp *l);
103
104static int nfs_boot_delroute_matcher(struct rtentry *, void *);
105static void nfs_boot_defrt(struct in_addr *);
106static  int nfs_boot_getfh(struct nfs_dlmount *ndm, struct lwp *);
107
108
109/*
110 * Called with an empty nfs_diskless struct to be filled in.
111 * Find an interface, determine its ip address (etc.) and
112 * save all the boot parameters in the nfs_diskless struct.
113 */
114int
115nfs_boot_init(struct nfs_diskless *nd, struct lwp *lwp)
116{
117	struct ifnet *ifp;
118	int error = 0;
119	int flags __unused;
120
121	/* Explicitly necessary or build fails
122	 * due to unused variable, otherwise.
123	 */
124	flags = 0;
125
126	/*
127	 * Find the network interface.
128	 */
129	ifp = ifunit(device_xname(root_device));
130	if (ifp == NULL) {
131		printf("nfs_boot: '%s' not found\n",
132		       device_xname(root_device));
133		return (ENXIO);
134	}
135	nd->nd_ifp = ifp;
136
137	error = EADDRNOTAVAIL; /* ??? */
138#if defined(NFS_BOOT_BOOTSTATIC)
139	if (error && nfs_boot_bootstatic) {
140		printf("nfs_boot: trying static\n");
141		error = nfs_bootstatic(nd, lwp, &flags);
142	}
143#endif
144#if defined(NFS_BOOT_BOOTP) || defined(NFS_BOOT_DHCP)
145	if (error && nfs_boot_rfc951) {
146#if defined(NFS_BOOT_DHCP)
147		printf("nfs_boot: trying DHCP/BOOTP\n");
148#else
149		printf("nfs_boot: trying BOOTP\n");
150#endif
151		error = nfs_bootdhcp(nd, lwp, &flags);
152	}
153#endif
154#ifdef NFS_BOOT_BOOTPARAM
155	if (error && nfs_boot_bootparam) {
156		printf("nfs_boot: trying RARP (and RPC/bootparam)\n");
157		error = nfs_bootparam(nd, lwp, &flags);
158	}
159#endif
160	if (error)
161		return (error);
162
163	/*
164	 * Set MTU if passed
165	 */
166	if (nd->nd_mtu >= IP_MIN_MTU )
167		nfs_boot_setmtu(nd->nd_ifp, nd->nd_mtu, lwp);
168
169	/*
170	 * If the gateway address is set, add a default route.
171	 * (The mountd RPCs may go across a gateway.)
172	 */
173	if (nd->nd_gwip.s_addr)
174		nfs_boot_defrt(&nd->nd_gwip);
175
176#ifdef TFTPROOT
177	if (nd->nd_nomount)
178		goto out;
179#endif
180	/*
181	 * Now fetch the NFS file handles as appropriate.
182	 */
183	error = nfs_boot_getfh(&nd->nd_root, lwp);
184
185	if (error)
186		nfs_boot_cleanup(nd, lwp);
187
188#ifdef TFTPROOT
189out:
190#endif
191	return (error);
192}
193
194void
195nfs_boot_cleanup(struct nfs_diskless *nd, struct lwp *lwp)
196{
197
198	nfs_boot_deladdress(nd->nd_ifp, lwp, nd->nd_myip.s_addr);
199	nfs_boot_ifupdown(nd->nd_ifp, lwp, 0);
200	nfs_boot_flushrt(nd->nd_ifp);
201}
202
203int
204nfs_boot_ifupdown(struct ifnet *ifp, struct lwp *lwp, int up)
205{
206	struct socket *so;
207	struct ifreq ireq;
208	int error;
209
210	memset(&ireq, 0, sizeof(ireq));
211	memcpy(ireq.ifr_name, ifp->if_xname, IFNAMSIZ);
212
213	/*
214	 * Get a socket to use for various things in here.
215	 * After this, use "goto out" to cleanup and return.
216	 */
217	error = socreate(AF_INET, &so, SOCK_DGRAM, 0, lwp, NULL);
218	if (error) {
219		printf("ifupdown: socreate, error=%d\n", error);
220		return (error);
221	}
222
223	/*
224	 * Bring up the interface. (just set the "up" flag)
225	 * Get the old interface flags and or IFF_UP into them so
226	 * things like media selection flags are not clobbered.
227	 */
228	error = ifioctl(so, SIOCGIFFLAGS, (void *)&ireq, lwp);
229	if (error) {
230		printf("ifupdown: GIFFLAGS, error=%d\n", error);
231		goto out;
232	}
233	if (up)
234		ireq.ifr_flags |= IFF_UP;
235	else
236		ireq.ifr_flags &= ~IFF_UP;
237	error = ifioctl(so, SIOCSIFFLAGS, &ireq, lwp);
238	if (error) {
239		printf("ifupdown: SIFFLAGS, error=%d\n", error);
240		goto out;
241	}
242
243	if (up)
244		/* give the link some time to get up */
245		tsleep(nfs_boot_ifupdown, PZERO, "nfsbif", 3 * hz);
246out:
247	soclose(so);
248	return (error);
249}
250
251void
252nfs_boot_setmtu(struct ifnet *ifp, int mtu, struct lwp *lwp)
253{
254	struct socket *so;
255	struct ifreq ireq;
256	int error;
257
258	memset(&ireq, 0, sizeof(ireq));
259	memcpy(ireq.ifr_name, ifp->if_xname, IFNAMSIZ);
260
261	/*
262	 * Get a socket to use for various things in here.
263	 * After this, use "goto out" to cleanup and return.
264	 */
265	error = socreate(AF_INET, &so, SOCK_DGRAM, 0, lwp, NULL);
266	if (error) {
267		printf("setmtu: socreate, error=%d\n", error);
268		return;
269	}
270
271	/*
272	 * Get structure, set the new MTU, push structure.
273	 */
274	error = ifioctl(so, SIOCGIFMTU, (void *)&ireq, lwp);
275	if (error) {
276		printf("setmtu: GIFMTU, error=%d\n", error);
277		goto out;
278	}
279
280	ireq.ifr_mtu = mtu;
281
282	error = ifioctl(so, SIOCSIFMTU, &ireq, lwp);
283	if (error) {
284		printf("setmtu: SIFMTU, error=%d\n", error);
285		goto out;
286	}
287
288out:
289	soclose(so);
290	return;
291}
292
293int
294nfs_boot_setaddress(struct ifnet *ifp, struct lwp *lwp,
295		uint32_t addr, uint32_t netmask, uint32_t braddr)
296{
297	struct socket *so;
298	struct ifaliasreq iareq;
299	struct sockaddr_in *sin;
300	int error;
301
302	/*
303	 * Get a socket to use for various things in here.
304	 * After this, use "goto out" to cleanup and return.
305	 */
306	error = socreate(AF_INET, &so, SOCK_DGRAM, 0, lwp, NULL);
307	if (error) {
308		printf("setaddress: socreate, error=%d\n", error);
309		return (error);
310	}
311
312	memset(&iareq, 0, sizeof(iareq));
313	memcpy(iareq.ifra_name, ifp->if_xname, IFNAMSIZ);
314
315	/* Set the I/F address */
316	sin = (struct sockaddr_in *)&iareq.ifra_addr;
317	sin->sin_len = sizeof(*sin);
318	sin->sin_family = AF_INET;
319	sin->sin_addr.s_addr = addr;
320
321	/* Set the netmask */
322	if (netmask != INADDR_ANY) {
323		sin = (struct sockaddr_in *)&iareq.ifra_mask;
324		sin->sin_len = sizeof(*sin);
325		sin->sin_family = AF_INET;
326		sin->sin_addr.s_addr = netmask;
327	} /* else leave subnetmask unspecified (len=0) */
328
329	/* Set the broadcast addr. */
330	if (braddr != INADDR_ANY) {
331		sin = (struct sockaddr_in *)&iareq.ifra_broadaddr;
332		sin->sin_len = sizeof(*sin);
333		sin->sin_family = AF_INET;
334		sin->sin_addr.s_addr = braddr;
335	} /* else leave broadcast addr unspecified (len=0) */
336
337	error = ifioctl(so, SIOCAIFADDR, (void *)&iareq, lwp);
338	if (error) {
339		printf("setaddress, error=%d\n", error);
340		goto out;
341	}
342
343	/* give the link some time to get up */
344	tsleep(nfs_boot_setaddress, PZERO, "nfsbtd", 3 * hz);
345out:
346	soclose(so);
347	return (error);
348}
349
350int
351nfs_boot_deladdress(struct ifnet *ifp, struct lwp *lwp, uint32_t addr)
352{
353	struct socket *so;
354	struct ifreq ifr;
355	struct sockaddr_in sin;
356	struct in_addr ia = {.s_addr = addr};
357	int error;
358
359	/*
360	 * Get a socket to use for various things in here.
361	 * After this, use "goto out" to cleanup and return.
362	 */
363	error = socreate(AF_INET, &so, SOCK_DGRAM, 0, lwp, NULL);
364	if (error) {
365		printf("deladdress: socreate, error=%d\n", error);
366		return (error);
367	}
368
369	memset(&ifr, 0, sizeof(ifr));
370	memcpy(ifr.ifr_name, ifp->if_xname, IFNAMSIZ);
371
372	sockaddr_in_init(&sin, &ia, 0);
373	ifreq_setaddr(SIOCDIFADDR, &ifr, sintocsa(&sin));
374
375	error = ifioctl(so, SIOCDIFADDR, &ifr, lwp);
376	if (error) {
377		printf("deladdress, error=%d\n", error);
378		goto out;
379	}
380
381out:
382	soclose(so);
383	return (error);
384}
385
386int
387nfs_boot_setrecvtimo(struct socket *so)
388{
389	struct timeval tv;
390
391	tv.tv_sec = 1;
392	tv.tv_usec = 0;
393
394	return (so_setsockopt(NULL, so, SOL_SOCKET, SO_RCVTIMEO, &tv,
395	    sizeof(tv)));
396}
397
398int
399nfs_boot_enbroadcast(struct socket *so)
400{
401	int32_t on;
402
403	on = 1;
404	return (so_setsockopt(NULL, so, SOL_SOCKET, SO_BROADCAST, &on,
405	    sizeof(on)));
406}
407
408int
409nfs_boot_sobind_ipport(struct socket *so, uint16_t port, struct lwp *l)
410{
411	struct sockaddr_in sin;
412	int error;
413
414	sin.sin_len = sizeof(sin);
415	sin.sin_family = AF_INET;
416	sin.sin_addr.s_addr = INADDR_ANY;
417	sin.sin_port = htons(port);
418	error = sobind(so, (struct sockaddr *)&sin, l);
419	return (error);
420}
421
422/*
423 * What is the longest we will wait before re-sending a request?
424 * Note this is also the frequency of "timeout" messages.
425 * The re-send loop counts up linearly to this maximum, so the
426 * first complaint will happen after (1+2+3+4+5)=15 seconds.
427 */
428#define	MAX_RESEND_DELAY 5	/* seconds */
429#define TOTAL_TIMEOUT   30	/* seconds */
430
431int
432nfs_boot_sendrecv(struct socket *so, struct sockaddr_in *nam,
433		int (*sndproc)(struct mbuf *, void *, int),
434		struct mbuf *snd,
435		int (*rcvproc)(struct mbuf **, void *),
436		struct mbuf **rcv, struct mbuf **from_p,
437		void *context, struct lwp *lwp)
438{
439	int error, rcvflg, timo, secs, waited;
440	struct mbuf *m, *from;
441	struct uio uio;
442
443	/* Free at end if not null. */
444	from = NULL;
445
446	/*
447	 * Send it, repeatedly, until a reply is received,
448	 * but delay each re-send by an increasing amount.
449	 * If the delay hits the maximum, start complaining.
450	 */
451	waited = timo = 0;
452send_again:
453	waited += timo;
454	if (waited >= TOTAL_TIMEOUT)
455		return (ETIMEDOUT);
456
457	/* Determine new timeout. */
458	if (timo < MAX_RESEND_DELAY)
459		timo++;
460	else
461		printf("nfs_boot: timeout...\n");
462
463	if (sndproc) {
464		error = (*sndproc)(snd, context, waited);
465		if (error)
466			goto out;
467	}
468
469	/* Send request (or re-send). */
470	m = m_copypacket(snd, M_WAIT);
471	if (m == NULL) {
472		error = ENOBUFS;
473		goto out;
474	}
475	error = (*so->so_send)(so, (struct sockaddr *)nam, NULL,
476	    m, NULL, 0, lwp);
477	if (error) {
478		printf("nfs_boot: sosend: %d\n", error);
479		goto out;
480	}
481	m = NULL;
482
483	/*
484	 * Wait for up to timo seconds for a reply.
485	 * The socket receive timeout was set to 1 second.
486	 */
487
488	secs = timo;
489	for (;;) {
490		if (from) {
491			m_freem(from);
492			from = NULL;
493		}
494		if (m) {
495			m_freem(m);
496			m = NULL;
497		}
498		uio.uio_resid = 1 << 16; /* ??? */
499		rcvflg = 0;
500		error = (*so->so_receive)(so, &from, &uio, &m, NULL, &rcvflg);
501		if (error == EWOULDBLOCK) {
502			if (--secs <= 0)
503				goto send_again;
504			continue;
505		}
506		if (error)
507			goto out;
508#ifdef DIAGNOSTIC
509		if (!m || !(m->m_flags & M_PKTHDR)
510		    || (1 << 16) - uio.uio_resid != m->m_pkthdr.len)
511			panic("nfs_boot_sendrecv: return size");
512#endif
513
514		if ((*rcvproc)(&m, context))
515			continue;
516
517		if (rcv)
518			*rcv = m;
519		else
520			m_freem(m);
521		if (from_p) {
522			*from_p = from;
523			from = NULL;
524		}
525		break;
526	}
527out:
528	if (from) m_freem(from);
529	return (error);
530}
531
532/*
533 * Install a default route to the passed IP address.
534 */
535static void
536nfs_boot_defrt(struct in_addr *gw_ip)
537{
538	struct sockaddr dst, gw, mask;
539	struct sockaddr_in *sin;
540	int error;
541
542	/* Destination: (default) */
543	memset((void *)&dst, 0, sizeof(dst));
544	dst.sa_len = sizeof(dst);
545	dst.sa_family = AF_INET;
546	/* Gateway: */
547	memset((void *)&gw, 0, sizeof(gw));
548	sin = (struct sockaddr_in *)&gw;
549	sin->sin_len = sizeof(*sin);
550	sin->sin_family = AF_INET;
551	sin->sin_addr.s_addr = gw_ip->s_addr;
552	/* Mask: (zero length) */
553	/* XXX - Just pass a null pointer? */
554	memset(&mask, 0, sizeof(mask));
555
556	/* add, dest, gw, mask, flags, 0 */
557	error = rtrequest(RTM_ADD, &dst, &gw, &mask,
558			  (RTF_UP | RTF_GATEWAY | RTF_STATIC), NULL);
559	if (error) {
560		printf("nfs_boot: add route, error=%d\n", error);
561		error = 0;
562	}
563}
564
565static int
566nfs_boot_delroute_matcher(struct rtentry *rt, void *w)
567{
568
569	if ((void *)rt->rt_ifp != w)
570		return 0;
571
572	return 1;
573}
574
575void
576nfs_boot_flushrt(struct ifnet *ifp)
577{
578
579	rt_delete_matched_entries(AF_INET, nfs_boot_delroute_matcher, ifp, false);
580}
581
582/*
583 * Get an initial NFS file handle using Sun RPC/mountd.
584 * Separate function because we used to call it twice.
585 * (once for root and once for swap)
586 *
587 * ndm  output
588 */
589static int
590nfs_boot_getfh(struct nfs_dlmount *ndm, struct lwp *l)
591{
592	struct nfs_args *args;
593	struct sockaddr_in *sin;
594	char *pathname;
595	int error;
596	u_int16_t port;
597
598	args = &ndm->ndm_args;
599
600	/* Initialize mount args. */
601	memset((void *) args, 0, sizeof(*args));
602	args->addr     = &ndm->ndm_saddr;
603	args->addrlen  = args->addr->sa_len;
604#ifdef NFS_BOOT_UDP
605	args->sotype   = SOCK_DGRAM;
606#else
607	args->sotype   = SOCK_STREAM;
608#endif
609	args->fh       = ndm->ndm_fh;
610	args->hostname = ndm->ndm_host;
611	args->flags    = NFSMNT_NOCONN | NFSMNT_RESVPORT;
612
613#ifndef NFS_V2_ONLY
614	args->flags    |= NFSMNT_NFSV3;
615#endif
616#ifdef	NFS_BOOT_OPTIONS
617	args->flags    |= NFS_BOOT_OPTIONS;
618#endif
619#ifdef	NFS_BOOT_RWSIZE
620	/*
621	 * Reduce rsize,wsize for interfaces that consistently
622	 * drop fragments of long UDP messages.  (i.e. wd8003).
623	 * You can always change these later via remount.
624	 */
625	args->flags   |= NFSMNT_WSIZE | NFSMNT_RSIZE;
626	args->wsize    = NFS_BOOT_RWSIZE;
627	args->rsize    = NFS_BOOT_RWSIZE;
628#endif
629
630	/*
631	 * Find the pathname part of the "server:pathname"
632	 * string left in ndm->ndm_host by nfs_boot_init.
633	 */
634	pathname = strchr(ndm->ndm_host, ':');
635	if (pathname == 0) {
636		printf("nfs_boot: getfh - no pathname\n");
637		return (EIO);
638	}
639	pathname++;
640
641	/*
642	 * Get file handle using RPC to mountd/mount
643	 */
644	sin = (struct sockaddr_in *)&ndm->ndm_saddr;
645	error = md_mount(sin, pathname, args, l);
646	if (error) {
647		printf("nfs_boot: mountd `%s', error=%d\n",
648		       ndm->ndm_host, error);
649		return (error);
650	}
651
652	/* Set port number for NFS use. */
653	/* XXX: NFS port is always 2049, right? */
654retry:
655	error = krpc_portmap(sin, NFS_PROG,
656		    (args->flags & NFSMNT_NFSV3) ? NFS_VER3 : NFS_VER2,
657		    (args->sotype == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP,
658		    &port, l);
659	if (port == htons(0))
660		error = EIO;
661	if (error) {
662		if (args->sotype == SOCK_STREAM) {
663			args->sotype = SOCK_DGRAM;
664			goto retry;
665		}
666		printf("nfs_boot: portmap NFS, error=%d\n", error);
667		return (error);
668	}
669	sin->sin_port = port;
670	return (0);
671}
672
673
674/*
675 * RPC: mountd/mount
676 * Given a server pathname, get an NFS file handle.
677 * Also, sets sin->sin_port to the NFS service port.
678 *
679 * mdsin   mountd server address
680 */
681static int
682md_mount(struct sockaddr_in *mdsin, char *path,
683	 struct nfs_args *argp, struct lwp *lwp)
684{
685	/* The RPC structures */
686	struct rdata {
687		u_int32_t errno;
688		union {
689			u_int8_t  v2fh[NFSX_V2FH];
690			struct {
691				u_int32_t fhlen;
692				u_int8_t  fh[1];
693			} v3fh;
694		} fh;
695	} *rdata;
696	struct mbuf *m;
697	u_int8_t *fh;
698	int minlen, error;
699	int mntver;
700
701	mntver = (argp->flags & NFSMNT_NFSV3) ? 3 : 2;
702	do {
703		/*
704		 * Get port number for MOUNTD.
705		 */
706		error = krpc_portmap(mdsin, RPCPROG_MNT, mntver,
707		                    IPPROTO_UDP, &mdsin->sin_port, lwp);
708		if (error)
709			continue;
710
711		/* This mbuf is consumed by krpc_call. */
712		m = xdr_string_encode(path, strlen(path));
713		if (m == NULL)
714			return ENOMEM;
715
716		/* Do RPC to mountd. */
717		error = krpc_call(mdsin, RPCPROG_MNT, mntver,
718		                  RPCMNT_MOUNT, &m, NULL, lwp);
719		if (error != EPROGMISMATCH)
720			break;
721		/* Try lower version of mountd. */
722	} while (--mntver >= 1);
723	if (error) {
724		printf("nfs_boot: mountd error=%d\n", error);
725		return error;
726	}
727	if (mntver != 3)
728		argp->flags &= ~NFSMNT_NFSV3;
729
730	/* The reply might have only the errno. */
731	if (m->m_len < 4)
732		goto bad;
733	/* Have at least errno, so check that. */
734	rdata = mtod(m, struct rdata *);
735	error = fxdr_unsigned(u_int32_t, rdata->errno);
736	if (error)
737		goto out;
738
739	/* Have errno==0, so the fh must be there. */
740	if (mntver == 3) {
741		argp->fhsize   = fxdr_unsigned(u_int32_t, rdata->fh.v3fh.fhlen);
742		if (argp->fhsize > NFSX_V3FHMAX)
743			goto bad;
744		minlen = 2 * sizeof(u_int32_t) + argp->fhsize;
745	} else {
746		argp->fhsize   = NFSX_V2FH;
747		minlen = sizeof(u_int32_t) + argp->fhsize;
748	}
749
750	if (m->m_len < minlen) {
751		m = m_pullup(m, minlen);
752		if (m == NULL)
753			return(EBADRPC);
754		rdata = mtod(m, struct rdata *);
755	}
756
757	fh = (mntver == 3) ?
758		rdata->fh.v3fh.fh : rdata->fh.v2fh;
759	memcpy(argp->fh, fh, argp->fhsize);
760
761	goto out;
762
763bad:
764	error = EBADRPC;
765
766out:
767	m_freem(m);
768	return error;
769}
770