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