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