summaryrefslogtreecommitdiffstats
path: root/contrib/amd/amd/amfs_generic.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/amd/amd/amfs_generic.c')
-rw-r--r--contrib/amd/amd/amfs_generic.c1262
1 files changed, 1262 insertions, 0 deletions
diff --git a/contrib/amd/amd/amfs_generic.c b/contrib/amd/amd/amfs_generic.c
new file mode 100644
index 0000000..3e7c365
--- /dev/null
+++ b/contrib/amd/amd/amfs_generic.c
@@ -0,0 +1,1262 @@
+/*
+ * Copyright (c) 1997-2006 Erez Zadok
+ * Copyright (c) 1990 Jan-Simon Pendry
+ * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
+ * Copyright (c) 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Jan-Simon Pendry at Imperial College, London.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgment:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *
+ * File: am-utils/amd/amfs_generic.c
+ *
+ */
+
+/*
+ * generic functions used by amfs filesystems, ripped out of amfs_auto.c.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+#include <am_defs.h>
+#include <amd.h>
+
+
+/****************************************************************************
+ *** MACROS ***
+ ****************************************************************************/
+#define IN_PROGRESS(cp) ((cp)->mp->am_mnt->mf_flags & MFF_MOUNTING)
+
+
+/****************************************************************************
+ *** STRUCTURES ***
+ ****************************************************************************/
+/*
+ * Mounting a file system may take a significant period of time. The
+ * problem is that if this is done in the main process thread then the
+ * entire automounter could be blocked, possibly hanging lots of processes
+ * on the system. Instead we use a continuation scheme to allow mounts to
+ * be attempted in a sub-process. When the sub-process exits we pick up the
+ * exit status (by convention a UN*X error number) and continue in a
+ * notifier. The notifier gets handed a data structure and can then
+ * determine whether the mount was successful or not. If not, it updates
+ * the data structure and tries again until there are no more ways to try
+ * the mount, or some other permanent error occurs. In the mean time no RPC
+ * reply is sent, even after the mount is successful. We rely on the RPC
+ * retry mechanism to resend the lookup request which can then be handled.
+ */
+struct continuation {
+ am_node *mp; /* Node we are trying to mount */
+ int retry; /* Try again? */
+ time_t start; /* Time we started this mount */
+ int callout; /* Callout identifier */
+ mntfs **mf; /* Current mntfs */
+};
+
+
+/****************************************************************************
+ *** FORWARD DEFINITIONS ***
+ ****************************************************************************/
+static am_node *amfs_lookup_node(am_node *mp, char *fname, int *error_return);
+static mntfs *amfs_lookup_one_mntfs(am_node *new_mp, mntfs *mf, char *ivec,
+ char *def_opts, char *pfname);
+static mntfs **amfs_lookup_mntfs(am_node *new_mp, int *error_return);
+static void amfs_cont(int rc, int term, opaque_t arg);
+static void amfs_retry(int rc, int term, opaque_t arg);
+static void free_continuation(struct continuation *cp);
+static int amfs_bgmount(struct continuation *cp);
+static char *amfs_parse_defaults(am_node *mp, mntfs *mf, char *def_opts);
+
+
+/****************************************************************************
+ *** FUNCTIONS ***
+ ****************************************************************************/
+static am_node *
+amfs_lookup_node(am_node *mp, char *fname, int *error_return)
+{
+ am_node *new_mp;
+ int error = 0; /* Error so far */
+ int in_progress = 0; /* # of (un)mount in progress */
+ mntfs *mf;
+ char *expanded_fname = 0;
+
+ dlog("in amfs_lookup_node");
+
+ /*
+ * If the server is shutting down
+ * then don't return information
+ * about the mount point.
+ */
+ if (amd_state == Finishing) {
+ if (mp->am_mnt == 0 || mp->am_mnt->mf_fsflags & FS_DIRECT) {
+ dlog("%s mount ignored - going down", fname);
+ } else {
+ dlog("%s/%s mount ignored - going down", mp->am_path, fname);
+ }
+ ereturn(ENOENT);
+ }
+
+ /*
+ * Handle special case of "." and ".."
+ */
+ if (fname[0] == '.') {
+ if (fname[1] == '\0')
+ return mp; /* "." is the current node */
+ if (fname[1] == '.' && fname[2] == '\0') {
+ if (mp->am_parent) {
+ dlog(".. in %s gives %s", mp->am_path, mp->am_parent->am_path);
+ return mp->am_parent; /* ".." is the parent node */
+ }
+ ereturn(ESTALE);
+ }
+ }
+
+ /*
+ * Check for valid key name.
+ * If it is invalid then pretend it doesn't exist.
+ */
+ if (!valid_key(fname)) {
+ plog(XLOG_WARNING, "Key \"%s\" contains a disallowed character", fname);
+ ereturn(ENOENT);
+ }
+
+ /*
+ * Expand key name.
+ * expanded_fname is now a private copy.
+ */
+ expanded_fname = expand_selectors(fname);
+
+ /*
+ * Search children of this node
+ */
+ for (new_mp = mp->am_child; new_mp; new_mp = new_mp->am_osib) {
+ if (FSTREQ(new_mp->am_name, expanded_fname)) {
+ if (new_mp->am_error) {
+ error = new_mp->am_error;
+ continue;
+ }
+
+ /*
+ * If the error code is undefined then it must be
+ * in progress.
+ */
+ mf = new_mp->am_mnt;
+ if (mf->mf_error < 0)
+ goto in_progrss;
+
+ /*
+ * If there was a previous error with this node
+ * then return that error code.
+ */
+ if (mf->mf_flags & MFF_ERROR) {
+ error = mf->mf_error;
+ continue;
+ }
+ if (!(mf->mf_flags & MFF_MOUNTED) || (mf->mf_flags & MFF_UNMOUNTING)) {
+ in_progrss:
+ /*
+ * If the fs is not mounted or it is unmounting then there
+ * is a background (un)mount in progress. In this case
+ * we just drop the RPC request (return nil) and
+ * wait for a retry, by which time the (un)mount may
+ * have completed.
+ */
+ dlog("ignoring mount of %s in %s -- %smounting in progress, flags %x",
+ expanded_fname, mf->mf_mount,
+ (mf->mf_flags & MFF_UNMOUNTING) ? "un" : "", mf->mf_flags);
+ in_progress++;
+ if (mf->mf_flags & MFF_UNMOUNTING) {
+ dlog("will remount later");
+ new_mp->am_flags |= AMF_REMOUNT;
+ }
+ continue;
+ }
+
+ /*
+ * Otherwise we have a hit: return the current mount point.
+ */
+ dlog("matched %s in %s", expanded_fname, new_mp->am_path);
+ XFREE(expanded_fname);
+ return new_mp;
+ }
+ }
+
+ if (in_progress) {
+ dlog("Waiting while %d mount(s) in progress", in_progress);
+ XFREE(expanded_fname);
+ ereturn(-1);
+ }
+
+ /*
+ * If an error occurred then return it.
+ */
+ if (error) {
+ dlog("Returning error: %s", strerror(error));
+ XFREE(expanded_fname);
+ ereturn(error);
+ }
+
+ /*
+ * If the server is going down then just return,
+ * don't try to mount any more file systems
+ */
+ if ((int) amd_state >= (int) Finishing) {
+ dlog("not found - server going down anyway");
+ ereturn(ENOENT);
+ }
+
+ /*
+ * Allocate a new map
+ */
+ new_mp = get_ap_child(mp, expanded_fname);
+ XFREE(expanded_fname);
+ if (new_mp == 0)
+ ereturn(ENOSPC);
+
+ *error_return = -1;
+ return new_mp;
+}
+
+
+
+static mntfs *
+amfs_lookup_one_mntfs(am_node *new_mp, mntfs *mf, char *ivec,
+ char *def_opts, char *pfname)
+{
+ am_ops *p;
+ am_opts *fs_opts;
+ mntfs *new_mf;
+ char *mp_dir = 0;
+#ifdef HAVE_FS_AUTOFS
+ int on_autofs = 1;
+#endif /* HAVE_FS_AUTOFS */
+
+ /* match the operators */
+ fs_opts = CALLOC(am_opts);
+ p = ops_match(fs_opts, ivec, def_opts, new_mp->am_path,
+ pfname, mf->mf_info);
+#ifdef HAVE_FS_AUTOFS
+ /* XXX: this should be factored out into an autofs-specific function */
+ if (new_mp->am_flags & AMF_AUTOFS) {
+ /* ignore user-provided fs if we're using autofs */
+ if (fs_opts->opt_sublink) {
+ /*
+ * For sublinks we need to use a hack with autofs:
+ * mount the filesystem on the original opt_fs (which is NOT an
+ * autofs mountpoint) and symlink (or lofs-mount) to it from
+ * the autofs mountpoint.
+ */
+ on_autofs = 0;
+ mp_dir = fs_opts->opt_fs;
+ } else {
+ if (p->autofs_fs_flags & FS_ON_AUTOFS) {
+ mp_dir = new_mp->am_path;
+ } else {
+ mp_dir = fs_opts->opt_fs;
+ on_autofs = 0;
+ }
+ }
+ } else
+#endif /* HAVE_FS_AUTOFS */
+ mp_dir = fs_opts->opt_fs;
+
+ /*
+ * Find or allocate a filesystem for this node.
+ */
+ new_mf = find_mntfs(p, fs_opts,
+ mp_dir,
+ fs_opts->fs_mtab,
+ def_opts,
+ fs_opts->opt_opts,
+ fs_opts->opt_remopts);
+
+ /*
+ * See whether this is a real filesystem
+ */
+ p = new_mf->mf_ops;
+ if (p == &amfs_error_ops) {
+ plog(XLOG_MAP, "Map entry %s for %s did not match", ivec, new_mp->am_path);
+ free_mntfs(new_mf);
+ return NULL;
+ }
+
+ dlog("Got a hit with %s", p->fs_type);
+
+#ifdef HAVE_FS_AUTOFS
+ if (new_mp->am_flags & AMF_AUTOFS && on_autofs) {
+ new_mf->mf_flags |= MFF_ON_AUTOFS;
+ new_mf->mf_fsflags = new_mf->mf_ops->autofs_fs_flags;
+ }
+ /*
+ * A new filesystem is an autofs filesystems if:
+ * 1. it claims it can be one (has the FS_AUTOFS flag)
+ * 2. autofs is enabled system-wide
+ * 3. either has an autofs parent,
+ * or it is explicitly requested to be autofs.
+ */
+ if (new_mf->mf_ops->autofs_fs_flags & FS_AUTOFS &&
+ amd_use_autofs &&
+ ((mf->mf_flags & MFF_IS_AUTOFS) ||
+ (new_mf->mf_fo && new_mf->mf_fo->opt_mount_type &&
+ STREQ(new_mf->mf_fo->opt_mount_type, "autofs"))))
+ new_mf->mf_flags |= MFF_IS_AUTOFS;
+#endif /* HAVE_FS_AUTOFS */
+
+ return new_mf;
+}
+
+
+static mntfs **
+amfs_lookup_mntfs(am_node *new_mp, int *error_return)
+{
+ am_node *mp;
+ char *info; /* Mount info - where to get the file system */
+ char **ivecs, **cur_ivec; /* Split version of info */
+ int num_ivecs;
+ char *orig_def_opts; /* Original Automount options */
+ char *def_opts; /* Automount options */
+ int error = 0; /* Error so far */
+ char path_name[MAXPATHLEN]; /* General path name buffer */
+ char *pfname; /* Path for database lookup */
+ mntfs *mf, **mf_array;
+ int count;
+
+ dlog("in amfs_lookup_mntfs");
+
+ mp = new_mp->am_parent;
+
+ /*
+ * If we get here then this is a reference to an,
+ * as yet, unknown name so we need to search the mount
+ * map for it.
+ */
+ if (mp->am_pref) {
+ if (strlen(mp->am_pref) + strlen(new_mp->am_name) >= sizeof(path_name))
+ ereturn(ENAMETOOLONG);
+ xsnprintf(path_name, sizeof(path_name), "%s%s", mp->am_pref, new_mp->am_name);
+ pfname = path_name;
+ } else {
+ pfname = new_mp->am_name;
+ }
+
+ mf = mp->am_mnt;
+
+ dlog("will search map info in %s to find %s", mf->mf_info, pfname);
+ /*
+ * Consult the oracle for some mount information.
+ * info is malloc'ed and belongs to this routine.
+ * It ends up being free'd in free_continuation().
+ *
+ * Note that this may return -1 indicating that information
+ * is not yet available.
+ */
+ error = mapc_search((mnt_map *) mf->mf_private, pfname, &info);
+ if (error) {
+ if (error > 0)
+ plog(XLOG_MAP, "No map entry for %s", pfname);
+ else
+ plog(XLOG_MAP, "Waiting on map entry for %s", pfname);
+ ereturn(error);
+ }
+ dlog("mount info is %s", info);
+
+ /*
+ * Split info into an argument vector.
+ * The vector is malloc'ed and belongs to
+ * this routine. It is free'd further down.
+ *
+ * Note: the vector pointers point into info, so don't free it!
+ */
+ ivecs = strsplit(info, ' ', '\"');
+
+ if (mf->mf_auto)
+ def_opts = mf->mf_auto;
+ else
+ def_opts = "";
+
+ orig_def_opts = amfs_parse_defaults(mp, mf, strdup(def_opts));
+ def_opts = strdup(orig_def_opts);
+
+ /* first build our defaults */
+ num_ivecs = 0;
+ for (cur_ivec = ivecs; *cur_ivec; cur_ivec++) {
+ if (**cur_ivec == '-') {
+ /*
+ * Pick up new defaults
+ */
+ char *new_def_opts = str3cat(NULL, def_opts, ";", *cur_ivec + 1);
+ XFREE(def_opts);
+ def_opts = new_def_opts;
+ dlog("Setting def_opts to \"%s\"", def_opts);
+ continue;
+ } else
+ num_ivecs++;
+ }
+
+ mf_array = calloc(num_ivecs + 1, sizeof(mntfs *));
+
+ /* construct the array of struct mntfs for this mount point */
+ for (count = 0, cur_ivec = ivecs; *cur_ivec; cur_ivec++) {
+ mntfs *new_mf;
+
+ if (**cur_ivec == '-') {
+ XFREE(def_opts);
+ if ((*cur_ivec)[1] == '\0') {
+ /*
+ * If we have a single dash '-' than we need to reset the
+ * default options.
+ */
+ def_opts = strdup(orig_def_opts);
+ dlog("Resetting the default options, a single dash '-' was found.");
+ } else {
+ /* append options to /default options */
+ def_opts = str3cat((char *) 0, orig_def_opts, ";", *cur_ivec + 1);
+ dlog("Resetting def_opts to \"%s\"", def_opts);
+ }
+ continue;
+ }
+
+ /*
+ * If a mntfs has already been found, and we find
+ * a cut then don't try any more locations.
+ *
+ * XXX: we do not know when the "/" was added as an equivalent for "||".
+ * It's undocumented, it might go away at any time. Caveat emptor.
+ */
+ if (STREQ(*cur_ivec, "/") || STREQ(*cur_ivec, "||")) {
+ if (count > 0) {
+ dlog("Cut: not trying any more locations for %s", mp->am_path);
+ break;
+ }
+ continue;
+ }
+
+ new_mf = amfs_lookup_one_mntfs(new_mp, mf, *cur_ivec, def_opts, pfname);
+ if (new_mf == NULL)
+ continue;
+ mf_array[count++] = new_mf;
+ }
+
+ /* We're done with ivecs */
+ XFREE(ivecs);
+ XFREE(info);
+ XFREE(orig_def_opts);
+ XFREE(def_opts);
+ if (count == 0) { /* no match */
+ XFREE(mf_array);
+ ereturn(ENOENT);
+ }
+
+ return mf_array;
+}
+
+
+/*
+ * The continuation function. This is called by
+ * the task notifier when a background mount attempt
+ * completes.
+ */
+static void
+amfs_cont(int rc, int term, opaque_t arg)
+{
+ struct continuation *cp = (struct continuation *) arg;
+ am_node *mp = cp->mp;
+ mntfs *mf = mp->am_mnt;
+
+ dlog("amfs_cont: '%s'", mp->am_path);
+
+ /*
+ * Definitely not trying to mount at the moment
+ */
+ mf->mf_flags &= ~MFF_MOUNTING;
+
+ /*
+ * While we are mounting - try to avoid race conditions
+ */
+ new_ttl(mp);
+
+ /*
+ * Wakeup anything waiting for this mount
+ */
+ wakeup(get_mntfs_wchan(mf));
+
+ /*
+ * Check for termination signal or exit status...
+ */
+ if (rc || term) {
+#ifdef HAVE_FS_AUTOFS
+ if (mf->mf_flags & MFF_IS_AUTOFS &&
+ !(mf->mf_flags & MFF_MOUNTED))
+ autofs_release_fh(mp);
+#endif /* HAVE_FS_AUTOFS */
+
+ if (term) {
+ /*
+ * Not sure what to do for an error code.
+ */
+ mf->mf_error = EIO; /* XXX ? */
+ mf->mf_flags |= MFF_ERROR;
+ plog(XLOG_ERROR, "mount for %s got signal %d", mp->am_path, term);
+ } else {
+ /*
+ * Check for exit status...
+ */
+#ifdef __linux__
+ /*
+ * HACK ALERT!
+ *
+ * On Linux (and maybe not only) it's possible to run
+ * an amd which "knows" how to mount certain combinations
+ * of nfs_proto/nfs_version which the kernel doesn't grok.
+ * So if we got an EINVAL and we have a server that's not
+ * using NFSv2/UDP, try again with NFSv2/UDP.
+ *
+ * Too bad that there is no way to dynamically determine
+ * what combinations the _client_ supports, as opposed to
+ * what the _server_ supports...
+ */
+ if (rc == EINVAL &&
+ mf->mf_server &&
+ (mf->mf_server->fs_version != 2 ||
+ !STREQ(mf->mf_server->fs_proto, "udp")))
+ mf->mf_flags |= MFF_NFS_SCALEDOWN;
+ else
+#endif /* __linux__ */
+ {
+ mf->mf_error = rc;
+ mf->mf_flags |= MFF_ERROR;
+ errno = rc; /* XXX */
+ if (!STREQ(mp->am_mnt->mf_ops->fs_type, "linkx"))
+ plog(XLOG_ERROR, "%s: mount (amfs_cont): %m", mp->am_path);
+ }
+ }
+
+ if (!(mf->mf_flags & MFF_NFS_SCALEDOWN)) {
+ /*
+ * If we get here then that attempt didn't work, so
+ * move the info vector pointer along by one and
+ * call the background mount routine again
+ */
+ amd_stats.d_merr++;
+ cp->mf++;
+ }
+ amfs_bgmount(cp);
+ if (mp->am_error > 0)
+ assign_error_mntfs(mp);
+ } else {
+ /*
+ * The mount worked.
+ */
+ dlog("Mounting %s returned success", cp->mp->am_path);
+ am_mounted(cp->mp);
+ free_continuation(cp);
+ }
+
+ reschedule_timeout_mp();
+}
+
+
+/*
+ * Retry a mount
+ */
+static void
+amfs_retry(int rc, int term, opaque_t arg)
+{
+ struct continuation *cp = (struct continuation *) arg;
+ am_node *mp = cp->mp;
+ int error = 0;
+
+ dlog("Commencing retry for mount of %s", mp->am_path);
+
+ new_ttl(mp);
+
+ if ((cp->start + ALLOWED_MOUNT_TIME) < clocktime(NULL)) {
+ /*
+ * The entire mount has timed out. Set the error code and skip past all
+ * the mntfs's so that amfs_bgmount will not have any more
+ * ways to try the mount, thus causing an error.
+ */
+ plog(XLOG_INFO, "mount of \"%s\" has timed out", mp->am_path);
+ error = ETIMEDOUT;
+ while (*cp->mf)
+ cp->mf++;
+ /* explicitly forbid further retries after timeout */
+ cp->retry = FALSE;
+ }
+ if (error || !IN_PROGRESS(cp))
+ error = amfs_bgmount(cp);
+
+ reschedule_timeout_mp();
+}
+
+
+/*
+ * Discard an old continuation
+ */
+static void
+free_continuation(struct continuation *cp)
+{
+ mntfs **mfp;
+
+ dlog("free_continuation");
+ if (cp->callout)
+ untimeout(cp->callout);
+ /*
+ * we must free the mntfs's in the list.
+ * so free all of them if there was an error,
+ * or free all but the used one, if the mount succeeded.
+ */
+ for (mfp = cp->mp->am_mfarray; *mfp; mfp++) {
+ free_mntfs(*mfp);
+ }
+ XFREE(cp->mp->am_mfarray);
+ cp->mp->am_mfarray = 0;
+ XFREE(cp);
+}
+
+
+/*
+ * Pick a file system to try mounting and
+ * do that in the background if necessary
+ *
+For each location:
+ discard previous mount location if required
+ fetch next mount location
+ if the filesystem failed to be mounted then
+ this_error = error from filesystem
+ goto failed
+ if the filesystem is mounting or unmounting then
+ goto retry;
+ if the fileserver is down then
+ this_error = EIO
+ continue;
+ if the filesystem is already mounted
+ break
+ fi
+
+ this_error = initialize mount point
+
+ if no error on this mount and mount is delayed then
+ this_error = -1
+ fi
+ if this_error < 0 then
+ retry = true
+ fi
+ if no error on this mount then
+ if mount in background then
+ run mount in background
+ return -1
+ else
+ this_error = mount in foreground
+ fi
+ fi
+ if an error occurred on this mount then
+ update stats
+ save error in mount point
+ fi
+endfor
+ */
+static int
+amfs_bgmount(struct continuation *cp)
+{
+ am_node *mp = cp->mp;
+ mntfs *mf; /* Current mntfs */
+ int this_error = -1; /* Per-mount error */
+ int hard_error = -1; /* Cumulative per-node error */
+
+ if (mp->am_mnt)
+ free_mntfs(mp->am_mnt);
+
+ /*
+ * Try to mount each location.
+ * At the end:
+ * hard_error == 0 indicates something was mounted.
+ * hard_error > 0 indicates everything failed with a hard error
+ * hard_error < 0 indicates nothing could be mounted now
+ */
+ for (mp->am_mnt = *cp->mf; *cp->mf; cp->mf++, mp->am_mnt = *cp->mf) {
+ am_ops *p;
+
+ mf = dup_mntfs(mp->am_mnt);
+ p = mf->mf_ops;
+
+ if (hard_error < 0)
+ hard_error = this_error;
+ this_error = 0;
+
+ if (mf->mf_error > 0) {
+ this_error = mf->mf_error;
+ goto failed;
+ }
+
+ if (mf->mf_flags & (MFF_MOUNTING | MFF_UNMOUNTING)) {
+ /*
+ * Still mounting - retry later
+ */
+ dlog("mount of \"%s\" already pending", mf->mf_info);
+ goto retry;
+ }
+
+ if (FSRV_ISDOWN(mf->mf_server)) {
+ /*
+ * Would just mount from the same place
+ * as a hung mount - so give up
+ */
+ dlog("%s is already hung - giving up", mf->mf_server->fs_host);
+ this_error = EIO;
+ goto failed;
+ }
+
+ if (mp->am_link) {
+ XFREE(mp->am_link);
+ mp->am_link = NULL;
+ }
+ if (mf->mf_fo && mf->mf_fo->opt_sublink)
+ mp->am_link = strdup(mf->mf_fo->opt_sublink);
+
+ /*
+ * Will usually need to play around with the mount nodes
+ * file attribute structure. This must be done here.
+ * Try and get things initialized, even if the fileserver
+ * is not known to be up. In the common case this will
+ * progress things faster.
+ */
+
+ /*
+ * Fill in attribute fields.
+ */
+ if (mf->mf_fsflags & FS_DIRECTORY)
+ mk_fattr(&mp->am_fattr, NFDIR);
+ else
+ mk_fattr(&mp->am_fattr, NFLNK);
+
+ if (mf->mf_flags & MFF_MOUNTED) {
+ dlog("duplicate mount of \"%s\" ...", mf->mf_info);
+ /*
+ * Skip initial processing of the mountpoint if already mounted.
+ * This could happen if we have multiple sublinks into the same f/s,
+ * or if we are restarting an already-mounted filesystem.
+ */
+ goto already_mounted;
+ }
+
+ if (mf->mf_fo && mf->mf_fo->fs_mtab) {
+ plog(XLOG_MAP, "Trying mount of %s on %s fstype %s mount_type %s",
+ mf->mf_fo->fs_mtab, mf->mf_mount, p->fs_type,
+ mp->am_flags & AMF_AUTOFS ? "autofs" : "non-autofs");
+ }
+
+ if (p->fs_init && !(mf->mf_flags & MFF_RESTART))
+ this_error = p->fs_init(mf);
+
+ if (this_error > 0)
+ goto failed;
+ if (this_error < 0)
+ goto retry;
+
+ if (mf->mf_fo && mf->mf_fo->opt_delay) {
+ /*
+ * If there is a delay timer on the mount
+ * then don't try to mount if the timer
+ * has not expired.
+ */
+ int i = atoi(mf->mf_fo->opt_delay);
+ time_t now = clocktime(NULL);
+ if (i > 0 && now < (cp->start + i)) {
+ dlog("Mount of %s delayed by %lds", mf->mf_mount, (long) (i - now + cp->start));
+ goto retry;
+ }
+ }
+
+ /*
+ * If the directory is not yet made and it needs to be made, then make it!
+ */
+ if (!(mf->mf_flags & MFF_MKMNT) && mf->mf_fsflags & FS_MKMNT) {
+ plog(XLOG_INFO, "creating mountpoint directory '%s'", mf->mf_mount);
+ this_error = mkdirs(mf->mf_mount, 0555);
+ if (this_error) {
+ plog(XLOG_ERROR, "mkdirs failed: %s", strerror(this_error));
+ goto failed;
+ }
+ mf->mf_flags |= MFF_MKMNT;
+ }
+
+#ifdef HAVE_FS_AUTOFS
+ if (mf->mf_flags & MFF_IS_AUTOFS)
+ if ((this_error = autofs_get_fh(mp)))
+ goto failed;
+#endif /* HAVE_FS_AUTOFS */
+
+ already_mounted:
+ mf->mf_flags |= MFF_MOUNTING;
+ if (mf->mf_fsflags & FS_MBACKGROUND) {
+ dlog("backgrounding mount of \"%s\"", mf->mf_mount);
+ if (cp->callout) {
+ untimeout(cp->callout);
+ cp->callout = 0;
+ }
+
+ /* actually run the task, backgrounding as necessary */
+ run_task(mount_node, (opaque_t) mp, amfs_cont, (opaque_t) cp);
+ return -1;
+ } else {
+ dlog("foreground mount of \"%s\" ...", mf->mf_mount);
+ this_error = mount_node((opaque_t) mp);
+ }
+
+ mf->mf_flags &= ~MFF_MOUNTING;
+ if (this_error > 0)
+ goto failed;
+ if (this_error == 0) {
+ am_mounted(mp);
+ break; /* Success */
+ }
+
+ retry:
+ if (!cp->retry)
+ continue;
+ dlog("will retry ...\n");
+
+ /*
+ * Arrange that amfs_bgmount is called
+ * after anything else happens.
+ */
+ dlog("Arranging to retry mount of %s", mp->am_path);
+ sched_task(amfs_retry, (opaque_t) cp, get_mntfs_wchan(mf));
+ if (cp->callout)
+ untimeout(cp->callout);
+ cp->callout = timeout(RETRY_INTERVAL, wakeup,
+ (opaque_t) get_mntfs_wchan(mf));
+
+ mp->am_ttl = clocktime(NULL) + RETRY_INTERVAL;
+
+ /*
+ * Not done yet - so don't return anything
+ */
+ return -1;
+
+ failed:
+ amd_stats.d_merr++;
+ mf->mf_error = this_error;
+ mf->mf_flags |= MFF_ERROR;
+#ifdef HAVE_FS_AUTOFS
+ if (mp->am_autofs_fh)
+ autofs_release_fh(mp);
+#endif /* HAVE_FS_AUTOFS */
+ if (mf->mf_flags & MFF_MKMNT) {
+ rmdirs(mf->mf_mount);
+ mf->mf_flags &= ~MFF_MKMNT;
+ }
+ /*
+ * Wakeup anything waiting for this mount
+ */
+ wakeup(get_mntfs_wchan(mf));
+ free_mntfs(mf);
+ /* continue */
+ }
+
+ /*
+ * If we get here, then either the mount succeeded or
+ * there is no more mount information available.
+ */
+ if (this_error) {
+ mp->am_mnt = mf = new_mntfs();
+
+#ifdef HAVE_FS_AUTOFS
+ if (mp->am_flags & AMF_AUTOFS)
+ autofs_mount_failed(mp);
+ else
+#endif /* HAVE_FS_AUTOFS */
+ nfs_quick_reply(mp, this_error);
+
+ if (hard_error <= 0)
+ hard_error = this_error;
+ if (hard_error < 0)
+ hard_error = ETIMEDOUT;
+
+ /*
+ * Set a small(ish) timeout on an error node if
+ * the error was not a time out.
+ */
+ switch (hard_error) {
+ case ETIMEDOUT:
+ case EWOULDBLOCK:
+ case EIO:
+ mp->am_timeo = 17;
+ break;
+ default:
+ mp->am_timeo = 5;
+ break;
+ }
+ new_ttl(mp);
+ } else {
+ mf = mp->am_mnt;
+ /*
+ * Wakeup anything waiting for this mount
+ */
+ wakeup(get_mntfs_wchan(mf));
+ hard_error = 0;
+ }
+
+ /*
+ * Make sure that the error value in the mntfs has a
+ * reasonable value.
+ */
+ if (mf->mf_error < 0) {
+ mf->mf_error = hard_error;
+ if (hard_error)
+ mf->mf_flags |= MFF_ERROR;
+ }
+
+ /*
+ * In any case we don't need the continuation any more
+ */
+ free_continuation(cp);
+
+ return hard_error;
+}
+
+
+static char *
+amfs_parse_defaults(am_node *mp, mntfs *mf, char *def_opts)
+{
+ char *dflts;
+ char *dfl;
+ char **rvec = NULL;
+ struct mnt_map *mm = (mnt_map *) mf->mf_private;
+
+ dlog("determining /defaults entry value");
+
+ /*
+ * Find out if amd.conf overrode any map-specific /defaults.
+ *
+ * HACK ALERT: there's no easy way to find out what the map mount point is
+ * at this point, so I am forced to initialize the mnt_map->cfm field here
+ * for the first time, upon the very first search for a /defaults entry in
+ * this map. This initialization is much better done in mapc_create(),
+ * but it's impossible to do that there with the current code structure.
+ */
+ if (mm->cfm == NULL) { /* then initialize it for first time */
+ mm->cfm = find_cf_map(mf->mf_mount);
+ }
+ if (mm->cfm && mm->cfm->cfm_defaults) {
+ dlog("map %s map_defaults override: %s", mf->mf_mount, mm->cfm->cfm_defaults);
+ dflts = strdup(mm->cfm->cfm_defaults);
+ } else if (mapc_search(mm, "/defaults", &dflts) == 0) {
+ dlog("/defaults gave %s", dflts);
+ } else {
+ return def_opts; /* if nothing found */
+ }
+
+ /* trim leading '-' in case thee's one */
+ if (*dflts == '-')
+ dfl = dflts + 1;
+ else
+ dfl = dflts;
+
+ /*
+ * Chop the defaults up
+ */
+ rvec = strsplit(dfl, ' ', '\"');
+
+ if (gopt.flags & CFM_SELECTORS_IN_DEFAULTS) {
+ /*
+ * Pick whichever first entry matched the list of selectors.
+ * Strip the selectors from the string, and assign to dfl the
+ * rest of the string.
+ */
+ if (rvec) {
+ am_opts ap;
+ am_ops *pt;
+ char **sp = rvec;
+ while (*sp) { /* loop until you find something, if any */
+ memset((char *) &ap, 0, sizeof(am_opts));
+ /*
+ * This next routine cause many spurious "expansion of ... is"
+ * messages, which are ignored, b/c all we need out of this
+ * routine is to match selectors. These spurious messages may
+ * be wrong, esp. if they try to expand ${key} b/c it will
+ * get expanded to "/defaults"
+ */
+ pt = ops_match(&ap, *sp, "", mp->am_path, "/defaults",
+ mp->am_parent->am_mnt->mf_info);
+ free_opts(&ap); /* don't leak */
+ if (pt == &amfs_error_ops) {
+ plog(XLOG_MAP, "did not match defaults for \"%s\"", *sp);
+ } else {
+ dfl = strip_selectors(*sp, "/defaults");
+ plog(XLOG_MAP, "matched default selectors \"%s\"", dfl);
+ break;
+ }
+ ++sp;
+ }
+ }
+ } else { /* not selectors_in_defaults */
+ /*
+ * Extract first value
+ */
+ dfl = rvec[0];
+ }
+
+ /*
+ * If there were any values at all...
+ */
+ if (dfl) {
+ /*
+ * Log error if there were other values
+ */
+ if (!(gopt.flags & CFM_SELECTORS_IN_DEFAULTS) && rvec[1]) {
+ dlog("/defaults chopped into %s", dfl);
+ plog(XLOG_USER, "More than a single value for /defaults in %s", mf->mf_info);
+ }
+
+ /*
+ * Prepend to existing defaults if they exist,
+ * otherwise just use these defaults.
+ */
+ if (*def_opts && *dfl) {
+ size_t l = strlen(def_opts) + strlen(dfl) + 2;
+ char *nopts = (char *) xmalloc(l);
+ xsnprintf(nopts, l, "%s;%s", dfl, def_opts);
+ XFREE(def_opts);
+ def_opts = nopts;
+ } else if (*dfl) {
+ def_opts = strealloc(def_opts, dfl);
+ }
+ }
+
+ XFREE(dflts);
+
+ /* don't need info vector any more */
+ if (rvec)
+ XFREE(rvec);
+
+ return def_opts;
+}
+
+
+am_node *
+amfs_generic_mount_child(am_node *new_mp, int *error_return)
+{
+ int error;
+ struct continuation *cp; /* Continuation structure if need to mount */
+
+ dlog("in amfs_generic_mount_child");
+
+ *error_return = error = 0; /* Error so far */
+
+ /* we have an errorfs attached to the am_node, free it */
+ free_mntfs(new_mp->am_mnt);
+ new_mp->am_mnt = 0;
+
+ /*
+ * Construct a continuation
+ */
+ cp = ALLOC(struct continuation);
+ cp->callout = 0;
+ cp->mp = new_mp;
+ cp->retry = TRUE;
+ cp->start = clocktime(NULL);
+ cp->mf = new_mp->am_mfarray;
+
+ /*
+ * Try and mount the file system. If this succeeds immediately (possible
+ * for a ufs file system) then return the attributes, otherwise just
+ * return an error.
+ */
+ error = amfs_bgmount(cp);
+ reschedule_timeout_mp();
+ if (!error)
+ return new_mp;
+
+ /*
+ * Code for quick reply. If current_transp is set, then it's the
+ * transp that's been passed down from nfs_program_2() or from
+ * autofs_program_[123]().
+ * If new_mp->am_transp is not already set, set it by copying in
+ * current_transp. Once am_transp is set, nfs_quick_reply() and
+ * autofs_mount_succeeded() can use it to send a reply to the
+ * client that requested this mount.
+ */
+ if (current_transp && !new_mp->am_transp) {
+ dlog("Saving RPC transport for %s", new_mp->am_path);
+ new_mp->am_transp = (SVCXPRT *) xmalloc(sizeof(SVCXPRT));
+ *(new_mp->am_transp) = *current_transp;
+ }
+ if (error && (new_mp->am_mnt->mf_ops == &amfs_error_ops))
+ new_mp->am_error = error;
+
+ if (new_mp->am_error > 0)
+ assign_error_mntfs(new_mp);
+
+ ereturn(error);
+}
+
+
+/*
+ * Automount interface to RPC lookup routine
+ * Find the corresponding entry and return
+ * the file handle for it.
+ */
+am_node *
+amfs_generic_lookup_child(am_node *mp, char *fname, int *error_return, int op)
+{
+ am_node *new_mp;
+ mntfs **mf_array;
+ int mp_error;
+
+ dlog("in amfs_generic_lookup_child");
+
+ *error_return = 0;
+ new_mp = amfs_lookup_node(mp, fname, error_return);
+
+ /* return if we got an error */
+ if (!new_mp || *error_return > 0)
+ return new_mp;
+
+ /* also return if it's already mounted and known to be up */
+ if (*error_return == 0 && FSRV_ISUP(new_mp->am_mnt->mf_server))
+ return new_mp;
+
+ switch (op) {
+ case VLOOK_DELETE:
+ /*
+ * If doing a delete then don't create again!
+ */
+ ereturn(ENOENT);
+ case VLOOK_LOOKUP:
+ return new_mp;
+ }
+
+ /* save error_return */
+ mp_error = *error_return;
+
+ mf_array = amfs_lookup_mntfs(new_mp, error_return);
+ if (!mf_array) {
+ new_mp->am_error = new_mp->am_mnt->mf_error = *error_return;
+ free_map(new_mp);
+ return NULL;
+ }
+
+ /*
+ * Already mounted but known to be down:
+ * check if we have any alternatives to mount
+ */
+ if (mp_error == 0) {
+ mntfs **mfp;
+ for (mfp = mf_array; *mfp; mfp++)
+ if (*mfp != new_mp->am_mnt)
+ break;
+ if (*mfp != NULL) {
+ /*
+ * we found an alternative, so try mounting again.
+ */
+ *error_return = -1;
+ } else {
+ for (mfp = mf_array; *mfp; mfp++)
+ free_mntfs(*mfp);
+ XFREE(mf_array);
+ if (new_mp->am_flags & AMF_SOFTLOOKUP) {
+ ereturn(EIO);
+ } else {
+ *error_return = 0;
+ return new_mp;
+ }
+ }
+ }
+
+ /* store the array inside the am_node */
+ new_mp->am_mfarray = mf_array;
+
+ /*
+ * Note: while it might seem like a good idea to prioritize
+ * the list of mntfs's we got here, it probably isn't.
+ * It would ignore the ordering of entries specified by the user,
+ * which is counterintuitive and confusing.
+ */
+ return new_mp;
+}
+
+
+void
+amfs_generic_mounted(mntfs *mf)
+{
+ amfs_mkcacheref(mf);
+}
+
+
+/*
+ * Unmount an automount sub-node
+ */
+int
+amfs_generic_umount(am_node *mp, mntfs *mf)
+{
+ int error = 0;
+
+#ifdef HAVE_FS_AUTOFS
+ int unmount_flags = (mf->mf_flags & MFF_ON_AUTOFS) ? AMU_UMOUNT_AUTOFS : 0;
+ if (mf->mf_flags & MFF_IS_AUTOFS)
+ error = UMOUNT_FS(mp->am_path, mnttab_file_name, unmount_flags);
+#endif /* HAVE_FS_AUTOFS */
+
+ return error;
+}
+
+
+char *
+amfs_generic_match(am_opts *fo)
+{
+ char *p;
+
+ if (!fo->opt_rfs) {
+ plog(XLOG_USER, "amfs_generic_match: no mount point named (rfs:=)");
+ return 0;
+ }
+ if (!fo->opt_fs) {
+ plog(XLOG_USER, "amfs_generic_match: no map named (fs:=)");
+ return 0;
+ }
+
+ /*
+ * Swap round fs:= and rfs:= options
+ * ... historical (jsp)
+ */
+ p = fo->opt_rfs;
+ fo->opt_rfs = fo->opt_fs;
+ fo->opt_fs = p;
+
+ /*
+ * mtab entry turns out to be the name of the mount map
+ */
+ return strdup(fo->opt_rfs ? fo->opt_rfs : ".");
+}
OpenPOWER on IntegriCloud