xref: /original-bsd/usr.sbin/amd/amd/nfs_ops.c (revision 556ca7e8)
1 /*
2  * $Id: nfs_ops.c,v 5.2.1.4 91/03/17 17:45:55 jsp Alpha $
3  *
4  * Copyright (c) 1990 Jan-Simon Pendry
5  * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
6  * Copyright (c) 1990 The Regents of the University of California.
7  * 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  * %sccs.include.redist.c%
13  *
14  *	@(#)nfs_ops.c	5.2 (Berkeley) 03/17/91
15  */
16 
17 #include "am.h"
18 
19 #ifdef HAS_NFS
20 
21 #define NFS
22 #define NFSCLIENT
23 #ifdef NFS_3
24 typedef nfs_fh fhandle_t;
25 #endif /* NFS_3 */
26 #ifdef NFS_HDR
27 #include NFS_HDR
28 #endif /* NFS_HDR */
29 #include <sys/mount.h>
30 #include "mount.h"
31 
32 /*
33  * Network file system
34  */
35 
36 /*
37  * Convert from nfsstat to UN*X error code
38  */
39 #define unx_error(e)	((int)(e))
40 
41 /*
42  * The NFS layer maintains a cache of file handles.
43  * This is *fundamental* to the implementation and
44  * also allows quick remounting when a filesystem
45  * is accessed soon after timing out.
46  *
47  * The NFS server layer knows to flush this cache
48  * when a server goes down so avoiding stale handles.
49  *
50  * Each cache entry keeps a hard reference to
51  * the corresponding server.  This ensures that
52  * the server keepalive information is maintained.
53  *
54  * The copy of the sockaddr_in here is taken so
55  * that the port can be twiddled to talk to mountd
56  * instead of portmap or the NFS server as used
57  * elsewhere.
58  * The port# is flushed if a server goes down.
59  * The IP address is never flushed - we assume
60  * that the address of a mounted machine never
61  * changes.  If it does, then you have other
62  * problems...
63  */
64 typedef struct fh_cache fh_cache;
65 struct fh_cache {
66 	qelem	fh_q;			/* List header */
67 	voidp	fh_wchan;		/* Wait channel */
68 	int	fh_error;		/* Valid data? */
69 	int	fh_id;			/* Unique id */
70 	int	fh_cid;			/* Callout id */
71 	struct fhstatus fh_handle;	/* Handle on filesystem */
72 	struct sockaddr_in fh_sin;	/* Address of mountd */
73 	fserver *fh_fs;			/* Server holding filesystem */
74 	char	*fh_path;		/* Filesystem on host */
75 };
76 
77 /*
78  * FH_TTL is the time a file handle will remain in the cache since
79  * last being used.  If the file handle becomes invalid, then it
80  * will be flushed anyway.
81  */
82 #define	FH_TTL		(5 * 60)		/* five minutes */
83 #define	FH_TTL_ERROR	(30)			/* 30 seconds */
84 
85 static int fh_id = 0;
86 #define	FHID_ALLOC()	(++fh_id)
87 extern qelem fh_head;
88 qelem fh_head = { &fh_head, &fh_head };
89 
90 static int call_mountd P((fh_cache*, unsigned long, fwd_fun, voidp));
91 
92 AUTH *nfs_auth;
93 
94 static fh_cache *find_nfs_fhandle_cache P((voidp idv, int done));
95 static fh_cache *find_nfs_fhandle_cache(idv, done)
96 voidp idv;
97 int done;
98 {
99 	fh_cache *fp, *fp2 = 0;
100 	int id = (int) idv;
101 
102 	ITER(fp, fh_cache, &fh_head) {
103 		if (fp->fh_id == id) {
104 			fp2 = fp;
105 			break;
106 		}
107 	}
108 
109 #ifdef DEBUG
110 	if (fp2) {
111 		dlog("fh cache gives fp %#x, fs %s", fp2, fp2->fh_path);
112 	} else {
113 		dlog("fh cache search failed");
114 	}
115 #endif /* DEBUG */
116 
117 	if (fp2 && !done) {
118 		fp2->fh_error = ETIMEDOUT;
119 		return 0;
120 	}
121 
122 	return fp2;
123 }
124 
125 /*
126  * Called when a filehandle appears
127  */
128 static void got_nfs_fh P((voidp pkt, int len, struct sockaddr_in *sa,
129 				struct sockaddr_in *ia, voidp idv, int done));
130 static void got_nfs_fh(pkt, len, sa, ia, idv, done)
131 voidp pkt;
132 int len;
133 struct sockaddr_in *sa, *ia;
134 voidp idv;
135 int done;
136 {
137 	fh_cache *fp = find_nfs_fhandle_cache(idv, done);
138 	if (fp) {
139 		fp->fh_error = pickup_rpc_reply(pkt, len, (voidp) &fp->fh_handle, xdr_fhstatus);
140 		if (!fp->fh_error) {
141 #ifdef DEBUG
142 			dlog("got filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
143 #endif /* DEBUG */
144 			/*
145 			 * Wakeup anything sleeping on this filehandle
146 			 */
147 			if (fp->fh_wchan) {
148 #ifdef DEBUG
149 				dlog("Calling wakeup on %#x", fp->fh_wchan);
150 #endif /* DEBUG */
151 				wakeup(fp->fh_wchan);
152 			}
153 		}
154 	}
155 }
156 
157 void flush_nfs_fhandle_cache P((fserver *fs));
158 void flush_nfs_fhandle_cache(fs)
159 fserver *fs;
160 {
161 	fh_cache *fp;
162 	ITER(fp, fh_cache, &fh_head) {
163 		if (fp->fh_fs == fs || fs == 0) {
164 			fp->fh_sin.sin_port = (u_short) 0;
165 			fp->fh_error = -1;
166 		}
167 	}
168 }
169 
170 static void discard_fh P((fh_cache *fp));
171 static void discard_fh(fp)
172 fh_cache *fp;
173 {
174 	rem_que(&fp->fh_q);
175 #ifdef DEBUG
176 	dlog("Discarding filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
177 #endif /* DEBUG */
178 	free_srvr(fp->fh_fs);
179 	free((voidp) fp->fh_path);
180 	free((voidp) fp);
181 }
182 
183 /*
184  * Determine the file handle for a node
185  */
186 static int prime_nfs_fhandle_cache P((char *path, fserver *fs, struct fhstatus *fhbuf, voidp wchan));
187 static int prime_nfs_fhandle_cache(path, fs, fhbuf, wchan)
188 char *path;
189 fserver *fs;
190 struct fhstatus *fhbuf;
191 voidp wchan;
192 {
193 	fh_cache *fp, *fp_save = 0;
194 	int error;
195 	int reuse_id = FALSE;
196 
197 #ifdef DEBUG
198 	dlog("Searching cache for %s:%s", fs->fs_host, path);
199 #endif /* DEBUG */
200 
201 	/*
202 	 * First search the cache
203 	 */
204 	ITER(fp, fh_cache, &fh_head) {
205 		if (fs == fp->fh_fs && strcmp(path, fp->fh_path) == 0) {
206 			switch (fp->fh_error) {
207 			case 0:
208 				error = fp->fh_error = unx_error(fp->fh_handle.fhs_status);
209 				if (error == 0) {
210 					if (fhbuf)
211 						bcopy((voidp) &fp->fh_handle, (voidp) fhbuf,
212 							sizeof(fp->fh_handle));
213 					if (fp->fh_cid)
214 						untimeout(fp->fh_cid);
215 					fp->fh_cid = timeout(FH_TTL, discard_fh, (voidp) fp);
216 				} else if (error == EACCES) {
217 					/*
218 					 * Now decode the file handle return code.
219 					 */
220 					plog(XLOG_INFO, "Filehandle denied for \"%s:%s\"",
221 						fs->fs_host, path);
222 				} else {
223 					errno = error;	/* XXX */
224 					plog(XLOG_INFO, "Filehandle error for \"%s:%s\": %m",
225 						fs->fs_host, path);
226 				}
227 
228 				/*
229 				 * The error was returned from the remote mount daemon.
230 				 * Policy: this error will be cached for now...
231 				 */
232 				return error;
233 
234 			case -1:
235 				/*
236 				 * Still thinking about it, but we can re-use.
237 				 */
238 				fp_save = fp;
239 				reuse_id = TRUE;
240 				break;
241 
242 			default:
243 				/*
244 				 * Return the error.
245 				 * Policy: make sure we recompute if required again
246 				 * in case this was caused by a network failure.
247 				 * This can thrash mountd's though...  If you find
248 				 * your mountd going slowly then:
249 				 * 1.  Add a fork() loop to main.
250 				 * 2.  Remove the call to innetgr() and don't use
251 				 *     netgroups, especially if you don't use YP.
252 				 */
253 				error = fp->fh_error;
254 				fp->fh_error = -1;
255 				return error;
256 			}
257 			break;
258 		}
259 	}
260 
261 	/*
262 	 * Not in cache
263 	 */
264 	if (fp_save) {
265 		fp = fp_save;
266 		/*
267 		 * Re-use existing slot
268 		 */
269 		untimeout(fp->fh_cid);
270 		free_srvr(fp->fh_fs);
271 		free(fp->fh_path);
272 	} else {
273 		fp = ALLOC(fh_cache);
274 		bzero((voidp) fp, sizeof(*fp));
275 		ins_que(&fp->fh_q, &fh_head);
276 	}
277 	if (!reuse_id)
278 		fp->fh_id = FHID_ALLOC();
279 	fp->fh_wchan = wchan;
280 	fp->fh_error = -1;
281 	fp->fh_cid = timeout(FH_TTL, discard_fh, (voidp) fp);
282 
283 	/*
284 	 * If the address has changed then don't try to re-use the
285 	 * port information
286 	 */
287 	if (fp->fh_sin.sin_addr.s_addr != fs->fs_ip->sin_addr.s_addr) {
288 		fp->fh_sin = *fs->fs_ip;
289 		fp->fh_sin.sin_port = 0;
290 	}
291 	fp->fh_fs = dup_srvr(fs);
292 	fp->fh_path = strdup(path);
293 
294 	error = call_mountd(fp, MOUNTPROC_MNT, got_nfs_fh, wchan);
295 	if (error) {
296 		/*
297 		 * Local error - cache for a short period
298 		 * just to prevent thrashing.
299 		 */
300 		untimeout(fp->fh_cid);
301 		fp->fh_cid = timeout(error < 0 ? 2 * ALLOWED_MOUNT_TIME : FH_TTL_ERROR,
302 						discard_fh, (voidp) fp);
303 		fp->fh_error = error;
304 	} else {
305 		error = fp->fh_error;
306 	}
307 	return error;
308 }
309 
310 static int call_mountd P((fh_cache *fp, u_long proc, fwd_fun f, voidp wchan));
311 static int call_mountd(fp, proc, f, wchan)
312 fh_cache *fp;
313 u_long proc;
314 fwd_fun f;
315 voidp wchan;
316 {
317 	struct rpc_msg mnt_msg;
318 	int len;
319 	char iobuf[8192];
320 	int error;
321 
322 	if (!nfs_auth) {
323 		nfs_auth = authunix_create_default();
324 		if (!nfs_auth)
325 			return ENOBUFS;
326 	}
327 
328 	if (fp->fh_sin.sin_port == 0) {
329 		u_short port;
330 		error = nfs_srvr_port(fp->fh_fs, &port, wchan);
331 		if (error)
332 			return error;
333 		fp->fh_sin.sin_port = port;
334 	}
335 
336 	rpc_msg_init(&mnt_msg, MOUNTPROG, MOUNTVERS, (unsigned long) 0);
337 	len = make_rpc_packet(iobuf, sizeof(iobuf), proc,
338 			&mnt_msg, (voidp) &fp->fh_path, xdr_nfspath,  nfs_auth);
339 
340 	if (len > 0) {
341 		error = fwd_packet(MK_RPC_XID(RPC_XID_MOUNTD, fp->fh_id),
342 			(voidp) iobuf, len, &fp->fh_sin, &fp->fh_sin, (voidp) fp->fh_id, f);
343 	} else {
344 		error = -len;
345 	}
346 	return error;
347 }
348 
349 /*-------------------------------------------------------------------------*/
350 
351 /*
352  * NFS needs the local filesystem, remote filesystem
353  * remote hostname.
354  * Local filesystem defaults to remote and vice-versa.
355  */
356 static char *nfs_match(fo)
357 am_opts *fo;
358 {
359 	char *xmtab;
360 	if (fo->opt_fs && !fo->opt_rfs)
361 		fo->opt_rfs = fo->opt_fs;
362 	if (!fo->opt_rfs) {
363 		plog(XLOG_USER, "nfs: no remote filesystem specified");
364 		return FALSE;
365 	}
366 	if (!fo->opt_rhost) {
367 		plog(XLOG_USER, "nfs: no remote host specified");
368 		return FALSE;
369 	}
370 	/*
371 	 * Determine magic cookie to put in mtab
372 	 */
373 	xmtab = (char *) xmalloc(strlen(fo->opt_rhost) + strlen(fo->opt_rfs) + 2);
374 	sprintf(xmtab, "%s:%s", fo->opt_rhost, fo->opt_rfs);
375 #ifdef DEBUG
376 	dlog("NFS: mounting remote server \"%s\", remote fs \"%s\" on \"%s\"",
377 		fo->opt_rhost, fo->opt_rfs, fo->opt_fs);
378 #endif /* DEBUG */
379 
380 	return xmtab;
381 }
382 
383 /*
384  * Initialise am structure for nfs
385  */
386 static int nfs_init(mf)
387 mntfs *mf;
388 {
389 	if (!mf->mf_private) {
390 		int error;
391 		struct fhstatus fhs;
392 
393 		char *colon = strchr(mf->mf_info, ':');
394 		if (colon == 0)
395 			return ENOENT;
396 
397 		error = prime_nfs_fhandle_cache(colon+1, mf->mf_server, &fhs, (voidp) mf);
398 		if (!error) {
399 			mf->mf_private = (voidp) ALLOC(fhstatus);
400 			mf->mf_prfree = (void (*)()) free;
401 			bcopy((voidp) &fhs, mf->mf_private, sizeof(fhs));
402 		}
403 		return error;
404 	}
405 
406 	return 0;
407 }
408 
409 int mount_nfs_fh(fhp, dir, fs_name, opts, mf)
410 struct fhstatus *fhp;
411 char *dir;
412 char *fs_name;
413 char *opts;
414 mntfs *mf;
415 {
416 	struct nfs_args nfs_args;
417 	struct mntent mnt;
418 	int retry;
419 	char *colon;
420 	/*char *path;*/
421 	char host[MAXHOSTNAMELEN + MAXPATHLEN + 2];
422 	fserver *fs = mf->mf_server;
423 	int flags;
424 #ifdef notdef
425 	unsigned short port;
426 #endif /* notdef */
427 
428 	MTYPE_TYPE type = MOUNT_TYPE_NFS;
429 
430 	bzero((voidp) &nfs_args, sizeof(nfs_args));	/* Paranoid */
431 
432 	/*
433 	 * Extract host name to give to kernel
434 	 */
435 	if (!(colon = strchr(fs_name, ':')))
436 		return ENOENT;
437 #ifndef NFS_ARGS_NEEDS_PATH
438 	*colon = '\0';
439 #endif
440 	strncpy(host, fs_name, sizeof(host));
441 #ifndef NFS_ARGS_NEEDS_PATH
442 	*colon = ':';
443 #endif /* NFS_ARGS_NEEDS_PATH */
444 	/*path = colon + 1;*/
445 
446 	bzero((voidp) &nfs_args, sizeof(nfs_args));
447 
448 	mnt.mnt_dir = dir;
449 	mnt.mnt_fsname = fs_name;
450 	mnt.mnt_type = MTAB_TYPE_NFS;
451 	mnt.mnt_opts = opts;
452 	mnt.mnt_freq = 0;
453 	mnt.mnt_passno = 0;
454 
455 	retry = hasmntval(&mnt, "retry");
456 	if (retry <= 0)
457 		retry = 1;	/* XXX */
458 
459 /*again:*/
460 
461 	/*
462 	 * set mount args
463 	 */
464 	NFS_FH_DREF(nfs_args.fh, (NFS_FH_TYPE) fhp->fhstatus_u.fhs_fhandle);
465 
466 #ifdef ULTRIX_HACK
467 	nfs_args.optstr = mnt.mnt_opts;
468 #endif /* ULTRIX_HACK */
469 
470 	nfs_args.hostname = host;
471 	nfs_args.flags |= NFSMNT_HOSTNAME;
472 #ifdef HOSTNAMESZ
473 	/*
474 	 * Most kernels have a name length restriction.
475 	 */
476 	if (strlen(host) >= HOSTNAMESZ)
477 		strcpy(host + HOSTNAMESZ - 3, "..");
478 #endif /* HOSTNAMESZ */
479 
480 	if (nfs_args.rsize = hasmntval(&mnt, "rsize"))
481 		nfs_args.flags |= NFSMNT_RSIZE;
482 
483 	if (nfs_args.wsize = hasmntval(&mnt, "wsize"))
484 		nfs_args.flags |= NFSMNT_WSIZE;
485 
486 	if (nfs_args.timeo = hasmntval(&mnt, "timeo"))
487 		nfs_args.flags |= NFSMNT_TIMEO;
488 
489 	if (nfs_args.retrans = hasmntval(&mnt, "retrans"))
490 		nfs_args.flags |= NFSMNT_RETRANS;
491 
492 #ifdef NFSMNT_BIODS
493 	if (nfs_args.biods = hasmntval(&mnt, "biods"))
494 		nfs_args.flags |= NFSMNT_BIODS;
495 
496 #endif /* NFSMNT_BIODS */
497 
498 #ifdef notdef
499 /*
500  * This isn't supported by the ping algorithm yet.
501  * In any case, it is all done in nfs_init().
502  */
503  	if (port = hasmntval(&mnt, "port"))
504 		sin.sin_port = htons(port);
505 	else
506 		sin.sin_port = htons(NFS_PORT);	/* XXX should use portmapper */
507 #endif /* notdef */
508 
509 	if (hasmntopt(&mnt, MNTOPT_SOFT) != NULL)
510 		nfs_args.flags |= NFSMNT_SOFT;
511 
512 #ifdef NFSMNT_SPONGY
513 	if (hasmntopt(&mnt, "spongy") != NULL) {
514 		nfs_args.flags |= NFSMNT_SPONGY;
515 		if (nfs_args.flags & NFSMNT_SOFT) {
516 			plog(XLOG_USER, "Mount opts soft and spongy are incompatible - soft ignored");
517 			nfs_args.flags &= ~NFSMNT_SOFT;
518 		}
519 	}
520 #endif /* MNTOPT_SPONGY */
521 
522 #ifdef MNTOPT_INTR
523 	if (hasmntopt(&mnt, MNTOPT_INTR) != NULL)
524 		nfs_args.flags |= NFSMNT_INT;
525 #endif /* MNTOPT_INTR */
526 
527 #ifdef MNTOPT_NODEVS
528 	if (hasmntopt(&mnt, MNTOPT_NODEVS) != NULL)
529 		nfs_args.flags |= NFSMNT_NODEVS;
530 #endif /* MNTOPT_NODEVS */
531 
532 #ifdef MNTOPT_COMPRESS
533 	if (hasmntopt(&mnt, "compress") != NULL)
534 		nfs_args.flags |= NFSMNT_COMPRESS;
535 #endif /* MNTOPT_COMPRESS */
536 
537 #ifdef MNTOPT_NOCONN
538 	if (hasmntopt(&mnt, "noconn") != NULL)
539 		nfs_args.flags |= NFSMNT_NOCONN;
540 #endif /* MNTOPT_NOCONN */
541 
542 #ifdef NFSMNT_PGTHRESH
543 	if (nfs_args.pg_thresh = hasmntval(&mnt, "pgthresh"))
544 		nfs_args.flags |= NFSMNT_PGTHRESH;
545 #endif /* NFSMNT_PGTHRESH */
546 
547 	NFS_SA_DREF(nfs_args, fs->fs_ip);
548 
549 	flags = compute_mount_flags(&mnt);
550 
551 #ifdef NFSMNT_NOCTO
552 	if (hasmntopt(&mnt, "nocto") != NULL)
553 		nfs_args.flags |= NFSMNT_NOCTO;
554 #endif /* NFSMNT_NOCTO */
555 
556 #ifdef HAS_TCP_NFS
557 	if (hasmntopt(&mnt, "tcp") != NULL)
558 		nfs_args.sotype = SOCK_STREAM;
559 #endif /* HAS_TCP_NFS */
560 
561 
562 #ifdef ULTRIX_HACK
563 	/*
564 	 * Ultrix passes the flags argument as part of the
565 	 * mount data structure, rather than using the
566 	 * flags argument to the system call.  This is
567 	 * confusing...
568 	 */
569 	if (!(nfs_args.flags & NFSMNT_PGTHRESH)) {
570 		nfs_args.pg_thresh = 64; /* 64k - XXX */
571 		nfs_args.flags |= NFSMNT_PGTHRESH;
572 	}
573 	nfs_args.gfs_flags = flags;
574 	flags &= M_RDONLY;
575 	if (flags & M_RDONLY)
576 		nfs_args.flags |= NFSMNT_RONLY;
577 #endif /* ULTRIX_HACK */
578 
579 	return mount_fs(&mnt, flags, (caddr_t) &nfs_args, retry, type);
580 }
581 
582 static int mount_nfs(dir, fs_name, opts, mf)
583 char *dir;
584 char *fs_name;
585 char *opts;
586 mntfs *mf;
587 {
588 #ifdef notdef
589 	int error;
590 	struct fhstatus fhs;
591 	char *colon;
592 
593 	if (!(colon = strchr(fs_name, ':')))
594 		return ENOENT;
595 
596 #ifdef DEBUG
597 	dlog("locating fhandle for %s", fs_name);
598 #endif /* DEBUG */
599 	error = prime_nfs_fhandle_cache(colon+1, mf->mf_server, &fhs, (voidp) 0);
600 
601 	if (error)
602 		return error;
603 
604 	return mount_nfs_fh(&fhs, dir, fs_name, opts, mf);
605 #endif
606 	if (!mf->mf_private) {
607 		plog(XLOG_ERROR, "Missing filehandle for %s", fs_name);
608 		return EINVAL;
609 	}
610 
611 	return mount_nfs_fh((struct fhstatus *) mf->mf_private, dir, fs_name, opts, mf);
612 }
613 
614 static int nfs_fmount(mf)
615 mntfs *mf;
616 {
617 	int error;
618 
619 	error = mount_nfs(mf->mf_mount, mf->mf_info, mf->mf_mopts, mf);
620 
621 #ifdef DEBUG
622 	if (error) {
623 		errno = error;
624 		dlog("mount_nfs: %m");
625 	}
626 #endif /* DEBUG */
627 	return error;
628 }
629 
630 static int nfs_fumount(mf)
631 mntfs *mf;
632 {
633 	int error = UMOUNT_FS(mf->mf_mount);
634 	if (error)
635 		return error;
636 
637 	return 0;
638 }
639 
640 static void nfs_umounted(mp)
641 am_node *mp;
642 {
643 #ifdef INFORM_MOUNTD
644 	/*
645 	 * Don't bother to inform remote mountd
646 	 * that we are finished.  Until a full
647 	 * track of filehandles is maintained
648 	 * the mountd unmount callback cannot
649 	 * be done correctly anyway...
650 	 */
651 
652 	mntfs *mf = mp->am_mnt;
653 	fserver *fs;
654 	char *colon, *path;
655 
656 	if (mf->mf_error || mf->mf_refc > 1)
657 		return;
658 
659 	fs = mf->mf_server;
660 
661 	/*
662 	 * Call the mount daemon on the server to
663 	 * announce that we are not using the fs any more.
664 	 *
665 	 * This is *wrong*.  The mountd should be called
666 	 * when the fhandle is flushed from the cache, and
667 	 * a reference held to the cached entry while the
668 	 * fs is mounted...
669 	 */
670 	colon = path = strchr(mf->mf_info, ':');
671 	if (fs && colon) {
672 		fh_cache f;
673 #ifdef DEBUG
674 		dlog("calling mountd for %s", mf->mf_info);
675 #endif /* DEBUG */
676 		*path++ = '\0';
677 		f.fh_path = path;
678 		f.fh_sin = *fs->fs_ip;
679 		f.fh_sin.sin_port = (u_short) 0;
680 		f.fh_fs = fs;
681 		f.fh_id = 0;
682 		f.fh_error = 0;
683 		(void) prime_nfs_fhandle_cache(colon+1, mf->mf_server, (struct fhstatus *) 0, (voidp) mf);
684 		(void) call_mountd(&f, MOUNTPROC_UMNT, (fwd_fun) 0, (voidp) 0);
685 		*colon = ':';
686 	}
687 #endif /* INFORM_MOUNTD */
688 }
689 
690 /*
691  * Network file system
692  */
693 am_ops nfs_ops = {
694 	"nfs",
695 	nfs_match,
696 	nfs_init,
697 	auto_fmount,
698 	nfs_fmount,
699 	auto_fumount,
700 	nfs_fumount,
701 	efs_lookuppn,
702 	efs_readdir,
703 	0, /* nfs_readlink */
704 	0, /* nfs_mounted */
705 	nfs_umounted,
706 	find_nfs_srvr,
707 	FS_MKMNT|FS_BACKGROUND|FS_AMQINFO
708 };
709 
710 #endif /* HAS_NFS */
711