/*
 * Copyright (c) 1990 Jan-Simon Pendry
 * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
 * Copyright (c) 1990, 1993
 *	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 acknowledgement:
 *	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.
 *
 *	@(#)afs_ops.c	8.1 (Berkeley) 6/6/93
 *
 * $Id: afs_ops.c,v 1.2 1995/05/30 03:45:43 rgrimes Exp $
 *
 */

#include "am.h"

#define NFS
#define NFSCLIENT

#include <sys/stat.h>
#ifdef NFS_3
typedef nfs_fh fhandle_t;
#endif /* NFS_3 */
#ifdef NFS_HDR
#include NFS_HDR
#endif /* NFS_HDR */
#include <sys/mount.h>
#include "mount.h"

/*
 * Automount file system
 * Direct file system
 * Root file system
 * Top-level file system
 */

/*
 * Interval between forced retries of a mount.
 */
#define RETRY_INTERVAL	2

/*
 * AFS needs nothing in particular.
 */
static char *afs_match P((am_opts *fo));
static char *afs_match(fo)
am_opts *fo;
{
	char *p = fo->opt_rfs;
	if (!fo->opt_rfs) {
		plog(XLOG_USER, "auto: no mount point named (rfs:=)");
		return 0;
	}
	if (!fo->opt_fs) {
		plog(XLOG_USER, "auto: no map named (fs:=)");
		return 0;
	}
	/*
	 * Swap round fs:= and rfs:= options
	 * ... historical (jsp)
	 */
	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 : ".");
}

/*
 * Mount an automounter directory.
 * The automounter is connected into the system
 * as a user-level NFS server.  mount_toplvl constructs
 * the necessary NFS parameters to be given to the
 * kernel so that it will talk back to us.
 */
static int mount_toplvl P((char *dir, char *opts));
static int mount_toplvl(dir, opts)
char *dir;
char *opts;
{
	struct nfs_args nfs_args;
	struct mntent mnt;
	int retry;
	struct sockaddr_in sin;
	unsigned short port;
	int flags;
	extern nfs_fh *root_fh();
	nfs_fh *fhp;
	char fs_hostname[MAXHOSTNAMELEN+MAXPATHLEN+1];

	MTYPE_TYPE type = MOUNT_TYPE_NFS;

	bzero((voidp) &nfs_args, sizeof(nfs_args));	/* Paranoid */

	mnt.mnt_dir = dir;
	mnt.mnt_fsname = pid_fsname;
	mnt.mnt_type = MNTTYPE_AUTO;
	mnt.mnt_opts = opts;
	mnt.mnt_freq = 0;
	mnt.mnt_passno = 0;

	retry = hasmntval(&mnt, "retry");
	if (retry <= 0)
		retry = 2;	/* XXX */

	/*
	 * get fhandle of remote path for automount point
	 */

	fhp = root_fh(dir);
	if (!fhp) {
		plog(XLOG_FATAL, "Can't find root file handle for %s", dir);
		return EINVAL;
	}

	NFS_FH_DREF(nfs_args.fh, (NFS_FH_TYPE) fhp);
#ifdef NFSv3
	nfs_args.fhsize = FHSIZE;
#endif

	/*
	 * Create sockaddr to point to the local machine.  127.0.0.1
	 * is not used since that will not work in HP-UX clusters and
	 * this is no more expensive.
	 */
	bzero((voidp) &sin, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr = myipaddr;
	if (port = hasmntval(&mnt, "port")) {
		sin.sin_port = htons(port);
	} else {
		plog(XLOG_ERROR, "no port number specified for %s", dir);
		return EINVAL;
	}

	/*
	 * set mount args
	 */
	NFS_SA_DREF(nfs_args, &sin);

	/*
	 * Make a ``hostname'' string for the kernel
	 */
#ifndef HOSTNAMESZ
#define	SHORT_MOUNT_NAME
#endif /* HOSTNAMESZ */
#ifdef SHORT_MOUNT_NAME
	sprintf(fs_hostname, "amd:%d", foreground ? mypid : getppid());
#else
	sprintf(fs_hostname, "pid%d@%s:%s", foreground ? mypid : getppid(), hostname, dir);
#endif /* SHORT_MOUNT_NAME */
	nfs_args.hostname = fs_hostname;
	nfs_args.flags |= NFSMNT_HOSTNAME;
#ifdef HOSTNAMESZ
	/*
	 * Most kernels have a name length restriction.
	 */
	if (strlen(fs_hostname) >= HOSTNAMESZ)
		strcpy(fs_hostname + HOSTNAMESZ - 3, "..");
#endif /* HOSTNAMESZ */

#ifdef NFSMNT_DUMBTIMR
	nfs_args.flags |= NFSMNT_DUMBTIMR;
	plog(XLOG_INFO, "defeating nfs window computation");
#endif

	/*
	 * Parse a subset of the standard nfs options.  The
	 * others are probably irrelevant for this application
	 */
	if (nfs_args.timeo = hasmntval(&mnt, "timeo"))
		nfs_args.flags |= NFSMNT_TIMEO;

	if (nfs_args.retrans = hasmntval(&mnt, "retrans"))
		nfs_args.flags |= NFSMNT_RETRANS;

#ifdef NFSMNT_BIODS
	if (nfs_args.biods = hasmntval(&mnt, "biods"))
		nfs_args.flags |= NFSMNT_BIODS;

#endif /* NFSMNT_BIODS */

#if defined(NFSMNT_ACREGMIN) && defined(NFSMNT_ACREGMAX)
	/*
	 * Don't cache attributes - they are changing under
	 * the kernel's feet...
	 */
	nfs_args.acregmin = nfs_args.acregmax = 1;
	nfs_args.flags |= NFSMNT_ACREGMIN|NFSMNT_ACREGMAX;
#endif /* defined(NFSMNT_ACREGMIN) && defined(NFSMNT_ACREGMAX) */
	/*
	 * These two are constructed internally by the calling routine
	 */
	if (hasmntopt(&mnt, MNTOPT_SOFT) != NULL)
		nfs_args.flags |= NFSMNT_SOFT;

#ifdef MNTOPT_INTR
	if (hasmntopt(&mnt, MNTOPT_INTR) != NULL)
		nfs_args.flags |= NFSMNT_INT;
#endif /* MNTOPT_INTR */

	flags = compute_mount_flags(&mnt);
#ifdef ULTRIX_HACK
	nfs_args.gfs_flags = flags;
	flags &= M_RDONLY;
	if (flags & M_RDONLY)
		nfs_args.flags |= NFSMNT_RONLY;
#endif /* ULTRIX_HACK */
	return mount_fs(&mnt, flags, (caddr_t) &nfs_args, retry, type);
}

static void afs_mkcacheref P((mntfs *mf));
static void afs_mkcacheref(mf)
mntfs *mf;
{
	/*
	 * Build a new map cache for this node, or re-use
	 * an existing cache for the same map.
	 */
	char *cache;
	if (mf->mf_fo && mf->mf_fo->opt_cache)
	  	cache = mf->mf_fo->opt_cache;
	else
	  	cache = "none";
	mf->mf_private = (voidp) mapc_find(mf->mf_info, cache);
	mf->mf_prfree = mapc_free;
}

/*
 * Mount the root...
 */
static int root_mount P((am_node *mp));
static int root_mount(mp)
am_node *mp;
{
	mntfs *mf = mp->am_mnt;

	mf->mf_mount = strealloc(mf->mf_mount, pid_fsname);
	mf->mf_private = (voidp) mapc_find(mf->mf_info, "");
	mf->mf_prfree = mapc_free;

	return 0;
}

/*
 * Mount a sub-mount
 */
static int afs_mount P((am_node *mp));
static int afs_mount(mp)
am_node *mp;
{
	mntfs *mf = mp->am_mnt;

	/*
	 * Pseudo-directories are used to provide some structure
	 * to the automounted directories instead
	 * of putting them all in the top-level automount directory.
	 *
	 * Here, just increment the parent's link count.
	 */
	mp->am_parent->am_fattr.nlink++;
	/*
	 * Info field of . means use parent's info field.
	 * Historical - not documented.
	 */
	if (mf->mf_info[0] == '.' && mf->mf_info[1] == '\0')
		mf->mf_info = strealloc(mf->mf_info, mp->am_parent->am_mnt->mf_info);
	/*
	 * Compute prefix:
	 *
	 * If there is an option prefix then use that else
	 * If the parent had a prefix then use that with name
	 *	of this node appended else
	 * Use the name of this node.
	 *
	 * That means if you want no prefix you must say so
	 * in the map.
	 */
	if (mf->mf_fo->opt_pref) {
		/*
		 * the prefix specified as an option
		 */
		mp->am_pref = strdup(mf->mf_fo->opt_pref);
	} else {
		/*
		 * else the parent's prefix
		 * followed by the name
		 * followed by /
		 */
		char *ppref = mp->am_parent->am_pref;
		if (ppref == 0)
			ppref = "";
		mp->am_pref = str3cat((char *) 0, ppref, mp->am_name, "/");
	}

	/*
	 * Attach a map cache
	 */
	afs_mkcacheref(mf);

	return 0;
}

/*
 * Mount the top-level
 */
static int toplvl_mount P((am_node *mp));
static int toplvl_mount(mp)
am_node *mp;
{
	mntfs *mf = mp->am_mnt;
	struct stat stb;
	char opts[256];
	int error;
	char *mnttype;

	/*
	 * Mounting the automounter.
	 * Make sure the mount directory exists, construct
	 * the mount options and call the mount_toplvl routine.
	 */

	if (stat(mp->am_path, &stb) < 0) {
		return errno;
	} else if ((stb.st_mode & S_IFMT) != S_IFDIR) {
		plog(XLOG_WARNING, "%s is not a directory", mp->am_path);
		return ENOTDIR;
	}

	if (mf->mf_ops == &toplvl_ops) mnttype = "indirect";
	else if (mf->mf_ops == &dfs_ops) mnttype = "direct";
#ifdef HAS_UNION_FS
	else if (mf->mf_ops == &union_ops) mnttype = "union";
#endif
	else mnttype = "auto";

	/*
	 * Construct some mount options
	 */
	sprintf(opts,
#ifdef MNTOPT_INTR
		"%s,%s,%s=%d,%s=%d,%s=%d,%s",
		MNTOPT_INTR,
#else
		"%s,%s=%d,%s=%d,%s=%d,%s",
#endif /* MNTOPT_INTR */
		"rw",
		"port", nfs_port,
		"timeo", afs_timeo,
		"retrans", afs_retrans,
		mnttype);

	error = mount_toplvl(mf->mf_mount, opts);
	if (error) {
		errno = error;
		plog(XLOG_FATAL, "mount_toplvl: %m");
		return error;
	}

	return 0;
}

static void toplvl_mounted P((mntfs *mf));
static void toplvl_mounted(mf)
mntfs *mf;
{
	afs_mkcacheref(mf);
}

#ifdef HAS_UNION_FS
/*
 * Create a reference to a union'ed entry
 */
static int create_union_node P((char *dir, voidp arg));
static int create_union_node(dir, arg)
char *dir;
voidp arg;
{
	if (strcmp(dir, "/defaults") != 0) {
		int error = 0;
		(void) toplvl_ops.lookuppn(arg, dir, &error, VLOOK_CREATE);
		if (error > 0) {
			errno = error; /* XXX */
			plog(XLOG_ERROR, "Could not mount %s: %m", dir);
		}
		return error;
	}
	return 0;
}

static void union_mounted P((mntfs *mf));
static void union_mounted(mf)
mntfs *mf;
{
	int i;

	afs_mkcacheref(mf);

	/*
	 * Having made the union mount point,
	 * populate all the entries...
	 */
	for (i = 0; i <= last_used_map; i++) {
		am_node *mp = exported_ap[i];
		if (mp && mp->am_mnt == mf) {
			/* return value from create_union_node is ignored by mapc_keyiter */
			(void) mapc_keyiter((mnt_map *) mp->am_mnt->mf_private,
				(void (*)P((char*,void*))) create_union_node, mp);
			break;
		}
	}

#ifdef notdef
	/*
	 * would be nice to flush most of the cache, but we need to
	 * keep the wildcard and /defaults entries...
	 */
	mapc_free(mf->mf_private);
	mf->mf_private = (voidp) mapc_find(mf->mf_info, "inc");
/*	mapc_add_kv(mf->mf_private, strdup("/defaults"),
		strdup("type:=link;opts:=nounmount;sublink:=${key}")); */
#endif
}
#endif /* HAS_UNION_FS */

/*
 * Unmount an automount sub-node
 */
static int afs_umount P((am_node *mp));
static int afs_umount(mp)
am_node *mp;
{
	return 0;
}

/*
 * Unmount a top-level automount node
 */
static int toplvl_umount P((am_node *mp));
static int toplvl_umount(mp)
am_node *mp;
{
	int error;

	struct stat stb;
again:
	/*
	 * The lstat is needed if this mount is type=direct.
	 * When that happens, the kernel cache gets confused
	 * between the underlying type (dir) and the mounted
	 * type (link) and so needs to be re-synced before
	 * the unmount.  This is all because the unmount system
	 * call follows links and so can't actually unmount
	 * a link (stupid!).  It was noted that doing an ls -ld
	 * of the mount point to see why things were not working
	 * actually fixed the problem - so simulate an ls -ld here.
	 */
	if (lstat(mp->am_path, &stb) < 0) {
#ifdef DEBUG
		dlog("lstat(%s): %m", mp->am_path);
#endif /* DEBUG */
	}
	error = UMOUNT_FS(mp->am_path);
	if (error == EBUSY) {
		plog(XLOG_WARNING, "afs_unmount retrying %s in 1s", mp->am_path);
		sleep(1);	/* XXX */
		goto again;
	}

	return error;
}

/*
 * Unmount an automount node
 */
static void afs_umounted P((am_node *mp));
static void afs_umounted(mp)
am_node *mp;
{
	/*
	 * If this is a pseudo-directory then just adjust the link count
	 * in the parent, otherwise call the generic unmount routine
	 */
	if (mp->am_parent && mp->am_parent->am_parent)
		--mp->am_parent->am_fattr.nlink;
}

/*
 * 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 succesful.
 * We rely on the RPC retry mechanism to resend the lookup request which
 * can then be handled.
 */


struct continuation {
	char **ivec;		/* Current mount info */
	am_node *mp;		/* Node we are trying to mount */
	char *key;		/* Map key */
	char *info;		/* Info string */
	char **xivec;		/* Saved strsplit vector */
	char *auto_opts;	/* Automount options */
	am_opts fs_opts;	/* Filesystem options */
	char *def_opts;		/* Default automount options */
	int retry;		/* Try again? */
	int tried;		/* Have we tried any yet? */
	time_t start;		/* Time we started this mount */
	int callout;		/* Callout identifier */
};

#define	IN_PROGRESS(cp) ((cp)->mp->am_mnt->mf_flags & MFF_MOUNTING)

/*
 * Discard an old continuation
 */
static void free_continuation P((struct continuation *cp));
static void free_continuation(cp)
struct continuation *cp;
{
	if (cp->callout)
		untimeout(cp->callout);
	free((voidp) cp->key);
	free((voidp) cp->xivec);
	free((voidp) cp->info);
	free((voidp) cp->auto_opts);
	free((voidp) cp->def_opts);
	free_opts(&cp->fs_opts);
	free((voidp) cp);
}

static int afs_bgmount P((struct continuation*, int));

/*
 * Discard the underlying mount point and replace
 * with a reference to an error filesystem.
 */
static void assign_error_mntfs P((am_node *mp));
static void assign_error_mntfs(mp)
am_node *mp;
{
	if (mp->am_error > 0) {
		/*
		 * Save the old error code
		 */
		int error = mp->am_error;
		if (error <= 0)
			error = mp->am_mnt->mf_error;
		/*
		 * Discard the old filesystem
		 */
		free_mntfs(mp->am_mnt);
		/*
		 * Allocate a new error reference
		 */
		mp->am_mnt = new_mntfs();
		/*
		 * Put back the error code
		 */
		mp->am_mnt->mf_error = error;
		mp->am_mnt->mf_flags |= MFF_ERROR;
		/*
		 * Zero the error in the mount point
		 */
		mp->am_error = 0;
	}
}

/*
 * The continuation function.  This is called by
 * the task notifier when a background mount attempt
 * completes.
 */
static void afs_cont P((int rc, int term, voidp closure));
static void afs_cont(rc, term, closure)
int rc;
int term;
voidp closure;
{
	struct continuation *cp = (struct continuation *) closure;
	mntfs *mf = cp->mp->am_mnt;

	/*
	 * Definitely not trying to mount at the moment
	 */
	mf->mf_flags &= ~MFF_MOUNTING;
	/*
	 * While we are mounting - try to avoid race conditions
	 */
	new_ttl(cp->mp);

	/*
	 * Wakeup anything waiting for this mount
	 */
	wakeup((voidp) mf);

	/*
	 * Check for termination signal or exit status...
	 */
	if (rc || term) {
		am_node *xmp;

		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", cp->mp->am_path, term);
		} else {
			/*
			 * Check for exit status...
			 */
			mf->mf_error = rc;
			mf->mf_flags |= MFF_ERROR;
			errno = rc;	/* XXX */
			plog(XLOG_ERROR, "%s: mount (afs_cont): %m", cp->mp->am_path);
		}

		/*
		 * 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->ivec++;
		xmp = cp->mp;
		(void) afs_bgmount(cp, 0);
		assign_error_mntfs(xmp);
	} else {
		/*
		 * The mount worked.
		 */
		am_mounted(cp->mp);
		free_continuation(cp);
	}

	reschedule_timeout_mp();
}

/*
 * Retry a mount
 */
/*ARGSUSED*/
static void afs_retry P((int rc, int term, voidp closure));
static void afs_retry(rc, term, closure)
int rc;
int term;
voidp closure;
{
	struct continuation *cp = (struct continuation *) closure;
	int error = 0;

#ifdef DEBUG
	dlog("Commencing retry for mount of %s", cp->mp->am_path);
#endif /* DEBUG */

	new_ttl(cp->mp);

	if ((cp->start + ALLOWED_MOUNT_TIME) < clocktime()) {
		/*
		 * The entire mount has timed out.
		 * Set the error code and skip past
		 * all the info vectors so that
		 * afs_bgmount will not have any more
		 * ways to try the mount, so causing
		 * an error.
		 */
		plog(XLOG_INFO, "mount of \"%s\" has timed out", cp->mp->am_path);
		error = ETIMEDOUT;
		while (*cp->ivec)
			cp->ivec++;
	}

	if (error || !IN_PROGRESS(cp)) {
		(void) afs_bgmount(cp, error);
	}
	reschedule_timeout_mp();
}

/*
 * Try to mount a file system.  Can be called
 * directly or in a sub-process by run_task
 */
static int try_mount P((voidp mvp));
static int try_mount(mvp)
voidp mvp;
{
	/*
	 * Mount it!
	 */
	int error;
	am_node *mp = (am_node *) mvp;
	mntfs *mf = mp->am_mnt;

	/*
	 * If the directory is not yet made and
	 * it needs to be made, then make it!
	 * This may be run in a backgroun process
	 * in which case the flag setting won't be
	 * noticed later - but it is set anyway
	 * just after run_task is called.  It
	 * should probably go away totally...
	 */
	if (!(mf->mf_flags & MFF_MKMNT) && mf->mf_ops->fs_flags & FS_MKMNT) {
		error = mkdirs(mf->mf_mount, 0555);
		if (!error)
			mf->mf_flags |= MFF_MKMNT;
	}

	error = mount_node(mp);
#ifdef DEBUG
	if (error > 0) {
		errno = error;
		dlog("afs call to mount_node failed: %m");
	}
#endif /* DEBUG */
	return error;
}

/*
 * Pick a file system to try mounting and
 * do that in the background if necessary
 *
For each location:
	if it is new -defaults then
		extract and process
		continue;
	fi
	if it is a cut then
		if a location has been tried then
			break;
		fi
		continue;
	fi
	parse mount location
	discard previous mount location if required
	find matching mounted filesystem
	if not applicable then
		this_error = No such file or directory
		continue
	fi
	if the filesystem failed to be mounted then
		this_error = error from filesystem
	elif the filesystem is mounting or unmounting then
		this_error = -1
	elif the fileserver is down then
		this_error = -1
	elif the filesystem is already mounted
		this_error = 0
		break
	fi
	if no error on this mount then
		this_error = initialise mount point
	fi
	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
		make mount point if required
	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 occured on this mount then
		update stats
		save error in mount point
	fi
endfor
 */

static int afs_bgmount P((struct continuation *cp, int mpe));
static int afs_bgmount(cp, mpe)
struct continuation *cp;
int mpe;
{
	mntfs *mf = cp->mp->am_mnt;	/* Current mntfs */
	mntfs *mf_retry = 0;		/* First mntfs which needed retrying */
	int this_error = -1;		/* Per-mount error */
	int hard_error = -1;
	int mp_error = mpe;

	/*
	 * 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 (; this_error && *cp->ivec; cp->ivec++) {
		am_ops *p;
		am_node *mp = cp->mp;
		char *link_dir;
		int dont_retry;

		if (hard_error < 0)
			hard_error = this_error;

		this_error = -1;

		if (**cp->ivec == '-') {
			/*
			 * Pick up new defaults
			 */
			if (cp->auto_opts && *cp->auto_opts)
				cp->def_opts = str3cat(cp->def_opts, cp->auto_opts, ";", *cp->ivec+1);
			else
				cp->def_opts = strealloc(cp->def_opts, *cp->ivec+1);
#ifdef DEBUG
			dlog("Setting def_opts to \"%s\"", cp->def_opts);
#endif /* DEBUG */
			continue;
		}

		/*
		 * If a mount has been attempted, and we find
		 * a cut then don't try any more locations.
		 */
		if (strcmp(*cp->ivec, "/") == 0 || strcmp(*cp->ivec, "||") == 0) {
			if (cp->tried) {
#ifdef DEBUG
				dlog("Cut: not trying any more locations for %s",
					mp->am_path);
#endif /* DEBUG */
				break;
			}
			continue;
		}

#ifdef SUNOS4_COMPAT
#ifdef nomore
		/*
		 * By default, you only get this bit on SunOS4.
		 * If you want this anyway, then define SUNOS4_COMPAT
		 * in the relevant "os-blah.h" file.
		 *
		 * We make the observation that if the local key line contains
		 * no '=' signs then either it is sick, or it is a SunOS4-style
		 * "host:fs[:link]" line.  In the latter case the am_opts field
		 * is also assumed to be in old-style, so you can't mix & match.
		 * You can use ${} expansions for the fs and link bits though...
		 *
		 * Actually, this doesn't really cover all the possibilities for
		 * the latest SunOS automounter and it is debatable whether there
		 * is any point bothering.
		 */
		if (strchr(*cp->ivec, '=') == 0)
			p = sunos4_match(&cp->fs_opts, *cp->ivec, cp->def_opts, mp->am_path, cp->key, mp->am_parent->am_mnt->mf_info);
		else
#endif
#endif /* SUNOS4_COMPAT */
			p = ops_match(&cp->fs_opts, *cp->ivec, cp->def_opts, mp->am_path, cp->key, mp->am_parent->am_mnt->mf_info);

		/*
		 * Find a mounted filesystem for this node.
		 */
		mp->am_mnt = mf = realloc_mntfs(mf, p, &cp->fs_opts, cp->fs_opts.opt_fs,
			cp->fs_opts.fs_mtab, cp->auto_opts, cp->fs_opts.opt_opts, cp->fs_opts.opt_remopts);

		p = mf->mf_ops;
#ifdef DEBUG
		dlog("Got a hit with %s", p->fs_type);
#endif /* DEBUG */
		/*
		 * Note whether this is a real mount attempt
		 */
		if (p == &efs_ops) {
			plog(XLOG_MAP, "Map entry %s for %s failed to match", *cp->ivec, mp->am_path);
			if (this_error <= 0)
				this_error = ENOENT;
			continue;
		} else {
			if (cp->fs_opts.fs_mtab) {
				plog(XLOG_MAP, "Trying mount of %s on %s fstype %s",
					cp->fs_opts.fs_mtab, mp->am_path, p->fs_type);
			}
			cp->tried = TRUE;
		}

		this_error = 0;
		dont_retry = FALSE;

		if (mp->am_link) {
			free(mp->am_link);
			mp->am_link = 0;
		}

		link_dir = mf->mf_fo->opt_sublink;

		if (link_dir && *link_dir) {
			if (*link_dir == '/') {
				mp->am_link = strdup(link_dir);
			} else {
				mp->am_link = str3cat((char *) 0,
					mf->mf_fo->opt_fs, "/", link_dir);
				normalize_slash(mp->am_link);
			}
		}

		if (mf->mf_error > 0) {
			this_error = mf->mf_error;
		} else if (mf->mf_flags & (MFF_MOUNTING|MFF_UNMOUNTING)) {
			/*
			 * Still mounting - retry later
			 */
#ifdef DEBUG
			dlog("Duplicate pending mount fstype %s", p->fs_type);
#endif /* DEBUG */
			this_error = -1;
		} else if (FSRV_ISDOWN(mf->mf_server)) {
			/*
			 * Would just mount from the same place
			 * as a hung mount - so give up
			 */
#ifdef DEBUG
			dlog("%s is already hung - giving up", mf->mf_mount);
#endif /* DEBUG */
			mp_error = EWOULDBLOCK;
			dont_retry = TRUE;
			this_error = -1;
		} else if (mf->mf_flags & MFF_MOUNTED) {
#ifdef DEBUG
			dlog("duplicate mount of \"%s\" ...", mf->mf_info);
#endif /* DEBUG */
			/*
			 * Just call mounted()
			 */
			am_mounted(mp);

			this_error = 0;
			break;
		}

		/*
		 * Will usually need to play around with the mount nodes
		 * file attribute structure.  This must be done here.
		 * Try and get things initialised, even if the fileserver
		 * is not known to be up.  In the common case this will
		 * progress things faster.
		 */
		if (!this_error) {
			/*
			 * Fill in attribute fields.
			 */
			if (mf->mf_ops->fs_flags & FS_DIRECTORY)
				mk_fattr(mp, NFDIR);
			else
				mk_fattr(mp, NFLNK);

			mp->am_fattr.fileid = mp->am_gen;

			if (p->fs_init)
				this_error = (*p->fs_init)(mf);
		}

		/*
		 * Make sure the fileserver is UP before doing any more work
		 */
		if (!FSRV_ISUP(mf->mf_server)) {
#ifdef DEBUG
			dlog("waiting for server %s to become available", mf->mf_server->fs_host);
#endif
			this_error =  -1;
		}

		if (!this_error && 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);
			if (i > 0 && clocktime() < (cp->start + i)) {
#ifdef DEBUG
				dlog("Mount of %s delayed by %ds", mf->mf_mount, i - clocktime() + cp->start);
#endif /* DEBUG */
				this_error = -1;
			}
		}

		if (this_error < 0 && !dont_retry) {
			if (!mf_retry)
				mf_retry = dup_mntfs(mf);
			cp->retry = TRUE;
		}

		if (!this_error)
		if (p->fs_flags & FS_MBACKGROUND) {
			mf->mf_flags |= MFF_MOUNTING;	/*XXX*/
#ifdef DEBUG
			dlog("backgrounding mount of \"%s\"", mf->mf_mount);
#endif /* DEBUG */
			if (cp->callout) {
				untimeout(cp->callout);
				cp->callout = 0;
			}
			run_task(try_mount, (voidp) mp, afs_cont, (voidp) cp);
			mf->mf_flags |= MFF_MKMNT;	/* XXX */
			if (mf_retry) free_mntfs(mf_retry);
			return -1;
		} else {
#ifdef DEBUG
			dlog("foreground mount of \"%s\" ...", mf->mf_info);
#endif /* DEBUG */
			this_error = try_mount((voidp) mp);
			if (this_error < 0) {
				if (!mf_retry)
					mf_retry = dup_mntfs(mf);
				cp->retry = TRUE;
			}
		}

		if (this_error >= 0) {
			if (this_error > 0) {
				amd_stats.d_merr++;
				if (mf != mf_retry) {
					mf->mf_error = this_error;
					mf->mf_flags |= MFF_ERROR;
				}
			}
			/*
			 * Wakeup anything waiting for this mount
			 */
			wakeup((voidp) mf);
		}
	}

	if (this_error && cp->retry) {
		free_mntfs(mf);
		mf = cp->mp->am_mnt = mf_retry;
		/*
		 * Not retrying again (so far)
		 */
		cp->retry = FALSE;
		cp->tried = FALSE;
		/*
		 * Start at the beginning.
		 * Rewind the location vector and
		 * reset the default options.
		 */
		cp->ivec = cp->xivec;
		cp->def_opts = strealloc(cp->def_opts, cp->auto_opts);
		/*
		 * Arrange that afs_bgmount is called
		 * after anything else happens.
		 */
#ifdef DEBUG
		dlog("Arranging to retry mount of %s", cp->mp->am_path);
#endif /* DEBUG */
		sched_task(afs_retry, (voidp) cp, (voidp) mf);
		if (cp->callout)
			untimeout(cp->callout);
		cp->callout = timeout(RETRY_INTERVAL, wakeup, (voidp) mf);

		cp->mp->am_ttl = clocktime() + RETRY_INTERVAL;

		/*
		 * Not done yet - so don't return anything
		 */
		return -1;
	}

	if (hard_error < 0 || this_error == 0)
		hard_error = this_error;

	/*
	 * Discard handle on duff filesystem.
	 * This should never happen since it
	 * should be caught by the case above.
	 */
	if (mf_retry) {
		if (hard_error)
			plog(XLOG_ERROR, "discarding a retry mntfs for %s", mf_retry->mf_mount);
		free_mntfs(mf_retry);
	}

	/*
	 * If we get here, then either the mount succeeded or
	 * there is no more mount information available.
	 */
	if (hard_error < 0 && mp_error)
		hard_error = cp->mp->am_error = mp_error;
	if (hard_error > 0) {
		/*
		 * Set a small(ish) timeout on an error node if
		 * the error was not a time out.
		 */
		switch (hard_error) {
		case ETIMEDOUT:
		case EWOULDBLOCK:
			cp->mp->am_timeo = 5;
			break;
		default:
			cp->mp->am_timeo = 17;
			break;
		}
		new_ttl(cp->mp);
	}

	/*
	 * 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;
}

/*
 * Automount interface to RPC lookup routine
 */
static am_node *afs_lookuppn P((am_node *mp, char *fname, int *error_return, int op));
static am_node *afs_lookuppn(mp, fname, error_return, op)
am_node *mp;
char *fname;
int *error_return;
int op;
{
#define ereturn(x) { *error_return = x; return 0; }

	/*
	 * Find the corresponding entry and return
	 * the file handle for it.
	 */
	am_node *ap, *new_mp, *ap_hung;
	char *info;			/* Mount info - where to get the file system */
	char **ivec, **xivec;		/* Split version of info */
	char *auto_opts;		/* Automount options */
	int error = 0;			/* Error so far */
	char path_name[MAXPATHLEN];	/* General path name buffer */
	char *pfname;			/* Path for database lookup */
	struct continuation *cp;	/* Continuation structure if we need to mount */
	int in_progress = 0;		/* # of (un)mount in progress */
	char *dflts;
	mntfs *mf;

#ifdef DEBUG
	dlog("in afs_lookuppn");
#endif /* DEBUG */

	/*
	 * If the server is shutting down
	 * then don't return information
	 * about the mount point.
	 */
	if (amd_state == Finishing) {
#ifdef DEBUG
		if ((mf = mp->am_mnt) == 0 || mf->mf_ops == &dfs_ops)
			dlog("%s mount ignored - going down", fname);
		else
			dlog("%s/%s mount ignored - going down", mp->am_path, fname);
#endif /* DEBUG */
		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) {
#ifdef DEBUG
				dlog(".. in %s gives %s", mp->am_path, mp->am_parent->am_path);
#endif /* DEBUG */
				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.
	 * fname is now a private copy.
	 */
	fname = expand_key(fname);

	for (ap_hung = 0, ap = mp->am_child; ap; ap = ap->am_osib) {
		/*
		 * Otherwise search children of this node
		 */
		if (FSTREQ(ap->am_name, fname)) {
			mf = ap->am_mnt;
			if (ap->am_error) {
				error = ap->am_error;
				continue;
			}

			/*
			 * If the error code is undefined then it must be
			 * in progress.
			 */
			if (mf->mf_error < 0)
				goto in_progrss;

			/*
			 * Check for a hung node
			 */
			if (FSRV_ISDOWN(mf->mf_server)) {
#ifdef DEBUG
				dlog("server hung");
#endif /* DEBUG */
				error = ap->am_error;
				ap_hung = ap;
				continue;
			}

			/*
			 * 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.
				 */
#ifdef DEBUG
				dlog("ignoring mount of %s in %s -- in progress",
					fname, mf->mf_mount);
#endif /* DEBUG */
				in_progress++;
				continue;
			}

			/*
			 * Otherwise we have a hit: return the current mount point.
			 */
#ifdef DEBUG
			dlog("matched %s in %s", fname, ap->am_path);
#endif /* DEBUG */
			free(fname);
			return ap;
		}
	}

	if (in_progress) {
#ifdef DEBUG
		dlog("Waiting while %d mount(s) in progress", in_progress);
#endif /* DEBUG */
		free(fname);
		ereturn(-1);
	}

	/*
	 * If an error occured then return it.
	 */
	if (error) {
#ifdef DEBUG
		errno = error; /* XXX */
		dlog("Returning error: %m", error);
#endif /* DEBUG */
		free(fname);
		ereturn(error);
	}

	/*
	 * If doing a delete then don't create again!
	 */
	switch (op) {
	case VLOOK_DELETE:
		ereturn(ENOENT);
		break;

	case VLOOK_CREATE:
		break;

	default:
		plog(XLOG_FATAL, "Unknown op to afs_lookuppn: 0x%x", op);
		ereturn(EINVAL);
		break;
	}

	/*
	 * If the server is going down then just return,
	 * don't try to mount any more file systems
	 */
	if ((int)amd_state >= (int)Finishing) {
#ifdef DEBUG
		dlog("not found - server going down anyway");
#endif /* DEBUG */
		free(fname);
		ereturn(ENOENT);
	}

	/*
	 * If we get there 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) {
		sprintf(path_name, "%s%s", mp->am_pref, fname);
		pfname = path_name;
	} else {
		pfname = fname;
	}

	mf = mp->am_mnt;

#ifdef DEBUG
	dlog("will search map info in %s to find %s", mf->mf_info, pfname);
#endif /* DEBUG */
	/*
	 * 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);
		free(fname);
		ereturn(error);
	}

#ifdef DEBUG
	dlog("mount info is %s", info);
#endif /* DEBUG */

	/*
	 * Split info into an argument vector.
	 * The vector is malloc'ed and belongs to
	 * this routine.  It is free'd in free_continuation()
	 */
	xivec = ivec = strsplit(info, ' ', '\"');

	/*
	 * Default error code...
	 */
	if (ap_hung)
		error = EWOULDBLOCK;
	else
		error = ENOENT;

	/*
	 * Allocate a new map
	 */
	new_mp = exported_ap_alloc();
	if (new_mp == 0) {
		free((voidp) xivec);
		free((voidp) info);
		free((voidp) fname);
		ereturn(ENOSPC);
	}

	if (mf->mf_auto)
		auto_opts = mf->mf_auto;
	else
		auto_opts = "";

	auto_opts = strdup(auto_opts);

#ifdef DEBUG
	dlog("searching for /defaults entry");
#endif /* DEBUG */
	if (mapc_search((mnt_map*) mf->mf_private, "/defaults", &dflts) == 0) {
	  	char *dfl;
		char **rvec;
#ifdef DEBUG
		dlog("/defaults gave %s", dflts);
#endif /* DEBUG */
		if (*dflts == '-')
			dfl = dflts+1;
		else
			dfl = dflts;

		/*
		 * Chop the defaults up
		 */
		rvec = strsplit(dfl, ' ', '\"');
		/*
		 * Extract first value
		 */
		dfl = rvec[0];

		/*
		 * If there were any values at all...
		 */
		if (dfl) {
			/*
			 * Log error if there were other values
			 */
			if (rvec[1]) {
#ifdef DEBUG
				dlog("/defaults chopped into %s", dfl);
#endif /* DEBUG */
				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 (*auto_opts && *dfl) {
				char *nopts = (char *) xmalloc(strlen(auto_opts)+strlen(dfl)+2);
				sprintf(nopts, "%s;%s", dfl, auto_opts);
				free(auto_opts);
				auto_opts = nopts;
			} else if (*dfl) {
				auto_opts = strealloc(auto_opts, dfl);
			}
		}
		free(dflts);
		/*
		 * Don't need info vector any more
		 */
		free((voidp) rvec);
	}

	/*
	 * Fill it in
	 */
	init_map(new_mp, fname);

	/*
	 * Put it in the table
	 */
	insert_am(new_mp, mp);

	/*
	 * Fill in some other fields,
	 * path and mount point.
	 *
	 * bugfix: do not prepend old am_path if direct map
	 *         <wls@astro.umd.edu> William Sebok
	 */
	new_mp->am_path = str3cat(new_mp->am_path,
		mf->mf_ops == &dfs_ops ? "" : mp->am_path,
		*fname == '/' ? "" : "/", fname);

#ifdef DEBUG
	dlog("setting path to %s", new_mp->am_path);
#endif /* DEBUG */

	/*
	 * Take private copy of pfname
	 */
	pfname = strdup(pfname);

	/*
	 * Construct a continuation
	 */
	cp = ALLOC(continuation);
	cp->mp = new_mp;
	cp->xivec = xivec;
	cp->ivec = ivec;
	cp->info = info;
	cp->key = pfname;
	cp->auto_opts = auto_opts;
	cp->retry = FALSE;
	cp->tried = FALSE;
	cp->start = clocktime();
	cp->def_opts = strdup(auto_opts);
	bzero((voidp) &cp->fs_opts, sizeof(cp->fs_opts));

	/*
	 * 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 = afs_bgmount(cp, error);
	reschedule_timeout_mp();
	if (!error) {
		free(fname);
		return new_mp;
	}

	if (error && (cp->mp->am_mnt->mf_ops == &efs_ops))
		cp->mp->am_error = error;

	assign_error_mntfs(new_mp);

	free(fname);

	ereturn(error);
#undef ereturn
}

/*
 * Locate next node in sibling list which is mounted
 * and is not an error node.
 */
static am_node *next_nonerror_node P((am_node *xp));
static am_node *next_nonerror_node(xp)
am_node *xp;
{
	mntfs *mf;

	/*
	 * Bug report (7/12/89) from Rein Tollevik <rein@ifi.uio.no>
	 * Fixes a race condition when mounting direct automounts.
	 * Also fixes a problem when doing a readdir on a directory
	 * containing hung automounts.
	 */
	while (xp &&
	       (!(mf = xp->am_mnt) ||			/* No mounted filesystem */
	        mf->mf_error != 0 ||			/* There was a mntfs error */
	        xp->am_error != 0 ||			/* There was a mount error */
	        !(mf->mf_flags & MFF_MOUNTED) ||	/* The fs is not mounted */
	        (mf->mf_server->fs_flags & FSF_DOWN))	/* The fs may be down */
		)
		xp = xp->am_osib;

	return xp;
}

static int afs_readdir P((am_node *mp, nfscookie cookie, struct dirlist *dp, struct entry *ep, int count));
static int afs_readdir(mp, cookie, dp, ep, count)
am_node *mp;
nfscookie cookie;
struct dirlist *dp;
struct entry *ep;
int count;
{
	unsigned int gen = *(unsigned int*) cookie;
	am_node *xp;

	dp->eof = FALSE;

	if (gen == 0) {
		/*
		 * In the default instance (which is used to
		 * start a search) we return "." and "..".
		 *
		 * This assumes that the count is big enough
		 * to allow both "." and ".." to be returned in
		 * a single packet.  If it isn't (which would
		 * be fairly unbelievable) then tough.
		 */
#ifdef DEBUG
		dlog("default search");
#endif /* DEBUG */
		/*
		 * Check for enough room.  This is extremely
		 * approximate but is more than enough space.
		 * Really need 2 times:
		 * 	4byte fileid
		 * 	4byte cookie
		 * 	4byte name length
		 * 	4byte name
		 * plus the dirlist structure
		 */
		if (count <
			(2 * (2 * (sizeof(*ep) + sizeof("..") + 4)
					+ sizeof(*dp))))
			return EINVAL;

		xp = next_nonerror_node(mp->am_child);
		dp->entries = ep;

		/* construct "." */
		ep[0].fileid = mp->am_gen;
		ep[0].name = ".";
		ep[0].nextentry = &ep[1];
		*(unsigned int *) ep[0].cookie = 0;

		/* construct ".." */
		if (mp->am_parent)
			ep[1].fileid = mp->am_parent->am_gen;
		else
			ep[1].fileid = mp->am_gen;
		ep[1].name = "..";
		ep[1].nextentry = 0;
		*(unsigned int *) ep[1].cookie =
			xp ? xp->am_gen : ~(unsigned int)0;

		if (!xp) dp->eof = TRUE;
		return 0;
	}

#ifdef DEBUG
	dlog("real child");
#endif /* DEBUG */

	if (gen == ~(unsigned int)0) {
#ifdef DEBUG
		dlog("End of readdir in %s", mp->am_path);
#endif /* DEBUG */
		dp->eof = TRUE;
		dp->entries = 0;
		return 0;
	}

	xp = mp->am_child;
	while (xp && xp->am_gen != gen)
		xp = xp->am_osib;

	if (xp) {
		int nbytes = count / 2;		/* conservative */
		int todo = MAX_READDIR_ENTRIES;
		dp->entries = ep;
		do {
			am_node *xp_next = next_nonerror_node(xp->am_osib);

			if (xp_next) {
				*(unsigned int *) ep->cookie = xp_next->am_gen;
			} else {
				*(unsigned int *) ep->cookie = ~(unsigned int)0;
				dp->eof = TRUE;
			}

			ep->fileid = xp->am_gen;
			ep->name = xp->am_name;
			nbytes -= sizeof(*ep) + strlen(xp->am_name) + 1;

			xp = xp_next;

			if (nbytes > 0 && !dp->eof && todo > 1) {
				ep->nextentry = ep + 1;
				ep++;
				--todo;
			} else {
				todo = 0;
			}
		} while (todo > 0);

		ep->nextentry = 0;

		return 0;
	}

	return ESTALE;

}

static am_node *dfs_readlink P((am_node *mp, int *error_return));
static am_node *dfs_readlink(mp, error_return)
am_node *mp;
int *error_return;
{
	am_node *xp;
	int rc = 0;

	xp = next_nonerror_node(mp->am_child);
	if (!xp) {
		if (!mp->am_mnt->mf_private)
			afs_mkcacheref(mp->am_mnt);	/* XXX */
		xp = afs_lookuppn(mp, mp->am_path+1, &rc, VLOOK_CREATE);
	}

	if (xp) {
		new_ttl(xp);	/* (7/12/89) from Rein Tollevik */
		return xp;
	}
	if (amd_state == Finishing)
		rc = ENOENT;
	*error_return = rc;
	return 0;
}

/*
 * Ops structure
 */
am_ops root_ops = {
	"root",
	0, /* root_match */
	0, /* root_init */
	root_mount,
	0,
	afs_umount,
	0,
	afs_lookuppn,
	afs_readdir,
	0, /* root_readlink */
	0, /* root_mounted */
	0, /* root_umounted */
	find_afs_srvr,
	FS_NOTIMEOUT|FS_AMQINFO|FS_DIRECTORY
};

am_ops afs_ops = {
	"auto",
	afs_match,
	0, /* afs_init */
	afs_mount,
	0,
	afs_umount,
	0,
	afs_lookuppn,
	afs_readdir,
	0, /* afs_readlink */
	0, /* afs_mounted */
	afs_umounted,
	find_afs_srvr,
	FS_AMQINFO|FS_DIRECTORY
};

am_ops toplvl_ops = {
	"toplvl",
	afs_match,
	0, /* afs_init */
	toplvl_mount,
	0,
	toplvl_umount,
	0,
	afs_lookuppn,
	afs_readdir,
	0, /* toplvl_readlink */
	toplvl_mounted,
	0, /* toplvl_umounted */
	find_afs_srvr,
	FS_MKMNT|FS_NOTIMEOUT|FS_BACKGROUND|FS_AMQINFO|FS_DIRECTORY
};

am_ops dfs_ops = {
	"direct",
	afs_match,
	0, /* dfs_init */
	toplvl_mount,
	0,
	toplvl_umount,
	0,
	efs_lookuppn,
	efs_readdir,
	dfs_readlink,
	toplvl_mounted,
	0, /* afs_umounted */
	find_afs_srvr,
	FS_MKMNT|FS_NOTIMEOUT|FS_BACKGROUND|FS_AMQINFO
};

#ifdef HAS_UNION_FS
am_ops union_ops = {
	"union",
	afs_match,
	0, /* afs_init */
	toplvl_mount,
	0,
	toplvl_umount,
	0,
	afs_lookuppn,
	afs_readdir,
	0, /* toplvl_readlink */
	union_mounted,
	0, /* toplvl_umounted */
	find_afs_srvr,
	FS_MKMNT|FS_NOTIMEOUT|FS_BACKGROUND|FS_AMQINFO|FS_DIRECTORY
};
#endif /* HAS_UNION_FS */
