1/*
2 * Copyright (c) 1997-2006 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 *
40 * File: am-utils/amd/amfs_nfsx.c
41 *
42 */
43
44/*
45 * NFS hierarchical mounts
46 *
47 * TODO: Re-implement.
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 * The rfs field contains a list of mounts to be done from
58 * the remote host.
59 */
60typedef struct amfs_nfsx_mnt {
61  mntfs *n_mnt;
62  int n_error;
63} amfs_nfsx_mnt;
64
65struct amfs_nfsx {
66  int nx_c;			/* Number of elements in nx_v */
67  amfs_nfsx_mnt *nx_v;		/* Underlying mounts */
68  amfs_nfsx_mnt *nx_try;
69  am_node *nx_mp;
70};
71
72/* forward definitions */
73static char *amfs_nfsx_match(am_opts *fo);
74static int amfs_nfsx_mount(am_node *am, mntfs *mf);
75static int amfs_nfsx_umount(am_node *am, mntfs *mf);
76static int amfs_nfsx_init(mntfs *mf);
77
78/*
79 * Ops structure
80 */
81am_ops amfs_nfsx_ops =
82{
83  "nfsx",
84  amfs_nfsx_match,
85  amfs_nfsx_init,
86  amfs_nfsx_mount,
87  amfs_nfsx_umount,
88  amfs_error_lookup_child,
89  amfs_error_mount_child,
90  amfs_error_readdir,
91  0,				/* amfs_nfsx_readlink */
92  0,				/* amfs_nfsx_mounted */
93  0,				/* amfs_nfsx_umounted */
94  find_nfs_srvr,		/* XXX */
95  0,				/* amfs_nfsx_get_wchan */
96  /* FS_UBACKGROUND| */ FS_AMQINFO,	/* nfs_fs_flags */
97#ifdef HAVE_FS_AUTOFS
98  AUTOFS_NFSX_FS_FLAGS,
99#endif /* HAVE_FS_AUTOFS */
100};
101
102
103static char *
104amfs_nfsx_match(am_opts *fo)
105{
106  char *xmtab;
107  char *ptr;
108  int len;
109
110  if (!fo->opt_rfs) {
111    plog(XLOG_USER, "amfs_nfsx: no remote filesystem specified");
112    return FALSE;
113  }
114
115  if (!fo->opt_rhost) {
116    plog(XLOG_USER, "amfs_nfsx: no remote host specified");
117    return FALSE;
118  }
119
120  /* set default sublink */
121  if (fo->opt_sublink == 0) {
122    ptr = strchr(fo->opt_rfs, ',');
123    if (ptr && ptr > (fo->opt_rfs + 1))
124      fo->opt_sublink = strnsave(fo->opt_rfs + 1, ptr - fo->opt_rfs - 1);
125  }
126
127  /*
128   * Remove trailing ",..." from ${fs}
129   * After deslashifying, overwrite the end of ${fs} with "/"
130   * to make sure it is unique.
131   */
132  if ((ptr = strchr(fo->opt_fs, ',')))
133    *ptr = '\0';
134  deslashify(fo->opt_fs);
135
136  /*
137   * Bump string length to allow trailing /
138   */
139  len = strlen(fo->opt_fs);
140  fo->opt_fs = xrealloc(fo->opt_fs, len + 1 + 1);
141  ptr = fo->opt_fs + len;
142
143  /*
144   * Make unique...
145   */
146  *ptr++ = '/';
147  *ptr = '\0';
148
149  /*
150   * Determine magic cookie to put in mtab
151   */
152  xmtab = str3cat((char *) 0, fo->opt_rhost, ":", fo->opt_rfs);
153  dlog("NFSX: mounting remote server \"%s\", remote fs \"%s\" on \"%s\"",
154       fo->opt_rhost, fo->opt_rfs, fo->opt_fs);
155
156  return xmtab;
157}
158
159
160static void
161amfs_nfsx_prfree(opaque_t vp)
162{
163  struct amfs_nfsx *nx = (struct amfs_nfsx *) vp;
164  int i;
165
166  for (i = 0; i < nx->nx_c; i++) {
167    mntfs *m = nx->nx_v[i].n_mnt;
168    if (m)
169      free_mntfs(m);
170  }
171
172  XFREE(nx->nx_v);
173  XFREE(nx);
174}
175
176
177static int
178amfs_nfsx_init(mntfs *mf)
179{
180  /*
181   * mf_info has the form:
182   *   host:/prefix/path,sub,sub,sub
183   */
184  int i;
185  int glob_error;
186  struct amfs_nfsx *nx;
187  int asked_for_wakeup = 0;
188
189  nx = (struct amfs_nfsx *) mf->mf_private;
190
191  if (nx == 0) {
192    char **ivec;
193    char *info = 0;
194    char *host;
195    char *pref;
196    int error = 0;
197
198    info = strdup(mf->mf_info);
199    host = strchr(info, ':');
200    if (!host) {
201      error = EINVAL;
202      goto errexit;
203    }
204    pref = host + 1;
205    host = info;
206
207    /*
208     * Split the prefix off from the suffices
209     */
210    ivec = strsplit(pref, ',', '\'');
211
212    /*
213     * Count array size
214     */
215    for (i = 0; ivec[i]; i++)
216      /* nothing */;
217
218    nx = ALLOC(struct amfs_nfsx);
219    mf->mf_private = (opaque_t) nx;
220    mf->mf_prfree = amfs_nfsx_prfree;
221
222    nx->nx_c = i - 1;		/* i-1 because we don't want the prefix */
223    nx->nx_v = (amfs_nfsx_mnt *) xmalloc(nx->nx_c * sizeof(amfs_nfsx_mnt));
224    nx->nx_mp = 0;
225    {
226      char *mp = 0;
227      char *xinfo = 0;
228      char *fs = mf->mf_fo->opt_fs;
229      char *rfs = 0;
230      for (i = 0; i < nx->nx_c; i++) {
231	char *path = ivec[i + 1];
232	rfs = str3cat(rfs, pref, "/", path);
233	/*
234	 * Determine the mount point.
235	 * If this is the root, then don't remove
236	 * the trailing slash to avoid mntfs name clashes.
237	 */
238	mp = str3cat(mp, fs, "/", rfs);
239	normalize_slash(mp);
240	deslashify(mp);
241	/*
242	 * Determine the mount info
243	 */
244	xinfo = str3cat(xinfo, host, *path == '/' ? "" : "/", path);
245	normalize_slash(xinfo);
246	if (pref[1] != '\0')
247	  deslashify(xinfo);
248	dlog("amfs_nfsx: init mount for %s on %s", xinfo, mp);
249	nx->nx_v[i].n_error = -1;
250	nx->nx_v[i].n_mnt = find_mntfs(&nfs_ops, mf->mf_fo, mp, xinfo, "", mf->mf_mopts, mf->mf_remopts);
251	/* propagate the on_autofs flag */
252	nx->nx_v[i].n_mnt->mf_flags |= mf->mf_flags & MFF_ON_AUTOFS;
253      }
254      if (rfs)
255	XFREE(rfs);
256      if (mp)
257	XFREE(mp);
258      if (xinfo)
259	XFREE(xinfo);
260    }
261
262    XFREE(ivec);
263  errexit:
264    if (info)
265      XFREE(info);
266    if (error)
267      return error;
268  }
269
270  /*
271   * Iterate through the mntfs's and call
272   * the underlying init routine on each
273   */
274  glob_error = 0;
275
276  for (i = 0; i < nx->nx_c; i++) {
277    amfs_nfsx_mnt *n = &nx->nx_v[i];
278    mntfs *m = n->n_mnt;
279    int error = 0;
280    if (m->mf_ops->fs_init && !(mf->mf_flags & MFF_RESTART))
281      error = m->mf_ops->fs_init(m);
282    /*
283     * if you just "return error" here, you will have made a failure
284     * in any submounts to fail the whole group.  There was old unused code
285     * here before.
286     */
287    if (error > 0)
288      n->n_error = error;
289
290    else if (error < 0) {
291      glob_error = -1;
292      if (!asked_for_wakeup) {
293	asked_for_wakeup = 1;
294	sched_task(wakeup_task, (opaque_t) mf, get_mntfs_wchan(m));
295      }
296    }
297  }
298
299  return glob_error;
300}
301
302
303static void
304amfs_nfsx_cont(int rc, int term, opaque_t arg)
305{
306  mntfs *mf = (mntfs *) arg;
307  struct amfs_nfsx *nx = (struct amfs_nfsx *) mf->mf_private;
308  am_node *mp = nx->nx_mp;
309  amfs_nfsx_mnt *n = nx->nx_try;
310
311  n->n_mnt->mf_flags &= ~(MFF_ERROR | MFF_MOUNTING);
312  mf->mf_flags &= ~MFF_ERROR;
313
314  /*
315   * Wakeup anything waiting for this mount
316   */
317  wakeup(get_mntfs_wchan(n->n_mnt));
318
319  if (rc || term) {
320    if (term) {
321      /*
322       * Not sure what to do for an error code.
323       */
324      plog(XLOG_ERROR, "mount for %s got signal %d", n->n_mnt->mf_mount, term);
325      n->n_error = EIO;
326    } else {
327      /*
328       * Check for exit status
329       */
330      errno = rc;		/* XXX */
331      plog(XLOG_ERROR, "%s: mount (amfs_nfsx_cont): %m", n->n_mnt->mf_mount);
332      n->n_error = rc;
333    }
334    free_mntfs(n->n_mnt);
335    n->n_mnt = new_mntfs();
336    n->n_mnt->mf_error = n->n_error;
337    n->n_mnt->mf_flags |= MFF_ERROR;
338  } else {
339    /*
340     * The mount worked.
341     */
342    mf_mounted(n->n_mnt, FALSE); /* FALSE => don't free the n_mnt->am_opts */
343    n->n_error = 0;
344  }
345
346  /*
347   * Do the remaining bits
348   */
349  if (amfs_nfsx_mount(mp, mf) >= 0)
350    wakeup(get_mntfs_wchan(mf));
351}
352
353
354static int
355try_amfs_nfsx_mount(opaque_t mv)
356{
357  mntfs *mf = (mntfs *) mv;
358  struct amfs_nfsx *nx = (struct amfs_nfsx *) mf->mf_private;
359  am_node *mp = nx->nx_mp;
360  int error;
361
362  error = mf->mf_ops->mount_fs(mp, mf);
363
364  return error;
365}
366
367
368static int
369amfs_nfsx_remount(am_node *am, mntfs *mf, int fg)
370{
371  struct amfs_nfsx *nx = (struct amfs_nfsx *) mf->mf_private;
372  amfs_nfsx_mnt *n;
373  int glob_error = -1;
374
375  /* Save the am_node pointer for later use */
376  nx->nx_mp = am;
377
378  /*
379   * Iterate through the mntfs's and mount each filesystem
380   * which is not yet mounted.
381   */
382  for (n = nx->nx_v; n < nx->nx_v + nx->nx_c; n++) {
383    mntfs *m = n->n_mnt;
384
385    if (m->mf_flags & MFF_MOUNTING)
386      break;
387
388    if (m->mf_flags & MFF_MOUNTED) {
389      mf_mounted(m, FALSE);	/* FALSE => don't free the m->am_opts */
390      n->n_error = glob_error = 0;
391      continue;
392    }
393
394    if (n->n_error < 0) {
395      /* Create the mountpoint, if and as required */
396      if (!(m->mf_flags & MFF_MKMNT) && m->mf_fsflags & FS_MKMNT) {
397	if (!mkdirs(m->mf_mount, 0555))
398	  m->mf_flags |= MFF_MKMNT;
399      }
400
401      dlog("calling underlying mount on %s", m->mf_mount);
402      if (!fg && foreground && (m->mf_fsflags & FS_MBACKGROUND)) {
403	m->mf_flags |= MFF_MOUNTING;
404	dlog("backgrounding mount of \"%s\"", m->mf_info);
405	nx->nx_try = n;
406	run_task(try_amfs_nfsx_mount, (opaque_t) m, amfs_nfsx_cont, (opaque_t) mf);
407	n->n_error = -1;
408	return -1;
409      } else {
410	dlog("foreground mount of \"%s\" ...", mf->mf_info);
411	n->n_error = m->mf_ops->mount_fs(am, m);
412      }
413
414      if (n->n_error > 0)
415	dlog("underlying fmount of %s failed: %s", m->mf_mount, strerror(n->n_error));
416
417      if (n->n_error == 0) {
418	glob_error = 0;
419      } else if (glob_error < 0) {
420	glob_error = n->n_error;
421      }
422    }
423  }
424
425  return glob_error < 0 ? 0 : glob_error;
426}
427
428
429static int
430amfs_nfsx_mount(am_node *am, mntfs *mf)
431{
432  return amfs_nfsx_remount(am, mf, FALSE);
433}
434
435
436/*
437 * Unmount an NFS hierarchy.
438 * Note that this is called in the foreground
439 * and so may hang under extremely rare conditions.
440 */
441static int
442amfs_nfsx_umount(am_node *am, mntfs *mf)
443{
444  struct amfs_nfsx *nx = (struct amfs_nfsx *) mf->mf_private;
445  amfs_nfsx_mnt *n;
446  int glob_error = 0;
447
448  /*
449   * Iterate in reverse through the mntfs's and unmount each filesystem
450   * which is mounted.
451   */
452  for (n = nx->nx_v + nx->nx_c - 1; n >= nx->nx_v; --n) {
453    mntfs *m = n->n_mnt;
454    /*
455     * If this node has not been messed with
456     * and there has been no error so far
457     * then try and unmount.
458     * If an error had occurred then zero
459     * the error code so that the remount
460     * only tries to unmount those nodes
461     * which had been successfully unmounted.
462     */
463    if (n->n_error == 0) {
464      dlog("calling underlying fumount on %s", m->mf_mount);
465      n->n_error = m->mf_ops->umount_fs(am, m);
466      if (n->n_error) {
467	glob_error = n->n_error;
468	n->n_error = 0;
469      } else {
470	/*
471	 * Make sure remount gets this node
472	 */
473	n->n_error = -1;
474      }
475    }
476  }
477
478  /*
479   * If any unmounts failed then remount the
480   * whole lot...
481   */
482  if (glob_error) {
483    glob_error = amfs_nfsx_remount(am, mf, TRUE);
484    if (glob_error) {
485      errno = glob_error;	/* XXX */
486      plog(XLOG_USER, "amfs_nfsx: remount of %s failed: %m", mf->mf_mount);
487    }
488    glob_error = EBUSY;
489  } else {
490    /*
491     * Remove all the mount points
492     */
493    for (n = nx->nx_v; n < nx->nx_v + nx->nx_c; n++) {
494      mntfs *m = n->n_mnt;
495      dlog("calling underlying umounted on %s", m->mf_mount);
496      if (m->mf_ops->umounted)
497	m->mf_ops->umounted(m);
498
499      if (n->n_error < 0) {
500	if (m->mf_fsflags & FS_MKMNT) {
501	  (void) rmdirs(m->mf_mount);
502	  m->mf_flags &= ~MFF_MKMNT;
503	}
504      }
505      free_mntfs(m);
506      n->n_mnt = 0;
507      n->n_error = -1;
508    }
509  }
510
511  return glob_error;
512}
513