1 /* $OpenBSD: nfs_ops.c,v 1.28 2024/04/23 13:34:51 jsg 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 *
find_nfs_fhandle_cache(void * idv,int done)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
got_nfs_fh(void * pkt,int len,struct sockaddr_in * sa,struct sockaddr_in * ia,void * idv,int done)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
flush_nfs_fhandle_cache(fserver * fs)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
discard_fh(void * arg)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
prime_nfs_fhandle_cache(char * path,fserver * fs,fhstatus * fhbuf,void * wchan)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
make_nfs_auth(void)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
call_mountd(fh_cache * fp,u_long proc,fwd_fun f,void * wchan)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 *
nfs_match(am_opts * fo)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
nfs_init(mntfs * mf)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
mount_nfs_fh(fhstatus * fhp,char * dir,char * fs_name,char * opts,mntfs * mf)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[HOST_NAME_MAX+1 + PATH_MAX+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
mount_nfs(char * dir,char * fs_name,char * opts,mntfs * mf)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
nfs_fmount(mntfs * mf)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
nfs_fumount(mntfs * mf)662 nfs_fumount(mntfs *mf)
663 {
664 return (umount_fs(mf->mf_mount));
665 }
666
667 static void
nfs_umounted(am_node * mp)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