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