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