amfs_host.c revision 131702
1/*
2 * Copyright (c) 1997-2004 Erez Zadok
3 * Copyright (c) 1990 Jan-Simon Pendry
4 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
5 * Copyright (c) 1990 The Regents of the University of California.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to Berkeley by
9 * Jan-Simon Pendry at Imperial College, London.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 *    must display the following acknowledgment:
21 *      This product includes software developed by the University of
22 *      California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 *    may be used to endorse or promote products derived from this software
25 *    without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
38 *
39 *      %W% (Berkeley) %G%
40 *
41 * $Id: amfs_host.c,v 1.4.2.7 2004/01/06 03:15:16 ezk Exp $
42 *
43 */
44
45/*
46 * NFS host file system.
47 * Mounts all exported filesystems from a given host.
48 * This has now degenerated into a mess but will not
49 * be rewritten.  Amd 6 will support the abstractions
50 * needed to make this work correctly.
51 */
52
53#ifdef HAVE_CONFIG_H
54# include <config.h>
55#endif /* HAVE_CONFIG_H */
56#include <am_defs.h>
57#include <amd.h>
58
59static char *amfs_host_match(am_opts *fo);
60static int amfs_host_fmount(mntfs *mf);
61static int amfs_host_fumount(mntfs *mf);
62static int amfs_host_init(mntfs *mf);
63static void amfs_host_umounted(am_node *mp);
64
65/*
66 * Ops structure
67 */
68am_ops amfs_host_ops =
69{
70  "host",
71  amfs_host_match,
72  amfs_host_init,
73  amfs_auto_fmount,
74  amfs_host_fmount,
75  amfs_auto_fumount,
76  amfs_host_fumount,
77  amfs_error_lookuppn,
78  amfs_error_readdir,
79  0,				/* amfs_host_readlink */
80  0,				/* amfs_host_mounted */
81  amfs_host_umounted,
82  find_nfs_srvr,
83  FS_MKMNT | FS_BACKGROUND | FS_AMQINFO
84};
85
86
87/*
88 * Determine the mount point:
89 *
90 * The next change we put in to better handle PCs.  This is a bit
91 * disgusting, so you'd better sit down.  We change the make_mntpt function
92 * to look for exported file systems without a leading '/'.  If they don't
93 * have a leading '/', we add one.  If the export is 'a:' through 'z:'
94 * (without a leading slash), we change it to 'a%' (or b% or z%).  This
95 * allows the entire PC disk to be mounted.
96 */
97static void
98make_mntpt(char *mntpt, const exports ex, const mntfs *mf)
99{
100  if (ex->ex_dir[0] == '/') {
101    if (ex->ex_dir[1] == 0)
102      strcpy(mntpt, (mf)->mf_mount);
103    else
104      sprintf(mntpt, "%s%s", mf->mf_mount, ex->ex_dir);
105  } else if (ex->ex_dir[0] >= 'a' &&
106	     ex->ex_dir[0] <= 'z' &&
107	     ex->ex_dir[1] == ':' &&
108	     ex->ex_dir[2] == '/' &&
109	     ex->ex_dir[3] == 0)
110    sprintf(mntpt, "%s/%c%%", mf->mf_mount, ex->ex_dir[0]);
111  else
112    sprintf(mntpt, "%s/%s", mf->mf_mount, ex->ex_dir);
113}
114
115
116/*
117 * Execute needs the same as NFS plus a helper command
118 */
119static char *
120amfs_host_match(am_opts *fo)
121{
122  extern am_ops nfs_ops;
123
124  /*
125   * Make sure rfs is specified to keep nfs_match happy...
126   */
127  if (!fo->opt_rfs)
128    fo->opt_rfs = "/";
129
130  return (*nfs_ops.fs_match) (fo);
131}
132
133
134static int
135amfs_host_init(mntfs *mf)
136{
137  fserver *fs;
138  u_short port;
139
140  if (strchr(mf->mf_info, ':') == 0)
141    return ENOENT;
142
143  /*
144   * This is primarily to schedule a wakeup so that as soon
145   * as our fileserver is ready, we can continue setting up
146   * the host filesystem.  If we don't do this, the standard
147   * amfs_auto code will set up a fileserver structure, but it will
148   * have to wait for another nfs request from the client to come
149   * in before finishing.  Our way is faster since we don't have
150   * to wait for the client to resend its request (which could
151   * take a second or two).
152   */
153  /*
154   * First, we find the fileserver for this mntfs and then call
155   * nfs_srvr_port with our mntfs passed as the wait channel.
156   * nfs_srvr_port will check some things and then schedule
157   * it so that when the fileserver is ready, a wakeup is done
158   * on this mntfs.   amfs_auto_cont() is already sleeping on this mntfs
159   * so as soon as that wakeup happens amfs_auto_cont() is called and
160   * this mount is retried.
161   */
162  if ((fs = mf->mf_server))
163    /*
164     * We don't really care if there's an error returned.
165     * Since this is just to help speed things along, the
166     * error will get handled properly elsewhere.
167     */
168    (void) nfs_srvr_port(fs, &port, (voidp) mf);
169
170  return 0;
171}
172
173
174static int
175do_mount(am_nfs_handle_t *fhp, char *dir, char *fs_name, char *opts, mntfs *mf)
176{
177  struct stat stb;
178
179#ifdef DEBUG
180  dlog("amfs_host: mounting fs %s on %s\n", fs_name, dir);
181#endif /* DEBUG */
182
183  (void) mkdirs(dir, 0555);
184  if (stat(dir, &stb) < 0 || (stb.st_mode & S_IFMT) != S_IFDIR) {
185    plog(XLOG_ERROR, "No mount point for %s - skipping", dir);
186    return ENOENT;
187  }
188
189  return mount_nfs_fh(fhp, dir, fs_name, opts, mf);
190}
191
192
193static int
194sortfun(const voidp x, const voidp y)
195{
196  exports *a = (exports *) x;
197  exports *b = (exports *) y;
198
199  return strcmp((*a)->ex_dir, (*b)->ex_dir);
200}
201
202
203/*
204 * Get filehandle
205 */
206static int
207fetch_fhandle(CLIENT *client, char *dir, am_nfs_handle_t *fhp, u_long nfs_version)
208{
209  struct timeval tv;
210  enum clnt_stat clnt_stat;
211
212  /*
213   * Pick a number, any number...
214   */
215  tv.tv_sec = 20;
216  tv.tv_usec = 0;
217
218#ifdef DEBUG
219  dlog("Fetching fhandle for %s", dir);
220#endif /* DEBUG */
221
222  /*
223   * Call the mount daemon on the remote host to
224   * get the filehandle.  Use NFS version specific call.
225   */
226
227  plog(XLOG_INFO, "fetch_fhandle: NFS version %d", (int) nfs_version);
228#ifdef HAVE_FS_NFS3
229  if (nfs_version == NFS_VERSION3) {
230    memset((char *) &fhp->v3, 0, sizeof(fhp->v3));
231    clnt_stat = clnt_call(client,
232			  MOUNTPROC_MNT,
233			  (XDRPROC_T_TYPE) xdr_dirpath,
234			  (SVC_IN_ARG_TYPE) &dir,
235			  (XDRPROC_T_TYPE) xdr_mountres3,
236			  (SVC_IN_ARG_TYPE) &fhp->v3,
237			  tv);
238    if (clnt_stat != RPC_SUCCESS) {
239      plog(XLOG_ERROR, "mountd rpc failed: %s", clnt_sperrno(clnt_stat));
240      return EIO;
241    }
242    /* Check the status of the filehandle */
243    if ((errno = fhp->v3.fhs_status)) {
244#ifdef DEBUG
245      dlog("fhandle fetch for mount version 3 failed: %m");
246#endif /* DEBUG */
247      return errno;
248    }
249  } else {			/* not NFS_VERSION3 mount */
250#endif /* HAVE_FS_NFS3 */
251    clnt_stat = clnt_call(client,
252			  MOUNTPROC_MNT,
253			  (XDRPROC_T_TYPE) xdr_dirpath,
254			  (SVC_IN_ARG_TYPE) &dir,
255			  (XDRPROC_T_TYPE) xdr_fhstatus,
256			  (SVC_IN_ARG_TYPE) &fhp->v2,
257			  tv);
258    if (clnt_stat != RPC_SUCCESS) {
259      plog(XLOG_ERROR, "mountd rpc failed: %s", clnt_sperrno(clnt_stat));
260      return EIO;
261    }
262    /* Check status of filehandle */
263    if (fhp->v2.fhs_status) {
264      errno = fhp->v2.fhs_status;
265#ifdef DEBUG
266      dlog("fhandle fetch for mount version 1 failed: %m");
267#endif /* DEBUG */
268      return errno;
269    }
270#ifdef HAVE_FS_NFS3
271  } /* end of "if (nfs_version == NFS_VERSION3)" statement */
272#endif /* HAVE_FS_NFS3 */
273
274  /* all is well */
275  return 0;
276}
277
278
279/*
280 * Scan mount table to see if something already mounted
281 */
282static int
283already_mounted(mntlist *mlist, char *dir)
284{
285  mntlist *ml;
286
287  for (ml = mlist; ml; ml = ml->mnext)
288    if (STREQ(ml->mnt->mnt_dir, dir))
289      return 1;
290  return 0;
291}
292
293
294/*
295 * Mount the export tree from a host
296 */
297static int
298amfs_host_fmount(mntfs *mf)
299{
300  struct timeval tv2;
301  CLIENT *client;
302  enum clnt_stat clnt_stat;
303  int n_export;
304  int j, k;
305  exports exlist = 0, ex;
306  exports *ep = 0;
307  am_nfs_handle_t *fp = 0;
308  char *host;
309  int error = 0;
310  struct sockaddr_in sin;
311  int sock = RPC_ANYSOCK;
312  int ok = FALSE;
313  mntlist *mlist;
314  char fs_name[MAXPATHLEN], *rfs_dir;
315  char mntpt[MAXPATHLEN];
316  struct timeval tv;
317  u_long mnt_version;
318
319  /*
320   * Read the mount list
321   */
322  mlist = read_mtab(mf->mf_mount, mnttab_file_name);
323
324#ifdef MOUNT_TABLE_ON_FILE
325  /*
326   * Unlock the mount list
327   */
328  unlock_mntlist();
329#endif /* MOUNT_TABLE_ON_FILE */
330
331  /*
332   * Take a copy of the server hostname, address, and nfs version
333   * to mount version conversion.
334   */
335  host = mf->mf_server->fs_host;
336  sin = *mf->mf_server->fs_ip;
337  plog(XLOG_INFO, "amfs_host_fmount: NFS version %d", (int) mf->mf_server->fs_version);
338#ifdef HAVE_FS_NFS3
339  if (mf->mf_server->fs_version == NFS_VERSION3)
340    mnt_version = MOUNTVERS3;
341  else
342#endif /* HAVE_FS_NFS3 */
343    mnt_version = MOUNTVERS;
344
345  /*
346   * The original 10 second per try timeout is WAY too large, especially
347   * if we're only waiting 10 or 20 seconds max for the response.
348   * That would mean we'd try only once in 10 seconds, and we could
349   * lose the transmit or receive packet, and never try again.
350   * A 2-second per try timeout here is much more reasonable.
351   * 09/28/92 Mike Mitchell, mcm@unx.sas.com
352   */
353  tv.tv_sec = 2;
354  tv.tv_usec = 0;
355
356  /*
357   * Create a client attached to mountd
358   */
359  client = get_mount_client(host, &sin, &tv, &sock, mnt_version);
360  if (client == NULL) {
361#ifdef HAVE_CLNT_SPCREATEERROR
362    plog(XLOG_ERROR, "get_mount_client failed for %s: %s",
363	 host, clnt_spcreateerror(""));
364#else /* not HAVE_CLNT_SPCREATEERROR */
365    plog(XLOG_ERROR, "get_mount_client failed for %s", host);
366#endif /* not HAVE_CLNT_SPCREATEERROR */
367    error = EIO;
368    goto out;
369  }
370  if (!nfs_auth) {
371    error = make_nfs_auth();
372    if (error)
373      goto out;
374  }
375  client->cl_auth = nfs_auth;
376
377#ifdef DEBUG
378  dlog("Fetching export list from %s", host);
379#endif /* DEBUG */
380
381  /*
382   * Fetch the export list
383   */
384  tv2.tv_sec = 10;
385  tv2.tv_usec = 0;
386  clnt_stat = clnt_call(client,
387			MOUNTPROC_EXPORT,
388			(XDRPROC_T_TYPE) xdr_void,
389			0,
390			(XDRPROC_T_TYPE) xdr_exports,
391			(SVC_IN_ARG_TYPE) & exlist,
392			tv2);
393  if (clnt_stat != RPC_SUCCESS) {
394    const char *msg = clnt_sperrno(clnt_stat);
395    plog(XLOG_ERROR, "host_fmount rpc failed: %s", msg);
396    /* clnt_perror(client, "rpc"); */
397    error = EIO;
398    goto out;
399  }
400
401  /*
402   * Figure out how many exports were returned
403   */
404  for (n_export = 0, ex = exlist; ex; ex = ex->ex_next) {
405    n_export++;
406  }
407
408  /*
409   * Allocate an array of pointers into the list
410   * so that they can be sorted.  If the filesystem
411   * is already mounted then ignore it.
412   */
413  ep = (exports *) xmalloc(n_export * sizeof(exports));
414  for (j = 0, ex = exlist; ex; ex = ex->ex_next) {
415    make_mntpt(mntpt, ex, mf);
416    if (already_mounted(mlist, mntpt))
417      /* we have at least one mounted f/s, so don't fail the mount */
418      ok = TRUE;
419    else
420      ep[j++] = ex;
421  }
422  n_export = j;
423
424  /*
425   * Sort into order.
426   * This way the mounts are done in order down the tree,
427   * instead of any random order returned by the mount
428   * daemon (the protocol doesn't specify...).
429   */
430  qsort(ep, n_export, sizeof(exports), sortfun);
431
432  /*
433   * Allocate an array of filehandles
434   */
435  fp = (am_nfs_handle_t *) xmalloc(n_export * sizeof(am_nfs_handle_t));
436
437  /*
438   * Try to obtain filehandles for each directory.
439   * If a fetch fails then just zero out the array
440   * reference but discard the error.
441   */
442  for (j = k = 0; j < n_export; j++) {
443    /* Check and avoid a duplicated export entry */
444    if (j > k && ep[k] && STREQ(ep[j]->ex_dir, ep[k]->ex_dir)) {
445#ifdef DEBUG
446      dlog("avoiding dup fhandle requested for %s", ep[j]->ex_dir);
447#endif /* DEBUG */
448      ep[j] = 0;
449    } else {
450      k = j;
451      error = fetch_fhandle(client, ep[j]->ex_dir, &fp[j],
452			    mf->mf_server->fs_version);
453      if (error)
454	ep[j] = 0;
455    }
456  }
457
458  /*
459   * Mount each filesystem for which we have a filehandle.
460   * If any of the mounts succeed then mark "ok" and return
461   * error code 0 at the end.  If they all fail then return
462   * the last error code.
463   */
464  strncpy(fs_name, mf->mf_info, sizeof(fs_name));
465  if ((rfs_dir = strchr(fs_name, ':')) == (char *) 0) {
466    plog(XLOG_FATAL, "amfs_host_fmount: mf_info has no colon");
467    error = EINVAL;
468    goto out;
469  }
470  ++rfs_dir;
471  for (j = 0; j < n_export; j++) {
472    ex = ep[j];
473    if (ex) {
474      strcpy(rfs_dir, ex->ex_dir);
475      make_mntpt(mntpt, ex, mf);
476      if (do_mount(&fp[j], mntpt, fs_name, mf->mf_mopts, mf) == 0)
477	ok = TRUE;
478    }
479  }
480
481  /*
482   * Clean up and exit
483   */
484out:
485  discard_mntlist(mlist);
486  if (ep)
487    XFREE(ep);
488  if (fp)
489    XFREE(fp);
490  if (sock != RPC_ANYSOCK)
491    (void) amu_close(sock);
492  if (client)
493    clnt_destroy(client);
494  if (exlist)
495    xdr_pri_free((XDRPROC_T_TYPE) xdr_exports, (caddr_t) &exlist);
496  if (ok)
497    return 0;
498  return error;
499}
500
501
502/*
503 * Return true if pref is a directory prefix of dir.
504 *
505 * XXX TODO:
506 * Does not work if pref is "/".
507 */
508static int
509directory_prefix(char *pref, char *dir)
510{
511  int len = strlen(pref);
512
513  if (!NSTREQ(pref, dir, len))
514    return FALSE;
515  if (dir[len] == '/' || dir[len] == '\0')
516    return TRUE;
517  return FALSE;
518}
519
520
521/*
522 * Unmount a mount tree
523 */
524static int
525amfs_host_fumount(mntfs *mf)
526{
527  mntlist *ml, *mprev;
528  int xerror = 0;
529
530  /*
531   * Read the mount list
532   */
533  mntlist *mlist = read_mtab(mf->mf_mount, mnttab_file_name);
534
535#ifdef MOUNT_TABLE_ON_FILE
536  /*
537   * Unlock the mount list
538   */
539  unlock_mntlist();
540#endif /* MOUNT_TABLE_ON_FILE */
541
542  /*
543   * Reverse list...
544   */
545  ml = mlist;
546  mprev = 0;
547  while (ml) {
548    mntlist *ml2 = ml->mnext;
549    ml->mnext = mprev;
550    mprev = ml;
551    ml = ml2;
552  }
553  mlist = mprev;
554
555  /*
556   * Unmount all filesystems...
557   */
558  for (ml = mlist; ml && !xerror; ml = ml->mnext) {
559    char *dir = ml->mnt->mnt_dir;
560    if (directory_prefix(mf->mf_mount, dir)) {
561      int error;
562#ifdef DEBUG
563      dlog("amfs_host: unmounts %s", dir);
564#endif /* DEBUG */
565      /*
566       * Unmount "dir"
567       */
568      error = UMOUNT_FS(dir, mnttab_file_name);
569      /*
570       * Keep track of errors
571       */
572      if (error) {
573	if (!xerror)
574	  xerror = error;
575	if (error != EBUSY) {
576	  errno = error;
577	  plog(XLOG_ERROR, "Tree unmount of %s failed: %m", ml->mnt->mnt_dir);
578	}
579      } else {
580	(void) rmdirs(dir);
581      }
582    }
583  }
584
585  /*
586   * Throw away mount list
587   */
588  discard_mntlist(mlist);
589
590  /*
591   * Try to remount, except when we are shutting down.
592   */
593  if (xerror && amd_state != Finishing) {
594    xerror = amfs_host_fmount(mf);
595    if (!xerror) {
596      /*
597       * Don't log this - it's usually too verbose
598       plog(XLOG_INFO, "Remounted host %s", mf->mf_info);
599       */
600      xerror = EBUSY;
601    }
602  }
603  return xerror;
604}
605
606
607/*
608 * Tell mountd we're done.
609 * This is not quite right, because we may still
610 * have other filesystems mounted, but the existing
611 * mountd protocol is badly broken anyway.
612 */
613static void
614amfs_host_umounted(am_node *mp)
615{
616  mntfs *mf = mp->am_mnt;
617  char *host;
618  CLIENT *client;
619  enum clnt_stat clnt_stat;
620  struct sockaddr_in sin;
621  int sock = RPC_ANYSOCK;
622  struct timeval tv;
623  u_long mnt_version;
624
625  if (mf->mf_error || mf->mf_refc > 1 || !mf->mf_server)
626    return;
627
628  /*
629   * Take a copy of the server hostname, address, and NFS version
630   * to mount version conversion.
631   */
632  host = mf->mf_server->fs_host;
633  sin = *mf->mf_server->fs_ip;
634  plog(XLOG_INFO, "amfs_host_umounted: NFS version %d", (int) mf->mf_server->fs_version);
635#ifdef HAVE_FS_NFS3
636  if (mf->mf_server->fs_version == NFS_VERSION3)
637    mnt_version = MOUNTVERS3;
638  else
639#endif /* HAVE_FS_NFS3 */
640    mnt_version = MOUNTVERS;
641
642  /*
643   * Create a client attached to mountd
644   */
645  tv.tv_sec = 10;
646  tv.tv_usec = 0;
647  client = get_mount_client(host, &sin, &tv, &sock, mnt_version);
648  if (client == NULL) {
649#ifdef HAVE_CLNT_SPCREATEERROR
650    plog(XLOG_ERROR, "get_mount_client failed for %s: %s",
651	 host, clnt_spcreateerror(""));
652#else /* not HAVE_CLNT_SPCREATEERROR */
653    plog(XLOG_ERROR, "get_mount_client failed for %s", host);
654#endif /* not HAVE_CLNT_SPCREATEERROR */
655    goto out;
656  }
657
658  if (!nfs_auth) {
659    if (make_nfs_auth())
660      goto out;
661  }
662  client->cl_auth = nfs_auth;
663
664#ifdef DEBUG
665  dlog("Unmounting all from %s", host);
666#endif /* DEBUG */
667
668  clnt_stat = clnt_call(client,
669			MOUNTPROC_UMNTALL,
670			(XDRPROC_T_TYPE) xdr_void,
671			0,
672			(XDRPROC_T_TYPE) xdr_void,
673			0,
674			tv);
675  if (clnt_stat != RPC_SUCCESS && clnt_stat != RPC_SYSTEMERROR) {
676    /* RPC_SYSTEMERROR seems to be returned for no good reason ... */
677    const char *msg = clnt_sperrno(clnt_stat);
678    plog(XLOG_ERROR, "unmount all from %s rpc failed: %s", host, msg);
679    goto out;
680  }
681
682out:
683  if (sock != RPC_ANYSOCK)
684    (void) amu_close(sock);
685  if (client)
686    clnt_destroy(client);
687}
688