1/*	$NetBSD$	*/
2
3/*
4 * Copyright (c) 1997-2009 Erez Zadok
5 * Copyright (c) 1990 Jan-Simon Pendry
6 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
7 * Copyright (c) 1990 The Regents of the University of California.
8 * All rights reserved.
9 *
10 * This code is derived from software contributed to Berkeley by
11 * Jan-Simon Pendry at Imperial College, London.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 *    notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 *    notice, this list of conditions and the following disclaimer in the
20 *    documentation and/or other materials provided with the distribution.
21 * 3. All advertising materials mentioning features or use of this software
22 *    must display the following acknowledgment:
23 *      This product includes software developed by the University of
24 *      California, Berkeley and its contributors.
25 * 4. Neither the name of the University nor the names of its contributors
26 *    may be used to endorse or promote products derived from this software
27 *    without specific prior written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 * SUCH DAMAGE.
40 *
41 *
42 * File: am-utils/amd/ops_nfs.c
43 *
44 */
45
46/*
47 * Network file system
48 */
49
50#ifdef HAVE_CONFIG_H
51# include <config.h>
52#endif /* HAVE_CONFIG_H */
53#include <am_defs.h>
54#include <amd.h>
55
56/*
57 * Convert from nfsstat to UN*X error code
58 */
59#define unx_error(e)	((int)(e))
60
61/*
62 * FH_TTL is the time a file handle will remain in the cache since
63 * last being used.  If the file handle becomes invalid, then it
64 * will be flushed anyway.
65 */
66#define	FH_TTL			(5 * 60) /* five minutes */
67#define	FH_TTL_ERROR		(30) /* 30 seconds */
68#define	FHID_ALLOC()		(++fh_id)
69
70/*
71 * The NFS layer maintains a cache of file handles.
72 * This is *fundamental* to the implementation and
73 * also allows quick remounting when a filesystem
74 * is accessed soon after timing out.
75 *
76 * The NFS server layer knows to flush this cache
77 * when a server goes down so avoiding stale handles.
78 *
79 * Each cache entry keeps a hard reference to
80 * the corresponding server.  This ensures that
81 * the server keepalive information is maintained.
82 *
83 * The copy of the sockaddr_in here is taken so
84 * that the port can be twiddled to talk to mountd
85 * instead of portmap or the NFS server as used
86 * elsewhere.
87 * The port# is flushed if a server goes down.
88 * The IP address is never flushed - we assume
89 * that the address of a mounted machine never
90 * changes.  If it does, then you have other
91 * problems...
92 */
93typedef struct fh_cache fh_cache;
94struct fh_cache {
95  qelem			fh_q;		/* List header */
96  wchan_t		fh_wchan;	/* Wait channel */
97  int			fh_error;	/* Valid data? */
98  int			fh_id;		/* Unique id */
99  int			fh_cid;		/* Callout id */
100  u_long		fh_nfs_version;	/* highest NFS version on host */
101  am_nfs_handle_t	fh_nfs_handle;	/* Handle on filesystem */
102  int			fh_status;	/* Status of last rpc */
103  struct sockaddr_in	fh_sin;		/* Address of mountd */
104  fserver		*fh_fs;		/* Server holding filesystem */
105  char			*fh_path;	/* Filesystem on host */
106};
107
108/* forward definitions */
109static int nfs_init(mntfs *mf);
110static char *nfs_match(am_opts *fo);
111static int nfs_mount(am_node *am, mntfs *mf);
112static int nfs_umount(am_node *am, mntfs *mf);
113static void nfs_umounted(mntfs *mf);
114static int call_mountd(fh_cache *fp, u_long proc, fwd_fun f, wchan_t wchan);
115static int webnfs_lookup(fh_cache *fp, fwd_fun f, wchan_t wchan);
116static int fh_id = 0;
117
118/* globals */
119AUTH *nfs_auth;
120qelem fh_head = {&fh_head, &fh_head};
121
122/*
123 * Network file system operations
124 */
125am_ops nfs_ops =
126{
127  "nfs",
128  nfs_match,
129  nfs_init,
130  nfs_mount,
131  nfs_umount,
132  amfs_error_lookup_child,
133  amfs_error_mount_child,
134  amfs_error_readdir,
135  0,				/* nfs_readlink */
136  0,				/* nfs_mounted */
137  nfs_umounted,
138  find_nfs_srvr,
139  0,				/* nfs_get_wchan */
140  FS_MKMNT | FS_BACKGROUND | FS_AMQINFO,	/* nfs_fs_flags */
141#ifdef HAVE_FS_AUTOFS
142  AUTOFS_NFS_FS_FLAGS,
143#endif /* HAVE_FS_AUTOFS */
144};
145
146
147static fh_cache *
148find_nfs_fhandle_cache(opaque_t arg, int done)
149{
150  fh_cache *fp, *fp2 = NULL;
151  int id = (long) arg;		/* for 64-bit archs */
152
153  ITER(fp, fh_cache, &fh_head) {
154    if (fp->fh_id == id) {
155      fp2 = fp;
156      break;
157    }
158  }
159
160  if (fp2) {
161    dlog("fh cache gives fp %#lx, fs %s", (unsigned long) fp2, fp2->fh_path);
162  } else {
163    dlog("fh cache search failed");
164  }
165
166  if (fp2 && !done) {
167    fp2->fh_error = ETIMEDOUT;
168    return 0;
169  }
170
171  return fp2;
172}
173
174
175/*
176 * Called when a filehandle appears via the mount protocol
177 */
178static void
179got_nfs_fh_mount(voidp pkt, int len, struct sockaddr_in *sa, struct sockaddr_in *ia, opaque_t arg, int done)
180{
181  fh_cache *fp;
182  struct fhstatus res;
183#ifdef HAVE_FS_NFS3
184  struct am_mountres3 res3;
185#endif /* HAVE_FS_NFS3 */
186
187  fp = find_nfs_fhandle_cache(arg, done);
188  if (!fp)
189    return;
190
191  /*
192   * retrieve the correct RPC reply for the file handle, based on the
193   * NFS protocol version.
194   */
195#ifdef HAVE_FS_NFS3
196  if (fp->fh_nfs_version == NFS_VERSION3) {
197    memset(&res3, 0, sizeof(res3));
198    fp->fh_error = pickup_rpc_reply(pkt, len, (voidp) &res3,
199				    (XDRPROC_T_TYPE) xdr_am_mountres3);
200    fp->fh_status = unx_error(res3.fhs_status);
201    memset(&fp->fh_nfs_handle.v3, 0, sizeof(am_nfs_fh3));
202    fp->fh_nfs_handle.v3.am_fh3_length = res3.mountres3_u.mountinfo.fhandle.fhandle3_len;
203    memmove(fp->fh_nfs_handle.v3.am_fh3_data,
204	    res3.mountres3_u.mountinfo.fhandle.fhandle3_val,
205	    fp->fh_nfs_handle.v3.am_fh3_length);
206  } else {
207#endif /* HAVE_FS_NFS3 */
208    memset(&res, 0, sizeof(res));
209    fp->fh_error = pickup_rpc_reply(pkt, len, (voidp) &res,
210				    (XDRPROC_T_TYPE) xdr_fhstatus);
211    fp->fh_status = unx_error(res.fhs_status);
212    memmove(&fp->fh_nfs_handle.v2, &res.fhs_fh, NFS_FHSIZE);
213#ifdef HAVE_FS_NFS3
214  }
215#endif /* HAVE_FS_NFS3 */
216
217  if (!fp->fh_error) {
218    dlog("got filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
219  } else {
220    plog(XLOG_USER, "filehandle denied for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
221    /*
222     * Force the error to be EACCES. It's debatable whether it should be
223     * ENOENT instead, but the server really doesn't give us any clues, and
224     * EACCES is more in line with the "filehandle denied" message.
225     */
226    fp->fh_error = EACCES;
227  }
228
229  /*
230   * Wakeup anything sleeping on this filehandle
231   */
232  if (fp->fh_wchan) {
233    dlog("Calling wakeup on %#lx", (unsigned long) fp->fh_wchan);
234    wakeup(fp->fh_wchan);
235  }
236}
237
238
239/*
240 * Called when a filehandle appears via WebNFS
241 */
242static void
243got_nfs_fh_webnfs(voidp pkt, int len, struct sockaddr_in *sa, struct sockaddr_in *ia, opaque_t arg, int done)
244{
245  fh_cache *fp;
246  nfsdiropres res;
247#ifdef HAVE_FS_NFS3
248  am_LOOKUP3res res3;
249#endif /* HAVE_FS_NFS3 */
250
251  fp = find_nfs_fhandle_cache(arg, done);
252  if (!fp)
253    return;
254
255  /*
256   * retrieve the correct RPC reply for the file handle, based on the
257   * NFS protocol version.
258   */
259#ifdef HAVE_FS_NFS3
260  if (fp->fh_nfs_version == NFS_VERSION3) {
261    memset(&res3, 0, sizeof(res3));
262    fp->fh_error = pickup_rpc_reply(pkt, len, (voidp) &res3,
263				    (XDRPROC_T_TYPE) xdr_am_LOOKUP3res);
264    fp->fh_status = unx_error(res3.status);
265    memset(&fp->fh_nfs_handle.v3, 0, sizeof(am_nfs_fh3));
266    fp->fh_nfs_handle.v3.am_fh3_length = res3.res_u.ok.object.am_fh3_length;
267    memmove(fp->fh_nfs_handle.v3.am_fh3_data,
268	    res3.res_u.ok.object.am_fh3_data,
269	    fp->fh_nfs_handle.v3.am_fh3_length);
270  } else {
271#endif /* HAVE_FS_NFS3 */
272    memset(&res, 0, sizeof(res));
273    fp->fh_error = pickup_rpc_reply(pkt, len, (voidp) &res,
274				    (XDRPROC_T_TYPE) xdr_diropres);
275    fp->fh_status = unx_error(res.dr_status);
276    memmove(&fp->fh_nfs_handle.v2, &res.dr_u.dr_drok_u.drok_fhandle, NFS_FHSIZE);
277#ifdef HAVE_FS_NFS3
278  }
279#endif /* HAVE_FS_NFS3 */
280
281  if (!fp->fh_error) {
282    dlog("got filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
283  } else {
284    plog(XLOG_USER, "filehandle denied for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
285    /*
286     * Force the error to be EACCES. It's debatable whether it should be
287     * ENOENT instead, but the server really doesn't give us any clues, and
288     * EACCES is more in line with the "filehandle denied" message.
289     */
290    fp->fh_error = EACCES;
291  }
292
293  /*
294   * Wakeup anything sleeping on this filehandle
295   */
296  if (fp->fh_wchan) {
297    dlog("Calling wakeup on %#lx", (unsigned long) fp->fh_wchan);
298    wakeup(fp->fh_wchan);
299  }
300}
301
302
303void
304flush_nfs_fhandle_cache(fserver *fs)
305{
306  fh_cache *fp;
307
308  ITER(fp, fh_cache, &fh_head) {
309    if (fp->fh_fs == fs || fs == NULL) {
310      /*
311       * Only invalidate port info for non-WebNFS servers
312       */
313      if (!(fp->fh_fs->fs_flags & FSF_WEBNFS))
314	fp->fh_sin.sin_port = (u_short) 0;
315      fp->fh_error = -1;
316    }
317  }
318}
319
320
321static void
322discard_fh(opaque_t arg)
323{
324  fh_cache *fp = (fh_cache *) arg;
325
326  rem_que(&fp->fh_q);
327  if (fp->fh_fs) {
328    dlog("Discarding filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
329    free_srvr(fp->fh_fs);
330  }
331  if (fp->fh_path)
332    XFREE(fp->fh_path);
333  XFREE(fp);
334}
335
336
337/*
338 * Determine the file handle for a node
339 */
340static int
341prime_nfs_fhandle_cache(char *path, fserver *fs, am_nfs_handle_t *fhbuf, mntfs *mf)
342{
343  fh_cache *fp, *fp_save = NULL;
344  int error;
345  int reuse_id = FALSE;
346
347  dlog("Searching cache for %s:%s", fs->fs_host, path);
348
349  /*
350   * First search the cache
351   */
352  ITER(fp, fh_cache, &fh_head) {
353    if (fs != fp->fh_fs  ||  !STREQ(path, fp->fh_path))
354      continue;			/* skip to next ITER item */
355    /* else we got a match */
356    switch (fp->fh_error) {
357    case 0:
358      plog(XLOG_INFO, "prime_nfs_fhandle_cache: NFS version %d", (int) fp->fh_nfs_version);
359
360      error = fp->fh_error = fp->fh_status;
361
362      if (error == 0) {
363	if (mf->mf_flags & MFF_NFS_SCALEDOWN) {
364	  fp_save = fp;
365	  /* XXX: why reuse the ID? */
366	  reuse_id = TRUE;
367	  break;
368	}
369
370	if (fhbuf) {
371#ifdef HAVE_FS_NFS3
372	  if (fp->fh_nfs_version == NFS_VERSION3) {
373	    memmove((voidp) &(fhbuf->v3), (voidp) &(fp->fh_nfs_handle.v3),
374		    sizeof(fp->fh_nfs_handle.v3));
375	  } else
376#endif /* HAVE_FS_NFS3 */
377	    {
378	      memmove((voidp) &(fhbuf->v2), (voidp) &(fp->fh_nfs_handle.v2),
379		      sizeof(fp->fh_nfs_handle.v2));
380	    }
381	}
382	if (fp->fh_cid)
383	  untimeout(fp->fh_cid);
384	fp->fh_cid = timeout(FH_TTL, discard_fh, (opaque_t) fp);
385      } else if (error == EACCES) {
386	/*
387	 * Now decode the file handle return code.
388	 */
389	plog(XLOG_INFO, "Filehandle denied for \"%s:%s\"",
390	     fs->fs_host, path);
391      } else {
392	errno = error;	/* XXX */
393	plog(XLOG_INFO, "Filehandle error for \"%s:%s\": %m",
394	     fs->fs_host, path);
395      }
396
397      /*
398       * The error was returned from the remote mount daemon.
399       * Policy: this error will be cached for now...
400       */
401      return error;
402
403    case -1:
404      /*
405       * Still thinking about it, but we can re-use.
406       */
407      fp_save = fp;
408      reuse_id = TRUE;
409      break;
410
411    default:
412      /*
413       * Return the error.
414       * Policy: make sure we recompute if required again
415       * in case this was caused by a network failure.
416       * This can thrash mountd's though...  If you find
417       * your mountd going slowly then:
418       * 1.  Add a fork() loop to main.
419       * 2.  Remove the call to innetgr() and don't use
420       *     netgroups, especially if you don't use YP.
421       */
422      error = fp->fh_error;
423      fp->fh_error = -1;
424      return error;
425    }	/* end of switch statement */
426  } /* end of ITER loop */
427
428  /*
429   * Not in cache
430   */
431  if (fp_save) {
432    fp = fp_save;
433    /*
434     * Re-use existing slot
435     */
436    untimeout(fp->fh_cid);
437    free_srvr(fp->fh_fs);
438    XFREE(fp->fh_path);
439  } else {
440    fp = ALLOC(struct fh_cache);
441    memset((voidp) fp, 0, sizeof(struct fh_cache));
442    ins_que(&fp->fh_q, &fh_head);
443  }
444  if (!reuse_id)
445    fp->fh_id = FHID_ALLOC();
446  fp->fh_wchan = get_mntfs_wchan(mf);
447  fp->fh_error = -1;
448  fp->fh_cid = timeout(FH_TTL, discard_fh, (opaque_t) fp);
449
450  /*
451   * If fs->fs_ip is null, remote server is probably down.
452   */
453  if (!fs->fs_ip) {
454    /* Mark the fileserver down and invalid again */
455    fs->fs_flags &= ~FSF_VALID;
456    fs->fs_flags |= FSF_DOWN;
457    error = AM_ERRNO_HOST_DOWN;
458    return error;
459  }
460
461  /*
462   * Either fp has been freshly allocated or the address has changed.
463   * Initialize address and nfs version.  Don't try to re-use the port
464   * information unless using WebNFS where the port is fixed either by
465   * the spec or the "port" mount option.
466   */
467  if (fp->fh_sin.sin_addr.s_addr != fs->fs_ip->sin_addr.s_addr) {
468    fp->fh_sin = *fs->fs_ip;
469    if (!(mf->mf_flags & MFF_WEBNFS))
470	fp->fh_sin.sin_port = 0;
471    fp->fh_nfs_version = fs->fs_version;
472  }
473
474  fp->fh_fs = dup_srvr(fs);
475  fp->fh_path = strdup(path);
476
477  if (mf->mf_flags & MFF_WEBNFS)
478    error = webnfs_lookup(fp, got_nfs_fh_webnfs, get_mntfs_wchan(mf));
479  else
480    error = call_mountd(fp, MOUNTPROC_MNT, got_nfs_fh_mount, get_mntfs_wchan(mf));
481  if (error) {
482    /*
483     * Local error - cache for a short period
484     * just to prevent thrashing.
485     */
486    untimeout(fp->fh_cid);
487    fp->fh_cid = timeout(error < 0 ? 2 * ALLOWED_MOUNT_TIME : FH_TTL_ERROR,
488			 discard_fh, (opaque_t) fp);
489    fp->fh_error = error;
490  } else {
491    error = fp->fh_error;
492  }
493
494  return error;
495}
496
497
498int
499make_nfs_auth(void)
500{
501  AUTH_CREATE_GIDLIST_TYPE group_wheel = 0;
502
503  /* Some NFS mounts (particularly cross-domain) require FQDNs to succeed */
504
505#ifdef HAVE_TRANSPORT_TYPE_TLI
506  if (gopt.flags & CFM_FULLY_QUALIFIED_HOSTS) {
507    plog(XLOG_INFO, "Using NFS auth for FQHN \"%s\"", hostd);
508    nfs_auth = authsys_create(hostd, 0, 0, 1, &group_wheel);
509  } else {
510    nfs_auth = authsys_create_default();
511  }
512#else /* not HAVE_TRANSPORT_TYPE_TLI */
513  if (gopt.flags & CFM_FULLY_QUALIFIED_HOSTS) {
514    plog(XLOG_INFO, "Using NFS auth for FQHN \"%s\"", hostd);
515    nfs_auth = authunix_create(hostd, 0, 0, 1, &group_wheel);
516  } else {
517    nfs_auth = authunix_create_default();
518  }
519#endif /* not HAVE_TRANSPORT_TYPE_TLI */
520
521  if (!nfs_auth)
522    return ENOBUFS;
523
524  return 0;
525}
526
527
528static int
529call_mountd(fh_cache *fp, u_long proc, fwd_fun fun, wchan_t wchan)
530{
531  struct rpc_msg mnt_msg;
532  int len;
533  char iobuf[UDPMSGSIZE];
534  int error;
535  u_long mnt_version;
536
537  if (!nfs_auth) {
538    error = make_nfs_auth();
539    if (error)
540      return error;
541  }
542
543  if (fp->fh_sin.sin_port == 0) {
544    u_short mountd_port;
545    error = get_mountd_port(fp->fh_fs, &mountd_port, wchan);
546    if (error)
547      return error;
548    fp->fh_sin.sin_port = mountd_port;
549  }
550
551  /* find the right version of the mount protocol */
552#ifdef HAVE_FS_NFS3
553  if (fp->fh_nfs_version == NFS_VERSION3)
554    mnt_version = AM_MOUNTVERS3;
555  else
556#endif /* HAVE_FS_NFS3 */
557    mnt_version = MOUNTVERS;
558  plog(XLOG_INFO, "call_mountd: NFS version %d, mount version %d",
559       (int) fp->fh_nfs_version, (int) mnt_version);
560
561  rpc_msg_init(&mnt_msg, MOUNTPROG, mnt_version, MOUNTPROC_NULL);
562  len = make_rpc_packet(iobuf,
563			sizeof(iobuf),
564			proc,
565			&mnt_msg,
566			(voidp) &fp->fh_path,
567			(XDRPROC_T_TYPE) xdr_nfspath,
568			nfs_auth);
569
570  if (len > 0) {
571    error = fwd_packet(MK_RPC_XID(RPC_XID_MOUNTD, fp->fh_id),
572		       iobuf,
573		       len,
574		       &fp->fh_sin,
575		       &fp->fh_sin,
576		       (opaque_t) ((long) fp->fh_id), /* cast to long needed for 64-bit archs */
577		       fun);
578  } else {
579    error = -len;
580  }
581
582  /*
583   * It may be the case that we're sending to the wrong MOUNTD port.  This
584   * occurs if mountd is restarted on the server after the port has been
585   * looked up and stored in the filehandle cache somewhere.  The correct
586   * solution, if we're going to cache port numbers is to catch the ICMP
587   * port unreachable reply from the server and cause the portmap request
588   * to be redone.  The quick solution here is to invalidate the MOUNTD
589   * port.
590   */
591  fp->fh_sin.sin_port = 0;
592
593  return error;
594}
595
596
597static int
598webnfs_lookup(fh_cache *fp, fwd_fun fun, wchan_t wchan)
599{
600  struct rpc_msg wnfs_msg;
601  int len;
602  char iobuf[UDPMSGSIZE];
603  int error;
604  u_long proc;
605  XDRPROC_T_TYPE xdr_fn;
606  voidp argp;
607  nfsdiropargs args;
608#ifdef HAVE_FS_NFS3
609  am_LOOKUP3args args3;
610#endif /* HAVE_FS_NFS3 */
611  char *wnfs_path;
612  size_t l;
613
614  if (!nfs_auth) {
615    error = make_nfs_auth();
616    if (error)
617      return error;
618  }
619
620  if (fp->fh_sin.sin_port == 0) {
621    /* FIXME: wrong, don't discard sin_port in the first place for WebNFS. */
622    plog(XLOG_WARNING, "webnfs_lookup: port == 0 for nfs on %s, fixed",
623	 fp->fh_fs->fs_host);
624    fp->fh_sin.sin_port = htons(NFS_PORT);
625  }
626
627  /*
628   * Use native path like the rest of amd (cf. RFC 2054, 6.1).
629   */
630  l = strlen(fp->fh_path) + 2;
631  wnfs_path = (char *) xmalloc(l);
632  wnfs_path[0] = 0x80;
633  xstrlcpy(wnfs_path + 1, fp->fh_path, l - 1);
634
635  /* find the right program and lookup procedure */
636#ifdef HAVE_FS_NFS3
637  if (fp->fh_nfs_version == NFS_VERSION3) {
638    proc = AM_NFSPROC3_LOOKUP;
639    xdr_fn = (XDRPROC_T_TYPE) xdr_am_LOOKUP3args;
640    argp = &args3;
641    /* WebNFS public file handle */
642    args3.what.dir.am_fh3_length = 0;
643    args3.what.name = wnfs_path;
644  } else {
645#endif /* HAVE_FS_NFS3 */
646    proc = NFSPROC_LOOKUP;
647    xdr_fn = (XDRPROC_T_TYPE) xdr_diropargs;
648    argp = &args;
649    /* WebNFS public file handle */
650    memset(&args.da_fhandle, 0, NFS_FHSIZE);
651    args.da_name = wnfs_path;
652#ifdef HAVE_FS_NFS3
653  }
654#endif /* HAVE_FS_NFS3 */
655
656  plog(XLOG_INFO, "webnfs_lookup: NFS version %d", (int) fp->fh_nfs_version);
657
658  rpc_msg_init(&wnfs_msg, NFS_PROGRAM, fp->fh_nfs_version, proc);
659  len = make_rpc_packet(iobuf,
660			sizeof(iobuf),
661			proc,
662			&wnfs_msg,
663			argp,
664			(XDRPROC_T_TYPE) xdr_fn,
665			nfs_auth);
666
667  if (len > 0) {
668    error = fwd_packet(MK_RPC_XID(RPC_XID_WEBNFS, fp->fh_id),
669		       iobuf,
670		       len,
671		       &fp->fh_sin,
672		       &fp->fh_sin,
673		       (opaque_t) ((long) fp->fh_id), /* cast to long needed for 64-bit archs */
674		       fun);
675  } else {
676    error = -len;
677  }
678
679  XFREE(wnfs_path);
680  return error;
681}
682
683
684/*
685 * NFS needs the local filesystem, remote filesystem
686 * remote hostname.
687 * Local filesystem defaults to remote and vice-versa.
688 */
689static char *
690nfs_match(am_opts *fo)
691{
692  char *xmtab;
693  size_t l;
694
695  if (fo->opt_fs && !fo->opt_rfs)
696    fo->opt_rfs = fo->opt_fs;
697  if (!fo->opt_rfs) {
698    plog(XLOG_USER, "nfs: no remote filesystem specified");
699    return NULL;
700  }
701  if (!fo->opt_rhost) {
702    plog(XLOG_USER, "nfs: no remote host specified");
703    return NULL;
704  }
705
706  /*
707   * Determine magic cookie to put in mtab
708   */
709  l = strlen(fo->opt_rhost) + strlen(fo->opt_rfs) + 2;
710  xmtab = (char *) xmalloc(l);
711  xsnprintf(xmtab, l, "%s:%s", fo->opt_rhost, fo->opt_rfs);
712  dlog("NFS: mounting remote server \"%s\", remote fs \"%s\" on \"%s\"",
713       fo->opt_rhost, fo->opt_rfs, fo->opt_fs);
714
715  return xmtab;
716}
717
718
719/*
720 * Initialize am structure for nfs
721 */
722static int
723nfs_init(mntfs *mf)
724{
725  int error;
726  am_nfs_handle_t fhs;
727  char *colon;
728
729  if (mf->mf_private) {
730    if (mf->mf_flags & MFF_NFS_SCALEDOWN) {
731      fserver *fs;
732
733      /* tell remote mountd that we're done with this filehandle */
734      mf->mf_ops->umounted(mf);
735
736      mf->mf_prfree(mf->mf_private);
737      fs = mf->mf_ops->ffserver(mf);
738      free_srvr(mf->mf_server);
739      mf->mf_server = fs;
740    } else
741      return 0;
742  }
743
744  colon = strchr(mf->mf_info, ':');
745  if (colon == 0)
746    return ENOENT;
747
748  error = prime_nfs_fhandle_cache(colon + 1, mf->mf_server, &fhs, mf);
749  if (!error) {
750    mf->mf_private = (opaque_t) ALLOC(am_nfs_handle_t);
751    mf->mf_prfree = (void (*)(opaque_t)) free;
752    memmove(mf->mf_private, (voidp) &fhs, sizeof(fhs));
753  }
754  return error;
755}
756
757
758int
759mount_nfs_fh(am_nfs_handle_t *fhp, char *mntdir, char *fs_name, mntfs *mf)
760{
761  MTYPE_TYPE type;
762  char *colon;
763  char *xopts=NULL, transp_timeo_opts[40], transp_retrans_opts[40];
764  char host[MAXHOSTNAMELEN + MAXPATHLEN + 2];
765  fserver *fs = mf->mf_server;
766  u_long nfs_version = fs->fs_version;
767  char *nfs_proto = fs->fs_proto; /* "tcp" or "udp" */
768  int on_autofs = mf->mf_flags & MFF_ON_AUTOFS;
769  int error;
770  int genflags;
771  int retry;
772  int proto = AMU_TYPE_NONE;
773  mntent_t mnt;
774  nfs_args_t nfs_args;
775
776  /*
777   * Extract HOST name to give to kernel.
778   * Some systems like osf1/aix3/bsd44 variants may need old code
779   * for NFS_ARGS_NEEDS_PATH.
780   */
781  if (!(colon = strchr(fs_name, ':')))
782    return ENOENT;
783#ifdef MOUNT_TABLE_ON_FILE
784  *colon = '\0';
785#endif /* MOUNT_TABLE_ON_FILE */
786  xstrlcpy(host, fs_name, sizeof(host));
787#ifdef MOUNT_TABLE_ON_FILE
788  *colon = ':';
789#endif /* MOUNT_TABLE_ON_FILE */
790#ifdef MAXHOSTNAMELEN
791  /* most kernels have a name length restriction */
792  if (strlen(host) >= MAXHOSTNAMELEN)
793    xstrlcpy(host + MAXHOSTNAMELEN - 3, "..",
794	     sizeof(host) - MAXHOSTNAMELEN + 3);
795#endif /* MAXHOSTNAMELEN */
796
797  /*
798   * Create option=VAL for udp/tcp specific timeouts and retrans values, but
799   * only if these options were specified.
800   */
801
802  transp_timeo_opts[0] = transp_retrans_opts[0] = '\0';	/* initialize */
803  if (STREQ(nfs_proto, "udp"))
804    proto = AMU_TYPE_UDP;
805  else if (STREQ(nfs_proto, "tcp"))
806    proto = AMU_TYPE_TCP;
807  if (proto != AMU_TYPE_NONE) {
808    if (gopt.amfs_auto_timeo[proto] > 0)
809      xsnprintf(transp_timeo_opts, sizeof(transp_timeo_opts), "%s=%d,",
810		MNTTAB_OPT_TIMEO, gopt.amfs_auto_timeo[proto]);
811    if (gopt.amfs_auto_retrans[proto] > 0)
812      xsnprintf(transp_retrans_opts, sizeof(transp_retrans_opts), "%s=%d,",
813		MNTTAB_OPT_RETRANS, gopt.amfs_auto_retrans[proto]);
814  }
815
816  if (mf->mf_remopts && *mf->mf_remopts &&
817      !islocalnet(fs->fs_ip->sin_addr.s_addr)) {
818    plog(XLOG_INFO, "Using remopts=\"%s\"", mf->mf_remopts);
819    /* use transp_opts first, so map-specific opts will override */
820    xopts = str3cat(xopts, transp_timeo_opts, transp_retrans_opts, mf->mf_remopts);
821  } else {
822    /* use transp_opts first, so map-specific opts will override */
823    xopts = str3cat(xopts, transp_timeo_opts, transp_retrans_opts, mf->mf_mopts);
824  }
825
826  memset((voidp) &mnt, 0, sizeof(mnt));
827  mnt.mnt_dir = mntdir;
828  mnt.mnt_fsname = fs_name;
829  mnt.mnt_opts = xopts;
830
831  /*
832   * Set mount types accordingly
833   */
834#ifndef HAVE_FS_NFS3
835  type = MOUNT_TYPE_NFS;
836  mnt.mnt_type = MNTTAB_TYPE_NFS;
837#else /* HAVE_FS_NFS3 */
838  if (nfs_version == NFS_VERSION3) {
839    type = MOUNT_TYPE_NFS3;
840    /*
841     * Systems that include the mount table "vers" option generally do not
842     * set the mnttab entry to "nfs3", but to "nfs" and then they set
843     * "vers=3".  Setting it to "nfs3" works, but it may break some things
844     * like "df -t nfs" and the "quota" program (esp. on Solaris and Irix).
845     * So on those systems, set it to "nfs".
846     * Note: MNTTAB_OPT_VERS is always set for NFS3 (see am_compat.h).
847     */
848# if defined(MNTTAB_OPT_VERS) && defined(MOUNT_TABLE_ON_FILE)
849    mnt.mnt_type = MNTTAB_TYPE_NFS;
850# else /* defined(MNTTAB_OPT_VERS) && defined(MOUNT_TABLE_ON_FILE) */
851    mnt.mnt_type = MNTTAB_TYPE_NFS3;
852# endif /* defined(MNTTAB_OPT_VERS) && defined(MOUNT_TABLE_ON_FILE) */
853  } else {
854    type = MOUNT_TYPE_NFS;
855    mnt.mnt_type = MNTTAB_TYPE_NFS;
856  }
857#endif /* HAVE_FS_NFS3 */
858  plog(XLOG_INFO, "mount_nfs_fh: NFS version %d", (int) nfs_version);
859  plog(XLOG_INFO, "mount_nfs_fh: using NFS transport %s", nfs_proto);
860
861  retry = hasmntval(&mnt, MNTTAB_OPT_RETRY);
862  if (retry <= 0)
863    retry = 1;			/* XXX */
864
865  genflags = compute_mount_flags(&mnt);
866#ifdef HAVE_FS_AUTOFS
867  if (on_autofs)
868    genflags |= autofs_compute_mount_flags(&mnt);
869#endif /* HAVE_FS_AUTOFS */
870
871  /* setup the many fields and flags within nfs_args */
872  compute_nfs_args(&nfs_args,
873		   &mnt,
874		   genflags,
875		   NULL,	/* struct netconfig *nfsncp */
876		   fs->fs_ip,
877		   nfs_version,
878		   nfs_proto,
879		   fhp,
880		   host,
881		   fs_name);
882
883  /* finally call the mounting function */
884  if (amuDebug(D_TRACE)) {
885    print_nfs_args(&nfs_args, nfs_version);
886    plog(XLOG_DEBUG, "Generic mount flags 0x%x used for NFS mount", genflags);
887  }
888  error = mount_fs(&mnt, genflags, (caddr_t) &nfs_args, retry, type,
889		    nfs_version, nfs_proto, mnttab_file_name, on_autofs);
890  XFREE(xopts);
891
892#ifdef HAVE_TRANSPORT_TYPE_TLI
893  free_knetconfig(nfs_args.knconf);
894  if (nfs_args.addr)
895    XFREE(nfs_args.addr);	/* allocated in compute_nfs_args() */
896#endif /* HAVE_TRANSPORT_TYPE_TLI */
897
898  return error;
899}
900
901
902static int
903nfs_mount(am_node *am, mntfs *mf)
904{
905  int error = 0;
906  mntent_t mnt;
907
908  if (!mf->mf_private) {
909    plog(XLOG_ERROR, "Missing filehandle for %s", mf->mf_info);
910    return EINVAL;
911  }
912
913  mnt.mnt_opts = mf->mf_mopts;
914  if (amu_hasmntopt(&mnt, "softlookup") ||
915      (amu_hasmntopt(&mnt, "soft") && !amu_hasmntopt(&mnt, "nosoftlookup")))
916    am->am_flags |= AMF_SOFTLOOKUP;
917
918  error = mount_nfs_fh((am_nfs_handle_t *) mf->mf_private,
919		       mf->mf_mount,
920		       mf->mf_info,
921		       mf);
922
923  if (error) {
924    errno = error;
925    dlog("mount_nfs: %m");
926  }
927
928  return error;
929}
930
931
932static int
933nfs_umount(am_node *am, mntfs *mf)
934{
935  int unmount_flags, new_unmount_flags, error;
936
937  unmount_flags = (mf->mf_flags & MFF_ON_AUTOFS) ? AMU_UMOUNT_AUTOFS : 0;
938  error = UMOUNT_FS(mf->mf_mount, mnttab_file_name, unmount_flags);
939
940#if defined(HAVE_UMOUNT2) && (defined(MNT2_GEN_OPT_FORCE) || defined(MNT2_GEN_OPT_DETACH))
941  /*
942   * If the attempt to unmount failed with EBUSY, and this fserver was
943   * marked for forced unmounts, then use forced/lazy unmounts.
944   */
945  if (error == EBUSY &&
946      gopt.flags & CFM_FORCED_UNMOUNTS &&
947      mf->mf_server->fs_flags & FSF_FORCE_UNMOUNT) {
948    plog(XLOG_INFO, "EZK: nfs_umount: trying forced/lazy unmounts");
949    /*
950     * XXX: turning off the FSF_FORCE_UNMOUNT may not be perfectly
951     * incorrect.  Multiple nodes may need to be timed out and restarted for
952     * a single hung fserver.
953     */
954    mf->mf_server->fs_flags &= ~FSF_FORCE_UNMOUNT;
955    new_unmount_flags = unmount_flags | AMU_UMOUNT_FORCE | AMU_UMOUNT_DETACH;
956    error = UMOUNT_FS(mf->mf_mount, mnttab_file_name, new_unmount_flags);
957  }
958#endif /* HAVE_UMOUNT2 && (MNT2_GEN_OPT_FORCE || MNT2_GEN_OPT_DETACH) */
959
960  /*
961   * Here is some code to unmount 'restarted' file systems.
962   * The restarted file systems are marked as 'nfs', not
963   * 'host', so we only have the map information for the
964   * the top-level mount.  The unmount will fail (EBUSY)
965   * if there are anything else from the NFS server mounted
966   * below the mount-point.  This code checks to see if there
967   * is anything mounted with the same prefix as the
968   * file system to be unmounted ("/a/b/c" when unmounting "/a/b").
969   * If there is, and it is a 'restarted' file system, we unmount
970   * it.
971   * Added by Mike Mitchell, mcm@unx.sas.com, 09/08/93
972   */
973  if (error == EBUSY) {
974    mntfs *new_mf;
975    int len = strlen(mf->mf_mount);
976    int didsome = 0;
977
978    ITER(new_mf, mntfs, &mfhead) {
979      if (new_mf->mf_ops != mf->mf_ops ||
980	  new_mf->mf_refc > 1 ||
981	  mf == new_mf ||
982	  ((new_mf->mf_flags & (MFF_MOUNTED | MFF_UNMOUNTING | MFF_RESTART)) == (MFF_MOUNTED | MFF_RESTART)))
983	continue;
984
985      if (NSTREQ(mf->mf_mount, new_mf->mf_mount, len) &&
986	  new_mf->mf_mount[len] == '/') {
987	new_unmount_flags =
988	  (new_mf->mf_flags & MFF_ON_AUTOFS) ? AMU_UMOUNT_AUTOFS : 0;
989	UMOUNT_FS(new_mf->mf_mount, mnttab_file_name, new_unmount_flags);
990	didsome = 1;
991      }
992    }
993    if (didsome)
994      error = UMOUNT_FS(mf->mf_mount, mnttab_file_name, unmount_flags);
995  }
996  if (error)
997    return error;
998
999  return 0;
1000}
1001
1002
1003static void
1004nfs_umounted(mntfs *mf)
1005{
1006  fserver *fs;
1007  char *colon, *path;
1008
1009  if (mf->mf_error || mf->mf_refc > 1)
1010    return;
1011
1012  /*
1013   * No need to inform mountd when WebNFS is in use.
1014   */
1015  if (mf->mf_flags & MFF_WEBNFS)
1016    return;
1017
1018  /*
1019   * Call the mount daemon on the server to announce that we are not using
1020   * the fs any more.
1021   *
1022   * XXX: This is *wrong*.  The mountd should be called when the fhandle is
1023   * flushed from the cache, and a reference held to the cached entry while
1024   * the fs is mounted...
1025   */
1026  fs = mf->mf_server;
1027  colon = path = strchr(mf->mf_info, ':');
1028  if (fs && colon) {
1029    fh_cache f;
1030
1031    dlog("calling mountd for %s", mf->mf_info);
1032    *path++ = '\0';
1033    f.fh_path = path;
1034    f.fh_sin = *fs->fs_ip;
1035    f.fh_sin.sin_port = (u_short) 0;
1036    f.fh_nfs_version = fs->fs_version;
1037    f.fh_fs = fs;
1038    f.fh_id = 0;
1039    f.fh_error = 0;
1040    prime_nfs_fhandle_cache(colon + 1, mf->mf_server, (am_nfs_handle_t *) NULL, mf);
1041    call_mountd(&f, MOUNTPROC_UMNT, (fwd_fun *) NULL, (wchan_t) NULL);
1042    *colon = ':';
1043  }
1044}
1045