1 /* $NetBSD: nfs_boot.c,v 1.55 2000/12/10 23:17:01 fvdl 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 delay(3000000); /* give the link some time to get up */ 216 out: 217 soclose(so); 218 return (error); 219 } 220 221 int 222 nfs_boot_setaddress(ifp, procp, addr, netmask, braddr) 223 struct ifnet *ifp; 224 struct proc *procp; 225 u_int32_t addr, netmask, braddr; 226 { 227 struct socket *so; 228 struct ifaliasreq iareq; 229 struct sockaddr_in *sin; 230 int error; 231 232 /* 233 * Get a socket to use for various things in here. 234 * After this, use "goto out" to cleanup and return. 235 */ 236 error = socreate(AF_INET, &so, SOCK_DGRAM, 0); 237 if (error) { 238 printf("setaddress: socreate, error=%d\n", error); 239 return (error); 240 } 241 242 memset(&iareq, 0, sizeof(iareq)); 243 memcpy(iareq.ifra_name, ifp->if_xname, IFNAMSIZ); 244 245 /* Set the I/F address */ 246 sin = (struct sockaddr_in *)&iareq.ifra_addr; 247 sin->sin_len = sizeof(*sin); 248 sin->sin_family = AF_INET; 249 sin->sin_addr.s_addr = addr; 250 251 /* Set the netmask */ 252 if (netmask != INADDR_ANY) { 253 sin = (struct sockaddr_in *)&iareq.ifra_mask; 254 sin->sin_len = sizeof(*sin); 255 sin->sin_family = AF_INET; 256 sin->sin_addr.s_addr = netmask; 257 } /* else leave subnetmask unspecified (len=0) */ 258 259 /* Set the broadcast addr. */ 260 if (braddr != INADDR_ANY) { 261 sin = (struct sockaddr_in *)&iareq.ifra_broadaddr; 262 sin->sin_len = sizeof(*sin); 263 sin->sin_family = AF_INET; 264 sin->sin_addr.s_addr = braddr; 265 } /* else leave broadcast addr unspecified (len=0) */ 266 267 error = ifioctl(so, SIOCAIFADDR, (caddr_t)&iareq, procp); 268 if (error) { 269 printf("setaddress, error=%d\n", error); 270 goto out; 271 } 272 273 delay(3000000); /* give the link some time to get up */ 274 out: 275 soclose(so); 276 return (error); 277 } 278 279 int 280 nfs_boot_deladdress(ifp, procp, addr) 281 struct ifnet *ifp; 282 struct proc *procp; 283 u_int32_t addr; 284 { 285 struct socket *so; 286 struct ifreq ireq; 287 struct sockaddr_in *sin; 288 int error; 289 290 /* 291 * Get a socket to use for various things in here. 292 * After this, use "goto out" to cleanup and return. 293 */ 294 error = socreate(AF_INET, &so, SOCK_DGRAM, 0); 295 if (error) { 296 printf("deladdress: socreate, error=%d\n", error); 297 return (error); 298 } 299 300 memset(&ireq, 0, sizeof(ireq)); 301 memcpy(ireq.ifr_name, ifp->if_xname, IFNAMSIZ); 302 303 sin = (struct sockaddr_in *)&ireq.ifr_addr; 304 sin->sin_len = sizeof(*sin); 305 sin->sin_family = AF_INET; 306 sin->sin_addr.s_addr = addr; 307 308 error = ifioctl(so, SIOCDIFADDR, (caddr_t)&ireq, procp); 309 if (error) { 310 printf("deladdress, error=%d\n", error); 311 goto out; 312 } 313 314 out: 315 soclose(so); 316 return (error); 317 } 318 319 int 320 nfs_boot_setrecvtimo(so) 321 struct socket *so; 322 { 323 struct mbuf *m; 324 struct timeval *tv; 325 326 m = m_get(M_WAIT, MT_SOOPTS); 327 tv = mtod(m, struct timeval *); 328 m->m_len = sizeof(*tv); 329 tv->tv_sec = 1; 330 tv->tv_usec = 0; 331 return (sosetopt(so, SOL_SOCKET, SO_RCVTIMEO, m)); 332 } 333 334 int 335 nfs_boot_enbroadcast(so) 336 struct socket *so; 337 { 338 struct mbuf *m; 339 int32_t *on; 340 341 m = m_get(M_WAIT, MT_SOOPTS); 342 on = mtod(m, int32_t *); 343 m->m_len = sizeof(*on); 344 *on = 1; 345 return (sosetopt(so, SOL_SOCKET, SO_BROADCAST, m)); 346 } 347 348 int 349 nfs_boot_sobind_ipport(so, port) 350 struct socket *so; 351 u_int16_t port; 352 { 353 struct mbuf *m; 354 struct sockaddr_in *sin; 355 int error; 356 357 m = m_getclr(M_WAIT, MT_SONAME); 358 sin = mtod(m, struct sockaddr_in *); 359 sin->sin_len = m->m_len = sizeof(*sin); 360 sin->sin_family = AF_INET; 361 sin->sin_addr.s_addr = INADDR_ANY; 362 sin->sin_port = htons(port); 363 error = sobind(so, m, curproc); 364 m_freem(m); 365 return (error); 366 } 367 368 /* 369 * What is the longest we will wait before re-sending a request? 370 * Note this is also the frequency of "timeout" messages. 371 * The re-send loop counts up linearly to this maximum, so the 372 * first complaint will happen after (1+2+3+4+5)=15 seconds. 373 */ 374 #define MAX_RESEND_DELAY 5 /* seconds */ 375 #define TOTAL_TIMEOUT 30 /* seconds */ 376 377 int 378 nfs_boot_sendrecv(so, nam, sndproc, snd, rcvproc, rcv, from_p, context) 379 struct socket *so; 380 struct mbuf *nam; 381 int (*sndproc) __P((struct mbuf*, void*, int)); 382 struct mbuf *snd; 383 int (*rcvproc) __P((struct mbuf*, void*)); 384 struct mbuf **rcv, **from_p; 385 void *context; 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); 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(gw_ip) 484 struct in_addr *gw_ip; 485 { 486 struct sockaddr dst, gw, mask; 487 struct sockaddr_in *sin; 488 int error; 489 490 /* Destination: (default) */ 491 memset((caddr_t)&dst, 0, sizeof(dst)); 492 dst.sa_len = sizeof(dst); 493 dst.sa_family = AF_INET; 494 /* Gateway: */ 495 memset((caddr_t)&gw, 0, sizeof(gw)); 496 sin = (struct sockaddr_in *)&gw; 497 sin->sin_len = sizeof(*sin); 498 sin->sin_family = AF_INET; 499 sin->sin_addr.s_addr = gw_ip->s_addr; 500 /* Mask: (zero length) */ 501 /* XXX - Just pass a null pointer? */ 502 memset(&mask, 0, sizeof(mask)); 503 504 /* add, dest, gw, mask, flags, 0 */ 505 error = rtrequest(RTM_ADD, &dst, &gw, &mask, 506 (RTF_UP | RTF_GATEWAY | RTF_STATIC), NULL); 507 if (error) { 508 printf("nfs_boot: add route, error=%d\n", error); 509 error = 0; 510 } 511 } 512 513 static int nfs_boot_delroute __P((struct radix_node *, void *)); 514 static int 515 nfs_boot_delroute(rn, w) 516 struct radix_node *rn; 517 void *w; 518 { 519 struct rtentry *rt = (struct rtentry *)rn; 520 int error; 521 522 if (rt->rt_ifp != (struct ifnet *)w) 523 return (0); 524 525 error = rtrequest(RTM_DELETE, rt_key(rt), NULL, rt_mask(rt), 0, NULL); 526 if (error) 527 printf("nfs_boot: del route, error=%d\n", error); 528 529 return (0); 530 } 531 532 void 533 nfs_boot_flushrt(ifp) 534 struct ifnet *ifp; 535 { 536 537 rn_walktree(rt_tables[AF_INET], nfs_boot_delroute, ifp); 538 } 539 540 /* 541 * Get an initial NFS file handle using Sun RPC/mountd. 542 * Separate function because we used to call it twice. 543 * (once for root and once for swap) 544 */ 545 static int 546 nfs_boot_getfh(ndm) 547 struct nfs_dlmount *ndm; /* output */ 548 { 549 struct nfs_args *args; 550 struct sockaddr_in *sin; 551 char *pathname; 552 int error; 553 u_int16_t port; 554 555 args = &ndm->ndm_args; 556 557 /* Initialize mount args. */ 558 memset((caddr_t) args, 0, sizeof(*args)); 559 args->addr = &ndm->ndm_saddr; 560 args->addrlen = args->addr->sa_len; 561 #ifdef NFS_BOOT_TCP 562 args->sotype = SOCK_STREAM; 563 #else 564 args->sotype = SOCK_DGRAM; 565 #endif 566 args->fh = ndm->ndm_fh; 567 args->hostname = ndm->ndm_host; 568 args->flags = NFSMNT_NOCONN | NFSMNT_RESVPORT; 569 570 #ifndef NFS_V2_ONLY 571 args->flags |= NFSMNT_NFSV3; 572 #endif 573 #ifdef NFS_BOOT_OPTIONS 574 args->flags |= NFS_BOOT_OPTIONS; 575 #endif 576 #ifdef NFS_BOOT_RWSIZE 577 /* 578 * Reduce rsize,wsize for interfaces that consistently 579 * drop fragments of long UDP messages. (i.e. wd8003). 580 * You can always change these later via remount. 581 */ 582 args->flags |= NFSMNT_WSIZE | NFSMNT_RSIZE; 583 args->wsize = NFS_BOOT_RWSIZE; 584 args->rsize = NFS_BOOT_RWSIZE; 585 #endif 586 587 /* 588 * Find the pathname part of the "server:pathname" 589 * string left in ndm->ndm_host by nfs_boot_init. 590 */ 591 pathname = strchr(ndm->ndm_host, ':'); 592 if (pathname == 0) { 593 printf("nfs_boot: getfh - no pathname\n"); 594 return (EIO); 595 } 596 pathname++; 597 598 /* 599 * Get file handle using RPC to mountd/mount 600 */ 601 sin = (struct sockaddr_in *)&ndm->ndm_saddr; 602 error = md_mount(sin, pathname, args); 603 if (error) { 604 printf("nfs_boot: mountd `%s', error=%d\n", 605 ndm->ndm_host, error); 606 return (error); 607 } 608 609 /* Set port number for NFS use. */ 610 /* XXX: NFS port is always 2049, right? */ 611 #ifdef NFS_BOOT_TCP 612 retry: 613 #endif 614 error = krpc_portmap(sin, NFS_PROG, 615 (args->flags & NFSMNT_NFSV3) ? NFS_VER3 : NFS_VER2, 616 (args->sotype == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP, 617 &port); 618 if (port == htons(0)) 619 error = EIO; 620 if (error) { 621 #ifdef NFS_BOOT_TCP 622 if (args->sotype == SOCK_STREAM) { 623 args->sotype = SOCK_DGRAM; 624 goto retry; 625 } 626 #endif 627 printf("nfs_boot: portmap NFS, error=%d\n", error); 628 return (error); 629 } 630 sin->sin_port = port; 631 return (0); 632 } 633 634 635 /* 636 * RPC: mountd/mount 637 * Given a server pathname, get an NFS file handle. 638 * Also, sets sin->sin_port to the NFS service port. 639 */ 640 static int 641 md_mount(mdsin, path, argp) 642 struct sockaddr_in *mdsin; /* mountd server address */ 643 char *path; 644 struct nfs_args *argp; 645 { 646 /* The RPC structures */ 647 struct rdata { 648 u_int32_t errno; 649 union { 650 u_int8_t v2fh[NFSX_V2FH]; 651 struct { 652 u_int32_t fhlen; 653 u_int8_t fh[1]; 654 } v3fh; 655 } fh; 656 } *rdata; 657 struct mbuf *m; 658 u_int8_t *fh; 659 int minlen, error; 660 int mntver; 661 662 mntver = (argp->flags & NFSMNT_NFSV3) ? 3 : 2; 663 do { 664 /* 665 * Get port number for MOUNTD. 666 */ 667 error = krpc_portmap(mdsin, RPCPROG_MNT, mntver, 668 IPPROTO_UDP, &mdsin->sin_port); 669 if (error) 670 continue; 671 672 /* This mbuf is consumed by krpc_call. */ 673 m = xdr_string_encode(path, strlen(path)); 674 if (m == NULL) 675 return ENOMEM; 676 677 /* Do RPC to mountd. */ 678 error = krpc_call(mdsin, RPCPROG_MNT, mntver, 679 RPCMNT_MOUNT, &m, NULL); 680 if (error != EPROGMISMATCH) 681 break; 682 /* Try lower version of mountd. */ 683 } while (--mntver >= 1); 684 if (error) { 685 printf("nfs_boot: mountd error=%d\n", error); 686 return error; 687 } 688 if (mntver != 3) 689 argp->flags &= ~NFSMNT_NFSV3; 690 691 /* The reply might have only the errno. */ 692 if (m->m_len < 4) 693 goto bad; 694 /* Have at least errno, so check that. */ 695 rdata = mtod(m, struct rdata *); 696 error = fxdr_unsigned(u_int32_t, rdata->errno); 697 if (error) 698 goto out; 699 700 /* Have errno==0, so the fh must be there. */ 701 if (mntver == 3) { 702 argp->fhsize = fxdr_unsigned(u_int32_t, rdata->fh.v3fh.fhlen); 703 if (argp->fhsize > NFSX_V3FHMAX) 704 goto bad; 705 minlen = 2 * sizeof(u_int32_t) + argp->fhsize; 706 } else { 707 argp->fhsize = NFSX_V2FH; 708 minlen = sizeof(u_int32_t) + argp->fhsize; 709 } 710 711 if (m->m_len < minlen) { 712 m = m_pullup(m, minlen); 713 if (m == NULL) 714 return(EBADRPC); 715 rdata = mtod(m, struct rdata *); 716 } 717 718 fh = (mntver == 3) ? 719 rdata->fh.v3fh.fh : rdata->fh.v2fh; 720 memcpy(argp->fh, fh, argp->fhsize); 721 722 goto out; 723 724 bad: 725 error = EBADRPC; 726 727 out: 728 m_freem(m); 729 return error; 730 } 731