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