1 /* $OpenBSD: nfs_ops.c,v 1.26 2014/10/26 03:28:41 guenther Exp $ */ 2 3 /*- 4 * Copyright (c) 1990 Jan-Simon Pendry 5 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine 6 * Copyright (c) 1990, 1993 7 * The Regents of the University of California. All rights reserved. 8 * 9 * This code is derived from software contributed to Berkeley by 10 * Jan-Simon Pendry at Imperial College, London. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #include "am.h" 38 #include <sys/stat.h> 39 40 #ifdef HAS_NFS 41 42 #define NFS 43 #define NFSCLIENT 44 45 #include "mount.h" 46 47 /* 48 * Network file system 49 */ 50 51 /* 52 * Convert from nfsstat to UN*X error code 53 */ 54 #define unx_error(e) ((int)(e)) 55 56 /* 57 * The NFS layer maintains a cache of file handles. 58 * This is *fundamental* to the implementation and 59 * also allows quick remounting when a filesystem 60 * is accessed soon after timing out. 61 * 62 * The NFS server layer knows to flush this cache 63 * when a server goes down so avoiding stale handles. 64 * 65 * Each cache entry keeps a hard reference to 66 * the corresponding server. This ensures that 67 * the server keepalive information is maintained. 68 * 69 * The copy of the sockaddr_in here is taken so 70 * that the port can be twiddled to talk to mountd 71 * instead of portmap or the NFS server as used 72 * elsewhere. 73 * The port# is flushed if a server goes down. 74 * The IP address is never flushed - we assume 75 * that the address of a mounted machine never 76 * changes. If it does, then you have other 77 * problems... 78 */ 79 typedef struct fh_cache fh_cache; 80 struct fh_cache { 81 qelem fh_q; /* List header */ 82 void *fh_wchan; /* Wait channel */ 83 int fh_error; /* Valid data? */ 84 int fh_id; /* Unique id */ 85 int fh_cid; /* Callout id */ 86 fhstatus fh_handle; /* Handle on filesystem */ 87 struct sockaddr_in fh_sin; /* Address of mountd */ 88 fserver *fh_fs; /* Server holding filesystem */ 89 char *fh_path; /* Filesystem on host */ 90 }; 91 92 /* 93 * FH_TTL is the time a file handle will remain in the cache since 94 * last being used. If the file handle becomes invalid, then it 95 * will be flushed anyway. 96 */ 97 #define FH_TTL (5 * 60) /* five minutes */ 98 #define FH_TTL_ERROR (30) /* 30 seconds */ 99 100 static int fh_id = 0; 101 #define FHID_ALLOC() (++fh_id) 102 extern qelem fh_head; 103 qelem fh_head = { &fh_head, &fh_head }; 104 105 static int call_mountd(fh_cache*, unsigned long, fwd_fun, void *); 106 107 AUTH *nfs_auth; 108 109 static fh_cache * 110 find_nfs_fhandle_cache(void *idv, int done) 111 { 112 fh_cache *fp, *fp2 = 0; 113 /* XXX EVIL XXX */ 114 int id = (int) ((long)idv); 115 116 ITER(fp, fh_cache, &fh_head) { 117 if (fp->fh_id == id) { 118 fp2 = fp; 119 break; 120 } 121 } 122 123 #ifdef DEBUG 124 if (fp2) { 125 dlog("fh cache gives fp %#x, fs %s", fp2, fp2->fh_path); 126 } else { 127 dlog("fh cache search failed"); 128 } 129 #endif /* DEBUG */ 130 131 if (fp2 && !done) { 132 fp2->fh_error = ETIMEDOUT; 133 return 0; 134 } 135 136 return fp2; 137 } 138 139 /* 140 * Called when a filehandle appears 141 */ 142 static void 143 got_nfs_fh(void *pkt, int len, struct sockaddr_in *sa, 144 struct sockaddr_in *ia, void *idv, int done) 145 { 146 fh_cache *fp = find_nfs_fhandle_cache(idv, done); 147 if (fp) { 148 fp->fh_handle.fhs_vers = MOUNTVERS; 149 fp->fh_error = pickup_rpc_reply(pkt, len, &fp->fh_handle, 150 xdr_fhstatus); 151 if (!fp->fh_error) { 152 #ifdef DEBUG 153 dlog("got filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path); 154 #endif /* DEBUG */ 155 /* 156 * Wakeup anything sleeping on this filehandle 157 */ 158 if (fp->fh_wchan) { 159 #ifdef DEBUG 160 dlog("Calling wakeup on %#x", fp->fh_wchan); 161 #endif /* DEBUG */ 162 wakeup(fp->fh_wchan); 163 } 164 } 165 } 166 } 167 168 void 169 flush_nfs_fhandle_cache(fserver *fs) 170 { 171 fh_cache *fp; 172 ITER(fp, fh_cache, &fh_head) { 173 if (fp->fh_fs == fs || fs == 0) { 174 fp->fh_sin.sin_port = (u_short) 0; 175 fp->fh_error = -1; 176 } 177 } 178 } 179 180 static void 181 discard_fh(void *arg) 182 { 183 fh_cache *fp = arg; 184 185 rem_que(&fp->fh_q); 186 #ifdef DEBUG 187 dlog("Discarding filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path); 188 #endif /* DEBUG */ 189 free_srvr(fp->fh_fs); 190 free(fp->fh_path); 191 free(fp); 192 } 193 194 /* 195 * Determine the file handle for a node 196 */ 197 static int 198 prime_nfs_fhandle_cache(char *path, fserver *fs, fhstatus *fhbuf, void *wchan) 199 { 200 fh_cache *fp, *fp_save = 0; 201 int error; 202 int reuse_id = FALSE; 203 204 #ifdef DEBUG 205 dlog("Searching cache for %s:%s", fs->fs_host, path); 206 #endif /* DEBUG */ 207 208 /* 209 * First search the cache 210 */ 211 ITER(fp, fh_cache, &fh_head) { 212 if (fs == fp->fh_fs && strcmp(path, fp->fh_path) == 0) { 213 switch (fp->fh_error) { 214 case 0: 215 error = fp->fh_error = unx_error(fp->fh_handle.fhs_stat); 216 if (error == 0) { 217 if (fhbuf) 218 bcopy(&fp->fh_handle, fhbuf, 219 sizeof(fp->fh_handle)); 220 if (fp->fh_cid) 221 untimeout(fp->fh_cid); 222 fp->fh_cid = timeout(FH_TTL, 223 discard_fh, fp); 224 } else if (error == EACCES) { 225 /* 226 * Now decode the file handle return code. 227 */ 228 plog(XLOG_INFO, "Filehandle denied for \"%s:%s\"", 229 fs->fs_host, path); 230 } else { 231 errno = error; /* XXX */ 232 plog(XLOG_INFO, "Filehandle error for \"%s:%s\": %m", 233 fs->fs_host, path); 234 } 235 236 /* 237 * The error was returned from the remote mount daemon. 238 * Policy: this error will be cached for now... 239 */ 240 return error; 241 242 case -1: 243 /* 244 * Still thinking about it, but we can re-use. 245 */ 246 fp_save = fp; 247 reuse_id = TRUE; 248 break; 249 250 default: 251 /* 252 * Return the error. 253 * Policy: make sure we recompute if required again 254 * in case this was caused by a network failure. 255 * This can thrash mountd's though... If you find 256 * your mountd going slowly then: 257 * 1. Add a fork() loop to main. 258 * 2. Remove the call to innetgr() and don't use 259 * netgroups, especially if you don't use YP. 260 */ 261 error = fp->fh_error; 262 fp->fh_error = -1; 263 return error; 264 } 265 break; 266 } 267 } 268 269 /* 270 * Not in cache 271 */ 272 if (fp_save) { 273 fp = fp_save; 274 /* 275 * Re-use existing slot 276 */ 277 untimeout(fp->fh_cid); 278 free_srvr(fp->fh_fs); 279 free(fp->fh_path); 280 } else { 281 fp = ALLOC(fh_cache); 282 bzero(fp, sizeof(*fp)); 283 ins_que(&fp->fh_q, &fh_head); 284 } 285 if (!reuse_id) 286 fp->fh_id = FHID_ALLOC(); 287 fp->fh_wchan = wchan; 288 fp->fh_error = -1; 289 fp->fh_cid = timeout(FH_TTL, discard_fh, fp); 290 291 /* 292 * If the address has changed then don't try to re-use the 293 * port information 294 */ 295 if (fp->fh_sin.sin_addr.s_addr != fs->fs_ip->sin_addr.s_addr) { 296 fp->fh_sin = *fs->fs_ip; 297 fp->fh_sin.sin_port = 0; 298 } 299 fp->fh_fs = dup_srvr(fs); 300 fp->fh_path = strdup(path); 301 302 error = call_mountd(fp, MOUNTPROC_MNT, got_nfs_fh, wchan); 303 if (error) { 304 /* 305 * Local error - cache for a short period 306 * just to prevent thrashing. 307 */ 308 untimeout(fp->fh_cid); 309 fp->fh_cid = timeout(error < 0 ? 2 * ALLOWED_MOUNT_TIME : FH_TTL_ERROR, 310 discard_fh, fp); 311 fp->fh_error = error; 312 } else { 313 error = fp->fh_error; 314 } 315 return error; 316 } 317 318 int 319 make_nfs_auth(void) 320 { 321 /* 322 * From: Chris Metcalf <metcalf@masala.lcs.mit.edu> 323 * Use hostd, not just hostname. Note that uids 324 * and gids and the gidlist are type *int* and not the 325 * system uid_t and gid_t types. 326 */ 327 static int group_wheel = 0; 328 nfs_auth = authunix_create(hostd, 0, 0, 1, &group_wheel); 329 if (!nfs_auth) 330 return ENOBUFS; 331 return 0; 332 } 333 334 static int 335 call_mountd(fh_cache *fp, u_long proc, fwd_fun f, void *wchan) 336 { 337 struct rpc_msg mnt_msg; 338 int len; 339 char iobuf[8192]; 340 int error; 341 342 if (!nfs_auth) { 343 error = make_nfs_auth(); 344 if (error) 345 return error; 346 } 347 348 if (fp->fh_sin.sin_port == 0) { 349 u_short port; 350 error = nfs_srvr_port(fp->fh_fs, &port, wchan); 351 if (error) 352 return error; 353 fp->fh_sin.sin_port = port; 354 } 355 356 rpc_msg_init(&mnt_msg, MOUNTPROG, MOUNTVERS, (unsigned long) 0); 357 len = make_rpc_packet(iobuf, sizeof(iobuf), proc, 358 &mnt_msg, &fp->fh_path, xdr_nfspath, nfs_auth); 359 360 /* 361 * XXX EVIL! We cast fh_id to a pointer, then back to an int 362 * XXX later. 363 */ 364 if (len > 0) { 365 error = fwd_packet(MK_RPC_XID(RPC_XID_MOUNTD, fp->fh_id), 366 iobuf, len, &fp->fh_sin, &fp->fh_sin, 367 (void *)((long)fp->fh_id), f); 368 } else { 369 error = -len; 370 } 371 /* 372 * It may be the case that we're sending to the wrong MOUNTD port. This 373 * occurs if mountd is restarted on the server after the port has been 374 * looked up and stored in the filehandle cache somewhere. The correct 375 * solution, if we're going to cache port numbers is to catch the ICMP 376 * port unreachable reply from the server and cause the portmap request 377 * to be redone. The quick solution here is to invalidate the MOUNTD 378 * port. 379 */ 380 fp->fh_sin.sin_port = 0; 381 382 return error; 383 } 384 385 /*-------------------------------------------------------------------------*/ 386 387 /* 388 * NFS needs the local filesystem, remote filesystem 389 * remote hostname. 390 * Local filesystem defaults to remote and vice-versa. 391 */ 392 static char * 393 nfs_match(am_opts *fo) 394 { 395 char *xmtab; 396 if (fo->opt_fs && !fo->opt_rfs) 397 fo->opt_rfs = fo->opt_fs; 398 if (!fo->opt_rfs) { 399 plog(XLOG_USER, "nfs: no remote filesystem specified"); 400 return FALSE; 401 } 402 if (!fo->opt_rhost) { 403 plog(XLOG_USER, "nfs: no remote host specified"); 404 return FALSE; 405 } 406 /* 407 * Determine magic cookie to put in mtab 408 */ 409 xmtab = xmalloc(strlen(fo->opt_rhost) + strlen(fo->opt_rfs) + 2); 410 snprintf(xmtab, strlen(fo->opt_rhost) + strlen(fo->opt_rfs) + 2, 411 "%s:%s", fo->opt_rhost, fo->opt_rfs); 412 #ifdef DEBUG 413 dlog("NFS: mounting remote server \"%s\", remote fs \"%s\" on \"%s\"", 414 fo->opt_rhost, fo->opt_rfs, fo->opt_fs); 415 #endif /* DEBUG */ 416 417 return xmtab; 418 } 419 420 /* 421 * Initialise am structure for nfs 422 */ 423 static int 424 nfs_init(mntfs *mf) 425 { 426 if (!mf->mf_private) { 427 int error; 428 fhstatus fhs; 429 430 char *colon = strchr(mf->mf_info, ':'); 431 if (colon == 0) 432 return ENOENT; 433 434 error = prime_nfs_fhandle_cache(colon+1, mf->mf_server, 435 &fhs, mf); 436 if (!error) { 437 mf->mf_private = ALLOC(fhstatus); 438 mf->mf_prfree = free; 439 bcopy(&fhs, mf->mf_private, sizeof(fhs)); 440 } 441 return error; 442 } 443 444 return 0; 445 } 446 447 int 448 mount_nfs_fh(fhstatus *fhp, char *dir, char *fs_name, char *opts, 449 mntfs *mf) 450 { 451 struct nfs_args nfs_args; 452 struct mntent mnt; 453 int retry; 454 char *colon; 455 /*char *path;*/ 456 char host[MAXHOSTNAMELEN + MAXPATHLEN + 2]; 457 fserver *fs = mf->mf_server; 458 int flags; 459 char *xopts; 460 int error; 461 #ifdef notdef 462 unsigned short port; 463 #endif /* notdef */ 464 465 const char *type = MOUNT_NFS; 466 467 bzero(&nfs_args, sizeof(nfs_args)); /* Paranoid */ 468 469 /* 470 * Extract host name to give to kernel 471 */ 472 if (!(colon = strchr(fs_name, ':'))) 473 return ENOENT; 474 strlcpy(host, fs_name, sizeof(host)); 475 /*path = colon + 1;*/ 476 477 if (mf->mf_remopts && *mf->mf_remopts && !islocalnet(fs->fs_ip->sin_addr.s_addr)) 478 xopts = strdup(mf->mf_remopts); 479 else 480 xopts = strdup(opts); 481 482 bzero(&nfs_args, sizeof(nfs_args)); 483 484 mnt.mnt_dir = dir; 485 mnt.mnt_fsname = fs_name; 486 mnt.mnt_type = "nfs"; 487 mnt.mnt_opts = xopts; 488 mnt.mnt_freq = 0; 489 mnt.mnt_passno = 0; 490 491 retry = hasmntval(&mnt, "retry"); 492 if (retry <= 0) 493 retry = 1; /* XXX */ 494 495 /*again:*/ 496 497 /* 498 * set mount args 499 */ 500 nfs_args.fh = (void *)fhp->fhs_fhandle; 501 nfs_args.fhsize = fhp->fhs_size; 502 nfs_args.version = NFS_ARGSVERSION; 503 504 nfs_args.hostname = host; 505 #ifdef HOSTNAMESZ 506 /* 507 * Most kernels have a name length restriction. 508 */ 509 if (strlen(host) >= HOSTNAMESZ) 510 strlcpy(host + HOSTNAMESZ - 3, "..", sizeof host - HOSTNAMESZ + 3); 511 #endif /* HOSTNAMESZ */ 512 513 if ((nfs_args.rsize = hasmntval(&mnt, "rsize"))) 514 nfs_args.flags |= NFSMNT_RSIZE; 515 516 #ifdef NFSMNT_READDIRSIZE 517 if ((nfs_args.readdirsize = hasmntval(&mnt, "readdirsize"))) { 518 nfs_args.flags |= NFSMNT_READDIRSIZE; 519 } else if (nfs_args.rsize) { 520 nfs_args.readdirsize = nfs_args.rsize; 521 nfs_args.flags |= NFSMNT_READDIRSIZE; 522 } 523 #endif 524 525 if ((nfs_args.wsize = hasmntval(&mnt, "wsize"))) 526 nfs_args.flags |= NFSMNT_WSIZE; 527 528 if ((nfs_args.timeo = hasmntval(&mnt, "timeo"))) 529 nfs_args.flags |= NFSMNT_TIMEO; 530 531 if ((nfs_args.retrans = hasmntval(&mnt, "retrans"))) 532 nfs_args.flags |= NFSMNT_RETRANS; 533 534 #ifdef NFSMNT_BIODS 535 if ((nfs_args.biods = hasmntval(&mnt, "biods"))) 536 nfs_args.flags |= NFSMNT_BIODS; 537 538 #endif /* NFSMNT_BIODS */ 539 540 #ifdef NFSMNT_MAXGRPS 541 if ((nfs_args.maxgrouplist = hasmntval(&mnt, "maxgroups"))) 542 nfs_args.flags |= NFSMNT_MAXGRPS; 543 #endif /* NFSMNT_MAXGRPS */ 544 545 #ifdef NFSMNT_READAHEAD 546 if ((nfs_args.readahead = hasmntval(&mnt, "readahead"))) 547 nfs_args.flags |= NFSMNT_READAHEAD; 548 #endif /* NFSMNT_READAHEAD */ 549 550 #ifdef notdef 551 /* 552 * This isn't supported by the ping algorithm yet. 553 * In any case, it is all done in nfs_init(). 554 */ 555 if ((port = hasmntval(&mnt, "port"))) 556 sin.sin_port = htons(port); 557 else 558 sin.sin_port = htons(NFS_PORT); /* XXX should use portmapper */ 559 #endif /* notdef */ 560 561 if (hasmntopt(&mnt, "soft") != NULL) 562 nfs_args.flags |= NFSMNT_SOFT; 563 564 #ifdef NFSMNT_SPONGY 565 if (hasmntopt(&mnt, "spongy") != NULL) { 566 nfs_args.flags |= NFSMNT_SPONGY; 567 if (nfs_args.flags & NFSMNT_SOFT) { 568 plog(XLOG_USER, "Mount opts soft and spongy are incompatible - soft ignored"); 569 nfs_args.flags &= ~NFSMNT_SOFT; 570 } 571 } 572 #endif /* MNTOPT_SPONGY */ 573 574 if (hasmntopt(&mnt, "intr") != NULL) 575 nfs_args.flags |= NFSMNT_INT; 576 577 #ifdef MNTOPT_NODEVS 578 if (hasmntopt(&mnt, MNTOPT_NODEVS) != NULL) 579 nfs_args.flags |= NFSMNT_NODEVS; 580 #endif /* MNTOPT_NODEVS */ 581 582 583 if (hasmntopt(&mnt, "noconn") != NULL) 584 nfs_args.flags |= NFSMNT_NOCONN; 585 586 if (hasmntopt(&mnt, "resvport") != NULL) 587 nfs_args.flags |= NFSMNT_RESVPORT; 588 589 #ifdef NFSMNT_PGTHRESH 590 if ((nfs_args.pg_thresh = hasmntval(&mnt, "pgthresh"))) 591 nfs_args.flags |= NFSMNT_PGTHRESH; 592 #endif /* NFSMNT_PGTHRESH */ 593 594 nfs_args.addr = (struct sockaddr *)fs->fs_ip; 595 nfs_args.addrlen = sizeof(*fs->fs_ip); 596 nfs_args.sotype = SOCK_DGRAM; 597 nfs_args.proto = 0; 598 599 flags = compute_mount_flags(&mnt); 600 601 #ifdef NFSMNT_NOCTO 602 if (hasmntopt(&mnt, "nocto") != NULL) 603 nfs_args.flags |= NFSMNT_NOCTO; 604 #endif /* NFSMNT_NOCTO */ 605 606 if (hasmntopt(&mnt, "tcp") != NULL) 607 nfs_args.sotype = SOCK_STREAM; 608 609 610 611 error = mount_fs(&mnt, flags, (caddr_t) &nfs_args, retry, type); 612 free(xopts); 613 return error; 614 } 615 616 static int 617 mount_nfs(char *dir, char *fs_name, char *opts, mntfs *mf) 618 { 619 #ifdef notdef 620 int error; 621 fhstatus fhs; 622 char *colon; 623 624 if (!(colon = strchr(fs_name, ':'))) 625 return ENOENT; 626 627 #ifdef DEBUG 628 dlog("locating fhandle for %s", fs_name); 629 #endif /* DEBUG */ 630 error = prime_nfs_fhandle_cache(colon+1, mf->mf_server, &fhs, NULL); 631 632 if (error) 633 return error; 634 635 return mount_nfs_fh(&fhs, dir, fs_name, opts, mf); 636 #endif 637 if (!mf->mf_private) { 638 plog(XLOG_ERROR, "Missing filehandle for %s", fs_name); 639 return EINVAL; 640 } 641 642 return mount_nfs_fh((fhstatus *) mf->mf_private, dir, fs_name, opts, mf); 643 } 644 645 static int 646 nfs_fmount(mntfs *mf) 647 { 648 int error; 649 650 error = mount_nfs(mf->mf_mount, mf->mf_info, mf->mf_mopts, mf); 651 652 #ifdef DEBUG 653 if (error) { 654 errno = error; 655 dlog("mount_nfs: %m"); 656 } 657 #endif /* DEBUG */ 658 return error; 659 } 660 661 static int 662 nfs_fumount(mntfs *mf) 663 { 664 return (umount_fs(mf->mf_mount)); 665 } 666 667 static void 668 nfs_umounted(am_node *mp) 669 { 670 671 #ifdef KICK_KERNEL 672 /* This should go into the mainline code, not in nfs_ops... */ 673 674 /* 675 * Run lstat over the underlying directory in 676 * case this was a direct mount. This will 677 * get the kernel back in sync with reality. 678 */ 679 if (mp->am_parent && mp->am_parent->am_path && 680 STREQ(mp->am_parent->am_mnt->mf_ops->fs_type, "direct")) { 681 struct stat stb; 682 pid_t pid; 683 if ((pid = background()) == 0) { 684 if (lstat(mp->am_parent->am_path, &stb) < 0) { 685 plog(XLOG_ERROR, "lstat(%s) after unmount: %m", mp->am_parent->am_path); 686 #ifdef DEBUG 687 } else { 688 dlog("hack lstat(%s): ok", mp->am_parent->am_path); 689 #endif /* DEBUG */ 690 } 691 _exit(0); 692 } 693 } 694 #endif /* KICK_KERNEL */ 695 } 696 697 /* 698 * Network file system 699 */ 700 am_ops nfs_ops = { 701 "nfs", 702 nfs_match, 703 nfs_init, 704 auto_fmount, 705 nfs_fmount, 706 auto_fumount, 707 nfs_fumount, 708 efs_lookuppn, 709 efs_readdir, 710 0, /* nfs_readlink */ 711 0, /* nfs_mounted */ 712 nfs_umounted, 713 find_nfs_srvr, 714 FS_MKMNT|FS_BACKGROUND|FS_AMQINFO 715 }; 716 717 #endif /* HAS_NFS */ 718