diff options
Diffstat (limited to 'contrib/amd/amd/amfs_generic.c')
-rw-r--r-- | contrib/amd/amd/amfs_generic.c | 1262 |
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 : "."); +} |