auto_vfsops.c   [plain text]


/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Portions Copyright 2007 Apple Inc.
 *
 * $Id$
 */

#pragma ident	"@(#)auto_vfsops.c	1.58	06/03/24 SMI"

#include <sys/param.h>
#include <sys/queue.h>
#include <sys/systm.h>
#include <sys/namei.h>
#include <sys/filedesc.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <mach/machine/vm_types.h>
#include <sys/vnode.h>
#include <sys/vfs_context.h>
#include <sys/socket.h>
#include <sys/mount.h>
#include <sys/errno.h>
#include <sys/malloc.h>
#include <sys/attr.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <sys/conf.h>
#include <miscfs/devfs/devfs.h>
#include <kern/locks.h>
#include <kern/assert.h>

#include "autofs.h"
#include "autofs_kern.h"

lck_grp_t *autofs_lck_grp;
static lck_mtx_t *autofs_global_lock;
lck_mtx_t *autofs_nodeid_lock;

static u_int autofs_mounts;

/*
 * autofs VFS operations
 */
static int auto_mount(mount_t, vnode_t, user_addr_t, vfs_context_t);
static int auto_start(mount_t, int, vfs_context_t);
static int auto_unmount(mount_t, int, vfs_context_t);
static int auto_root(mount_t, vnode_t *, vfs_context_t);
static int auto_vfs_getattr(mount_t, struct vfs_attr *, vfs_context_t);
static int auto_sync(mount_t, int, vfs_context_t);
static int auto_vget(mount_t, ino64_t, vnode_t *, vfs_context_t);
static int autofs_sysctl(int *, u_int, user_addr_t, size_t *, user_addr_t, size_t, vfs_context_t);

static struct vfsops autofs_vfsops = {
	auto_mount,
	auto_start,
	auto_unmount,
	auto_root,
	NULL,			/* auto_quotactl */
	auto_vfs_getattr, 	/* was auto_statfs */
	auto_sync,
	auto_vget,
	NULL,			/* auto_fhtovp */
	NULL,			/* auto_vnodetofh */
	NULL,			/* auto_init */
	autofs_sysctl
};

extern struct vnodeopv_desc autofsfs_vnodeop_opv_desc;
struct vnodeopv_desc * autofs_vnodeop_opv_descs[] =
	{&autofsfs_vnodeop_opv_desc, NULL};

struct vfs_fsentry autofs_fsentry = {
	&autofs_vfsops,			/* vfs operations */
	1,				/* # of vnodeopv_desc being registered (reg, spec, fifo ...) */
	autofs_vnodeop_opv_descs,	/* null terminated;  */
	0,				/* historic filesystem type number [ unused w. VFS_TBLNOTYPENUM specified ] */
	MNTTYPE_AUTOFS,			/* filesystem type name */
	VFS_TBLTHREADSAFE | VFS_TBLNOTYPENUM | VFS_TBL64BITREADY,	/* defines the FS capabilities */
	{ NULL, NULL }			/* reserved for future use; set this to zero*/
};

static vfstable_t  auto_vfsconf;

/*
 * No zones in OS X, so only one autofs_globals structure.
 */
static struct autofs_globals *fngp;

/*
 * rootfnnodep is allocated here.  Its sole purpose is to provide
 * read/write locking for top level fnnodes.  This object is
 * persistent and will not be deallocated until the module is unloaded.
 * XXX - is there some compelling reason for it to exist?  Where is
 * it used?
 *
 * There are no zones in OS X, so this is system-wide.
 */
static struct autofs_globals *
autofs_zone_init(void)
{
	struct autofs_globals *fngp = NULL;
	fnnode_t *fnp = NULL;
	static const char fnnode_name[] = "root_fnnode";
	struct timeval now;
	kern_return_t ret;

	MALLOC(fngp, struct autofs_globals *, sizeof (*fngp), M_AUTOFS,
	    M_WAITOK);
	if (fngp == NULL) {
		log(LOG_ERR, "autofs_zone_init: Couldn't create autofs globals structure\n");
		goto fail;
	}
	bzero(fngp, sizeof (*fngp));
	/*
	 * Allocate a "dummy" fnnode, not associated with any vnode.
	 */
	MALLOC(fnp, fnnode_t *, sizeof(fnnode_t), M_AUTOFS, M_WAITOK);
	if (fnp == NULL) {
		log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global fnnode\n");
		goto fail;
	}
	bzero(fnp, sizeof(*fnp));
	fnp->fn_namelen = sizeof fnnode_name;
	MALLOC(fnp->fn_name, char *, fnp->fn_namelen + 1, M_AUTOFS, M_WAITOK);
	if (fnp->fn_name == NULL) {
		log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global fnnode name\n");
		goto fail;
	}
	bcopy(fnnode_name, fnp->fn_name, sizeof fnnode_name);
	fnp->fn_name[sizeof fnnode_name] = '\0';
	fnp->fn_mode = AUTOFS_MODE;
	microtime(&now);
	fnp->fn_crtime = fnp->fn_atime = fnp->fn_mtime = fnp->fn_ctime = now;
	fnp->fn_ref_time = now.tv_sec;
	fnp->fn_nodeid = 1;	/* XXX - could just be zero? */
	fnp->fn_globals = fngp;
	fnp->fn_lock = lck_mtx_alloc_init(autofs_lck_grp, NULL);
	if (fnp->fn_lock == NULL) {
		log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global fnnode mutex\n");
		goto fail;
	}
	fnp->fn_rwlock = lck_rw_alloc_init(autofs_lck_grp, NULL);
	if (fnp->fn_rwlock == NULL) {
		log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global fnnode rwlock\n");
		goto fail;
	}

	fngp->fng_rootfnnodep = fnp;
	fngp->fng_fnnode_count = 1;
	fngp->fng_printed_not_running_msg = 0;
	fngp->fng_unmount_threads_lock = lck_mtx_alloc_init(autofs_lck_grp,
	    LCK_ATTR_NULL);
	if (fngp->fng_unmount_threads_lock == NULL) {
		log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global unmount threads lock\n");
		goto fail;
	}
	fngp->fng_unmount_threads = 0;
	fngp->fng_terminate_do_unmount_thread = 0;
	fngp->fng_do_unmount_thread_terminated = 0;
	fngp->fng_flush_notification_lock = lck_mtx_alloc_init(autofs_lck_grp,
	    LCK_ATTR_NULL);
	if (fngp->fng_flush_notification_lock == NULL) {
		log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global flush notification lock\n");
		goto fail;
	}

	/*
	 * Start the unmounter thread.
	 * XXX - priority?  minclsyspri?
	 */
	ret = auto_new_thread(auto_do_unmount, fngp);
	if (ret != KERN_SUCCESS) {
		log(LOG_ERR, "autofs_zone_init: Couldn't create unmounter thread, status 0x%08x", ret);
		goto fail;
	}
	return (fngp);

fail:
	if (fnp != NULL) {
		if (fnp->fn_rwlock != NULL)
			lck_rw_free(fnp->fn_rwlock, autofs_lck_grp);
		if (fnp->fn_lock != NULL)
			lck_mtx_free(fnp->fn_lock, autofs_lck_grp);
		if (fnp->fn_name != NULL)
			FREE(fnp->fn_name, M_AUTOFS);
		FREE(fnp, M_AUTOFS);
	}
	if (fngp != NULL) {
		if (fngp->fng_flush_notification_lock != NULL)
			lck_mtx_free(fngp->fng_flush_notification_lock,
			    autofs_lck_grp);
		if (fngp->fng_unmount_threads_lock != NULL)
			lck_mtx_free(fngp->fng_unmount_threads_lock,
			    autofs_lck_grp);
		FREE(fngp, M_AUTOFS);
	}
	return (NULL);
}

/*
 * Find the first occurrence of find in s.
 */
static char *
strstr(const char *s, const char *find)
{
	char c, sc;
	size_t len;

	if ((c = *find++) != 0) {
		len = strlen(find);
		do {
			do {
				if ((sc = *s++) == 0)
					return (NULL);
			} while (sc != c);
		} while (strncmp(s, find, len) != 0);
		s--;
	}
	return ((char *)s);
}

/*
 * A "struct mntopt" table is terminated with an entry with a null
 * m_option pointer; therefore, the number of real entries in the
 * table is one fewer than the total number of entries.
 */
static const struct mntopt restropts[] = {
	RESTRICTED_MNTOPTS
};
#define	NROPTS	((sizeof (restropts)/sizeof (restropts[0])) - 1)

/*
 * Check whether the specified option is set in the specified file
 * system.
 */
static u_int32_t
optionisset(mount_t mp, const struct mntopt *opt)
{
	u_int32_t flags;
	
	flags = vfs_flags(mp) & opt->m_flag;
	if (opt->m_inverse)
		return !flags;
	else
		return flags;
}

/*
 * This routine adds those options to the option string `buf' which are
 * forced by secpolicy_fs_mount.  If the automatic "security" options
 * are set, the option string gets them added if they aren't already
 * there.  We search the string with "strstr" and make sure that
 * the string we find is bracketed with <start|",">MNTOPT<","|"\0">
 *
 * This is one half of the option inheritence algorithm which
 * implements the "restrict" option.  The other half is implemented
 * in automountd; it takes its cue from the options we add here.
 */
static int
autofs_restrict_opts(mount_t mp, char *buf, size_t maxlen, size_t *curlen)
{
	fninfo_t *fnip = vfstofni(mp);
	int i;
	char *p;
	size_t len = *curlen - 1;

	/* Unrestricted */
	if (!(fnip->fi_mntflags & AUTOFS_MNT_RESTRICT))
		return (0);

	for (i = 0; i < NROPTS; i++) {
		size_t olen = strlen(restropts[i].m_option);

		/* Add "restrict" always and the others insofar set */
		if ((i == 0 || optionisset(mp, &restropts[i])) &&
		    ((p = strstr(buf, restropts[i].m_option)) == NULL ||
		    !((p == buf || p[-1] == ',') &&
		    (p[olen] == '\0' || p[olen] == ',')))) {

			if (len + olen + 1 > maxlen)
				return (-1);

			if (*buf != '\0')
				buf[len++] = ',';
			bcopy(restropts[i].m_option, &buf[len], olen);
			len += olen;
		}
	}
	if (len + 1 > maxlen)
		return (-1);
	buf[len++] = '\0';
	*curlen = len;
	return (0);
}

/* ARGSUSED */
static int
auto_mount(mount_t mp, vnode_t devvp, user_addr_t data, vfs_context_t context)
{
	int error;
	size_t len;
	int argsvers;
	struct autofs_args_64 args;
	fninfo_t *fnip = NULL;
	vnode_t rootvp = NULL;
	fnnode_t *rootfnp = NULL;
	char strbuff[PATH_MAX+1];
	uint64_t flags;
#ifdef DEBUG
	lck_attr_t *lckattr;
#endif

	AUTOFS_DPRINT((4, "auto_mount: mp %p devvp %p\n", mp, devvp));

	lck_mtx_lock(autofs_global_lock);
	if (fngp == NULL)
		fngp = autofs_zone_init();
	lck_mtx_unlock(autofs_global_lock);
	if (fngp == NULL)
		return (ENOMEM);

	/*
	 * Get argument version
	 */
	error = copyin(data, (caddr_t)&argsvers, sizeof (argsvers));
	if (error)
		return (error);

	/*
	 * Get arguments
	 */
	switch (argsvers) {

	case 1:
		if (vfs_context_is64bit(context))
			error = copyin(data, &args, sizeof (args));
		else {
			struct autofs_args args32;

			error = copyin(data, &args32, sizeof (args32));
			if (error == 0) {
				args.path = CAST_USER_ADDR_T(args32.path);
				args.opts = CAST_USER_ADDR_T(args32.opts);
				args.map = CAST_USER_ADDR_T(args32.map);
				args.subdir = CAST_USER_ADDR_T(args32.subdir);
				args.key = CAST_USER_ADDR_T(args32.key);
				args.mntflags = args32.mntflags;
				args.mount_to = args32.mount_to;
				args.mach_to = args32.mach_to;
				args.direct = args32.direct;
				args.trigger = args32.trigger;
			}
		}
		if (error)
			return (error);
		break;

	default:
		return (EINVAL);
	}

	/*
	 * We don't support remounts; the AUTOFS_UPDATE_OPTIONS ioctl
	 * should be used for that.
	 */
	flags = vfs_flags(mp) & MNT_CMDFLAGS;
	if (flags & MNT_UPDATE)
		return (EINVAL);

	/*
	 * Allocate fninfo struct and attach it to vfs
	 */
	MALLOC(fnip, fninfo_t *, sizeof(*fnip), M_AUTOFS, M_WAITOK);
	if (fnip == NULL)
		return (ENOMEM);
	bzero(fnip, sizeof(*fnip));
	fnip->fi_flags |= MF_DONTTRIGGER;	/* mount isn't finished yet */

	fnip->fi_mount_to = args.mount_to;
	fnip->fi_mach_to = args.mach_to;

	/*
	 * Assign a unique fsid to the mount
	 */
	vfs_getnewfsid(mp);
	vfs_setfsprivate(mp, fnip);

	/*
	 * Get path for mountpoint
	 */
	error = copyinstr(args.path, strbuff, sizeof (strbuff), &len);
	if (error) {
		error = EFAULT;
		goto errout;
	}
	MALLOC(fnip->fi_path, char *, len, M_AUTOFS, M_WAITOK);
	if (fnip->fi_path == NULL) {
		error = ENOMEM;
		goto errout;
	}
	fnip->fi_pathlen = (int)len;
	bcopy(strbuff, fnip->fi_path, len);

	/*
	 * Get default options
	 */
	error = copyinstr(args.opts, strbuff, sizeof (strbuff), &len);

	if (error != 0)
		goto errout;
		
	if (autofs_restrict_opts(mp, strbuff, sizeof (strbuff), &len) != 0) {
		error = EFAULT;
		goto errout;
	}
	MALLOC(fnip->fi_opts, char *, len, M_AUTOFS, M_WAITOK);
	if (fnip->fi_opts == NULL) {
		error = ENOMEM;
		goto errout;
	}
	fnip->fi_optslen = (int)len;
	bcopy(strbuff, fnip->fi_opts, len);

	/*
	 * Get context/map name
	 */
	error = copyinstr(args.map, strbuff, sizeof (strbuff), &len);
	if (error)
		goto errout;
	MALLOC(fnip->fi_map, char *, len, M_AUTOFS, M_WAITOK);
	if (fnip->fi_map == NULL) {
		error = ENOMEM;
		goto errout;
	}
	fnip->fi_maplen = (int)len;
	bcopy(strbuff, fnip->fi_map, len);

	if (args.trigger) {
		/*
		 * Mark this as a trigger, both to let users know
		 * that it's a trigger and to let the automount
		 * command know that it's a trigger.
		 */
		strncpy(vfs_statfs(mp)->f_mntfromname, "trigger",
		    sizeof(vfs_statfs(mp)->f_mntfromname)-1);
		vfs_statfs(mp)->f_mntfromname[sizeof(vfs_statfs(mp)->f_mntfromname)-1] = (char)0;
	} else {
		snprintf(vfs_statfs(mp)->f_mntfromname, sizeof(vfs_statfs(mp)->f_mntfromname),
		    "map %.*s", fnip->fi_maplen, fnip->fi_map);
	}

	/*
	 * Get subdirectory within map
	 */
	error = copyinstr(args.subdir, strbuff, sizeof (strbuff), &len);
	if (error)
		goto errout;
	MALLOC(fnip->fi_subdir, char *, len, M_AUTOFS, M_WAITOK);
	if (fnip->fi_subdir == NULL) {
		error = ENOMEM;
		goto errout;
	}
	fnip->fi_subdirlen = (int)len;
	bcopy(strbuff, fnip->fi_subdir, len);

	/*
	 * Get the key
	 */
	error = copyinstr(args.key, strbuff, sizeof (strbuff), &len);
	if (error)
		goto errout;
	MALLOC(fnip->fi_key, char *, len, M_AUTOFS, M_WAITOK);
	if (fnip->fi_key == NULL) {
		error = ENOMEM;
		goto errout;
	}
	fnip->fi_keylen = (int)len;
	bcopy(strbuff, fnip->fi_key, len);

	fnip->fi_mntflags = args.mntflags;

	/*
	 * Is this a direct mount?
	 */
	if (args.direct == 1)
		fnip->fi_flags |= MF_DIRECT;

	/*
	 * Get an rwlock.
	 */
#ifdef DEBUG
	/*
	 * Enable debugging on the lock.
	 */
	lckattr = lck_attr_alloc_init();
	lck_attr_setdebug(lckattr);
	fnip->fi_rwlock = lck_rw_alloc_init(autofs_lck_grp, lckattr);
	lck_attr_free(lckattr);
#else
	fnip->fi_rwlock = lck_rw_alloc_init(autofs_lck_grp, NULL);
#endif

	/*
	 * Make the root vnode
	 */
	error = auto_makefnnode(&rootfnp, VDIR, mp, NULL, fnip->fi_path,
	    NULL, 1, vfs_context_ucred(context), fngp);
	if (error)
		goto errout;
	rootvp = fntovn(rootfnp);

	rootfnp->fn_mode = AUTOFS_MODE;
	rootfnp->fn_parent = rootfnp;
	/* account for ".." entry (fn_parent) */
	rootfnp->fn_linkcnt = 1;
	error = vnode_ref(rootvp);	/* released in auto_unmount */
	if (error) {
		vnode_put(rootvp);
		vnode_recycle(rootvp);
		goto errout;
	}
	fnip->fi_rootvp = rootvp;

	/*
	 * Add to list of top level AUTOFS's if this isn't a trigger
	 * mount.
	 */
	if (!args.trigger) {
		lck_rw_lock_exclusive(fngp->fng_rootfnnodep->fn_rwlock);
		rootfnp->fn_parent = fngp->fng_rootfnnodep;
		rootfnp->fn_next = fngp->fng_rootfnnodep->fn_dirents;
		fngp->fng_rootfnnodep->fn_dirents = rootfnp;
		lck_rw_unlock_exclusive(fngp->fng_rootfnnodep->fn_rwlock);
	}

	/*
	 * One more mounted file system.
	 */
	lck_mtx_lock(autofs_global_lock);
	autofs_mounts++;
	lck_mtx_unlock(autofs_global_lock);

	vnode_put(rootvp);

	AUTOFS_DPRINT((5, "auto_mount: mp %p root %p fnip %p return %d\n",
	    mp, rootvp, fnip, error));

	return (0);

errout:
	assert(fnip != NULL);

	if (fnip->fi_rwlock != NULL)
		lck_rw_free(fnip->fi_rwlock, autofs_lck_grp);
	if (fnip->fi_path != NULL)
		FREE(fnip->fi_path, M_AUTOFS);
	if (fnip->fi_opts != NULL)
		FREE(fnip->fi_opts, M_AUTOFS);
	if (fnip->fi_map != NULL)
		FREE(fnip->fi_map, M_AUTOFS);
	if (fnip->fi_subdir != NULL)
		FREE(fnip->fi_subdir, M_AUTOFS);
	if (fnip->fi_key != NULL)
		FREE(fnip->fi_key, M_AUTOFS);
	FREE(fnip, sizeof M_AUTOFS);

	AUTOFS_DPRINT((5, "auto_mount: vfs %p root %p fnip %p return %d\n",
	    (void *)mp, (void *)rootvp, (void *)fnip, error));

	return (error);
}

static int
auto_update_options(struct autofs_update_args_64 *update_argsp)
{
	mount_t mp;
	fninfo_t *fnip;
	fnnode_t *fnp;
	char strbuff[PATH_MAX+1];
	int error;
	char *opts, *map;
	size_t optslen, maplen;

	/*
	 * Update the mount options on this autofs node.
	 * XXX - how do we make sure mp isn't freed out from under
	 * us?
	 */
	mp = vfs_getvfs(&update_argsp->fsid);
	if (mp == NULL)
		return (ENOENT);	/* no such mount */

	/*
	 * Make sure this is an autofs mount.
	 */
	if (!auto_is_autofs(mp))
		return (EINVAL);

	fnip = vfstofni(mp);
	if (fnip == NULL)
		return (EINVAL);

	/*
	 * We can't change the map type if the top-level directory has
	 * subdirectories (i.e., if stuff has happened under it).
	 */
	if ((update_argsp->direct == 1 && !(fnip->fi_flags & MF_DIRECT)) ||
	    (update_argsp->direct != 1 && (fnip->fi_flags & MF_DIRECT))) {
		fnp = vntofn(fnip->fi_rootvp);
		if (fnp->fn_dirents != NULL)
			return (EINVAL);
	}

	/*
	 * Get default options
	 */
	error = copyinstr(update_argsp->opts, strbuff, sizeof (strbuff),
	    &optslen);
	if (error)
		return (error);

	if (autofs_restrict_opts(mp, strbuff, sizeof (strbuff), &optslen) != 0)
		return (EFAULT);

	MALLOC(opts, char *, optslen, M_AUTOFS, M_WAITOK);
	bcopy(strbuff, opts, optslen);

	/*
	 * Get context/map name
	 */
	error = copyinstr(update_argsp->map, strbuff, sizeof (strbuff),
	    &maplen);
	if (error) {
		FREE(opts, M_AUTOFS);
		return (error);
	}
	MALLOC(map, char *, maplen, M_AUTOFS, M_WAITOK);
	bcopy(strbuff, map, maplen);

	/*
	 * We've fetched all the strings; lock out any other references
	 * to fnip, and update the mount information.
	 */
	lck_rw_lock_exclusive(fnip->fi_rwlock);
	FREE(fnip->fi_opts, M_AUTOFS);
	fnip->fi_opts = opts;
	fnip->fi_optslen = (int)optslen;
	FREE(fnip->fi_map, M_AUTOFS);
	fnip->fi_map = map;
	fnip->fi_maplen = (int)maplen;
	if (update_argsp->direct == 1)
		fnip->fi_flags |= MF_DIRECT;
	else
		fnip->fi_flags &= ~MF_DIRECT;
	fnip->fi_flags &= ~MF_DONTTRIGGER;
	fnip->fi_mntflags = update_argsp->mntflags;
	fnip->fi_mount_to = update_argsp->mount_to;
	fnip->fi_mach_to = update_argsp->mach_to;

	snprintf(vfs_statfs(mp)->f_mntfromname, sizeof(vfs_statfs(mp)->f_mntfromname),
	    "map %.*s", fnip->fi_maplen, fnip->fi_map);
	lck_rw_unlock_exclusive(fnip->fi_rwlock);

	/*
	 * Notify anybody looking at this mount's root directory
	 * that it might have changed; this remount might have
	 * been done as the result of a network change, so the
	 * map backing it might have changed.
	 */
	AUTO_KNOTE(fnip->fi_rootvp, NOTE_WRITE);
	return (0);
}

/*
 * This is called during a mount when the file system is "ready".
 * We refuse to trigger in auto_root() until that has happened.
 * Otherwise, we are called when the mount system call has a writer
 * lock on mp, which means no lookups can proceed past mp - including
 * the lookup necessary to mount on top of the trigger point!  That
 * means that if a mount is triggered in the process of mounting an
 * autofs file system, and the autofs mount can't complete until the
 * triggered mount completes, we deadlock, as the triggered mount
 * can't complete until the writer lock is released, and that doesn't
 * happen until the autofs mount completes.
 */
int
auto_start(mount_t mp, int flags, vfs_context_t context)
{
	fninfo_t *fnip;

	fnip = vfstofni(mp);
	lck_rw_lock_exclusive(fnip->fi_rwlock);
	fnip->fi_flags &= ~MF_DONTTRIGGER;
	lck_rw_unlock_exclusive(fnip->fi_rwlock);
	return (0);
}

/* ARGSUSED */
static int
auto_unmount(mount_t mp, int mntflags, vfs_context_t context)
{
	fninfo_t *fnip;
	vnode_t rvp;
	fnnode_t *rfnp, *fnp, *pfnp;
	fnnode_t *myrootfnnodep;
	int error;

	fnip = vfstofni(mp);
	AUTOFS_DPRINT((4, "auto_unmount mp %p fnip %p\n", (void *)mp,
			(void *)fnip));

	/*
	 * forced unmount is not supported by this file system
	 * and thus, ENOTSUP, is being returned.
	 *
	 * XXX - forced unmounts are done at system shutdown time;
	 * do we need to make them work?
	 */
	if (mntflags & MNT_FORCE)
		return (ENOTSUP);

#if 0
	assert(vn_vfswlock_held(mp->mnt_vnodecovered));
#endif
	rvp = fnip->fi_rootvp;
	rfnp = vntofn(rvp);

	error = vflush(mp, rvp, 0);
	if (error)
		return (error);

	if (vnode_isinuse(rvp, 1) || rfnp->fn_dirents != NULL)
		return (EBUSY);

	/*
	 * The root vnode is on the linked list of root fnnodes only if
	 * this was not a trigger node. Since we have no way of knowing,
	 * if we don't find it, then we assume it was a trigger node.
	 */
	myrootfnnodep = rfnp->fn_globals->fng_rootfnnodep;
	pfnp = NULL;
	lck_rw_lock_exclusive(myrootfnnodep->fn_rwlock);
	fnp = myrootfnnodep->fn_dirents;
	while (fnp != NULL) {
		if (fnp == rfnp) {
			/*
			 * A check here is made to see if rvp is busy.  If
			 * so, return EBUSY.  Otherwise proceed with
			 * disconnecting it from the list.
			 */
			if (vnode_isinuse(rvp, 1) || rfnp->fn_dirents != NULL) {
				lck_rw_unlock_exclusive(myrootfnnodep->fn_rwlock);
				return (EBUSY);
			}
			if (pfnp)
				pfnp->fn_next = fnp->fn_next;
			else
				myrootfnnodep->fn_dirents = fnp->fn_next;
			fnp->fn_next = NULL;
			break;
		}
		pfnp = fnp;
		fnp = fnp->fn_next;
	}
	lck_rw_unlock_exclusive(myrootfnnodep->fn_rwlock);

	assert(vnode_isinuse(rvp, 0) && !vnode_isinuse(rvp, 1));
	assert(rfnp->fn_direntcnt == 0);
	assert(rfnp->fn_linkcnt == 1);
	/*
	 * The following drops linkcnt to 0, therefore the disconnect is
	 * not attempted when auto_inactive() is called by
	 * vn_rele(). This is necessary because we have nothing to get
	 * disconnected from since we're the root of the filesystem. As a
	 * side effect the node is not freed, therefore I should free the
	 * node here.
	 *
	 * XXX - I really need to think of a better way of doing this.
	 * XXX - this is not Solaris, so maybe there is a better way.
	 */
	rfnp->fn_linkcnt--;

	/*
	 * release last reference to the root vnode
	 */
	vnode_rele(rvp);

	/*
	 * Wait for in-flight operations to complete on any remaining vnodes
	 * (such as the root vnode, which we just released).
	 *
	 * The root vnode will be recycled in vflush() when there are
	 * no longer any in-flight operations.
	 */
	vflush(mp, NULLVP, 0);

	lck_rw_free(fnip->fi_rwlock, autofs_lck_grp);
	FREE(fnip->fi_path, M_AUTOFS);
	FREE(fnip->fi_map, M_AUTOFS);
	FREE(fnip->fi_subdir, M_AUTOFS);
	FREE(fnip->fi_key, M_AUTOFS);
	FREE(fnip->fi_opts, M_AUTOFS);
	FREE(fnip, M_AUTOFS);
	
	/*
	 * One fewer mounted file system.
	 */
	lck_mtx_lock(autofs_global_lock);
	autofs_mounts--;
	lck_mtx_unlock(autofs_global_lock);

	AUTOFS_DPRINT((5, "auto_unmount: return=0\n"));

	return (0);
}

extern int thread_notrigger(void);

/*
 * find root of autofs
 */
static int
auto_root(mount_t mp, vnode_t *vpp, vfs_context_t context)
{
	int error;
	fninfo_t *fnip;
	fnnode_t *fnp;
	struct timeval now;

	fnip = vfstofni(mp);
	*vpp = fnip->fi_rootvp;
	error = vnode_get(*vpp);
	if (error)
		goto done;

	/*
	 * If this is a direct map, and the mount of this file system
	 * (not the mount *on* it, the mount *of* it; see auto_start()
	 * for an explanation) is complete and we're not trying to unmount
	 * it, and we should trigger a mount for this vnode, do so, so that
	 * our caller gets a vnode with something mounted on it if the mount
	 * can be done.
	 */
	auto_fninfo_lock_shared(fnip, context);
	if ((fnip->fi_flags & (MF_DIRECT|MF_DONTTRIGGER)) == MF_DIRECT &&
	    !thread_notrigger()) {
		fnp = vntofn(*vpp);
	retry:
		lck_mtx_lock(fnp->fn_lock);
		if (auto_dont_trigger(*vpp, context)) {
			/*
			 * Don't trigger the mount.
			 */
			lck_mtx_unlock(fnp->fn_lock);
			error = 0;
			goto unlock;
		}
		if (fnp->fn_flags & (MF_LOOKUP | MF_INPROG)) {
			/*
			 * Mount or lookup in progress,
			 * wait for it before proceeding.
			 */
			lck_mtx_unlock(fnp->fn_lock);
			error = auto_wait4mount(fnp, context);
			if (error && error != EAGAIN) {
				vnode_put(*vpp);
				goto unlock;
			}
			error = 0;
			/*
			 * Check again whether we should trigger the
			 * mount - an in-progress mount might have
			 * succeeded.
			 */
			goto retry;
		}

		if ((fnp->fn_flags & MF_MOUNTPOINT) &&
		    fnp->fn_trigger != NULL) {
			assert(fnp->fn_dirents == NULL);
			/*
			 * The filesystem that used to sit here
			 * has been forcibly unmounted.
			 */
			lck_mtx_unlock(fnp->fn_lock);
			error = EIO;
			vnode_put(*vpp);
			goto unlock;
		}

		if (fnp->fn_dirents == NULL) {
			/*
			 * Trigger mount, because this is a direct
			 * mountpoint with no subdirs.
			 */
			AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG);
			fnp->fn_error = 0;
			lck_mtx_unlock(fnp->fn_lock);
			microtime(&now);
			fnp->fn_ref_time = now.tv_sec;
			auto_new_mount_thread(fnp, ".", 1, context);
			/*
			 * At this point we're simply another thread
			 * waiting for the mount to finish.
			 */
			error = auto_wait4mount(fnp, context);
			if (error) {
				if (error == EAGAIN)
					goto retry;
				vnode_put(*vpp);
				goto unlock;
			}
		} else {
			/*
			 * Nothing to trigger, just unlock.
			 */
			lck_mtx_unlock(fnp->fn_lock);
			error = 0;
		}
	}
unlock:
	auto_fninfo_unlock_shared(fnip, context);

done:
	AUTOFS_DPRINT((5, "auto_root: mp %p, *vpp %p, error %d\n",
	    mp, *vpp, error));
	return (error);
}

/*
 * Get file system statistics.
 */
static int
auto_vfs_getattr(mount_t mp, struct vfs_attr *vfap, vfs_context_t context)
{
	AUTOFS_DPRINT((4, "auto_vfs_getattr %p\n", (void *)mp));

	/* XXX - return capabilities and attributes? */
	VFSATTR_RETURN(vfap, f_bsize, AUTOFS_BLOCKSIZE);
	VFSATTR_RETURN(vfap, f_iosize, 512);
	VFSATTR_RETURN(vfap, f_blocks, 0);
	VFSATTR_RETURN(vfap, f_bfree, 0);
	VFSATTR_RETURN(vfap, f_bavail, 0);
	VFSATTR_RETURN(vfap, f_files, 0);
	VFSATTR_RETURN(vfap, f_ffree, 0);

	return (0);
}

/*
 * autofs doesn't have any data or backing store and you can't write into
 * any of the autofs structures, so don't do anything.
 */
static int
auto_sync(mount_t mp, int waitfor, vfs_context_t context)
{
	return (0);
}

/*
 * Look up a autofs node by node number.
 * Currently not supported.
 */
static int
auto_vget(mount_t mp, ino64_t ino, vnode_t *vpp, vfs_context_t context)
{
	return (ENOTSUP);
}

static int
autofs_sysctl(int *name, u_int namelen, user_addr_t oldp, size_t *oldlenp, user_addr_t newp, size_t newlen, vfs_context_t context)
{
	int error;
#ifdef DEBUG
	struct sysctl_req *req = NULL;
	uint32_t debug;
#endif
	
	/*
	 * All names at this level are terminal
	 */
	if (namelen > 1)
		return ENOTDIR;		/* overloaded error code */

	error = 0;

#ifdef DEBUG
	req = CAST_DOWN(struct sysctl_req *, oldp);
#endif

	AUTOFS_DPRINT((4, "autofs_sysctl called.\n"));
	switch (name[0]) {
#ifdef DEBUG
	case AUTOFS_CTL_DEBUG:
		error = SYSCTL_IN(req, &debug, sizeof(debug));
		if (error)
			break;
		auto_debug_set(debug);
		break;
#endif
	default:
		error = ENOTSUP;
		break;
	}

	return (error);
}

/*
 * Opening /dev/autofs when nobody else has it open makes you the
 * automounter, so you and all your children never trigger mounts and
 * never get stuck waiting for a mount to complete (as it's your job
 * to complete the mount.
 *
 * Opening /dev/autofs when somebody else has it open fails with EBUSY.
 *
 * Closing /dev/autofs clears automounter_pid, so nobody's the automounter;
 * that means if the automounter exits, it ceases to be the automounter.
 */
static d_open_t	 autofs_dev_open;
static d_close_t autofs_dev_close;
static d_ioctl_t autofs_ioctl;

static struct cdevsw autofs_cdevsw = {
	autofs_dev_open,
	autofs_dev_close,
	eno_rdwrt,	/* d_read */
	eno_rdwrt,	/* d_write */
	autofs_ioctl,
	eno_stop,
	eno_reset,
	0,		/* struct tty ** d_ttys */
	eno_select,
	eno_mmap,
	eno_strat,
	eno_getc,
	eno_putc,
	0		/* d_type */
};

static int	autofs_major = -1;
static void	*autofs_devfs;

static pid_t	automounter_pid;
static lck_rw_t *autofs_automounter_pid_rwlock;

static int
autofs_dev_open(__unused dev_t dev, __unused int oflags, __unused int devtype,
    struct proc *p)
{
	lck_rw_lock_exclusive(autofs_automounter_pid_rwlock);
	if (automounter_pid != 0) {
		lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
		return (EBUSY);
	}
	automounter_pid = proc_pid(p);
	lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
	return (0);
}

static int
autofs_dev_close(__unused dev_t dev, __unused int flag, __unused int fmt,
    __unused struct proc *p)
{
	lck_rw_lock_exclusive(autofs_automounter_pid_rwlock);
	automounter_pid = 0;
	lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
	return (0);
}

static int
autofs_ioctl(__unused dev_t dev, u_long cmd, __unused caddr_t data,
    __unused int flag, __unused struct proc *p)
{
	int error;

	switch (cmd) {

	case AUTOFS_NOTIFYCHANGE:
		if (fngp != NULL) {
			/*
			 * Post a flush notification, to wake up the
			 * thread waiting for the flush notification.
			 */
			lck_mtx_lock(fngp->fng_flush_notification_lock);
			fngp->fng_flush_notification_pending = 1;
			wakeup(&fngp->fng_flush_notification_pending);
			lck_mtx_unlock(fngp->fng_flush_notification_lock);
		}
		error = 0;
		break;

	case AUTOFS_WAITFORFLUSH:
		lck_mtx_lock(autofs_global_lock);
		if (fngp == NULL)
			fngp = autofs_zone_init();
		lck_mtx_unlock(autofs_global_lock);
		if (fngp == NULL)
			return (ENOMEM);

		/*
		 * Block until there's a flush notification pending or we
		 * get a signal.
		 */
		error = 0;
		lck_mtx_lock(fngp->fng_flush_notification_lock);
		while (error == 0 && !fngp->fng_flush_notification_pending) {
			error = msleep(&fngp->fng_flush_notification_pending,
			    fngp->fng_flush_notification_lock, PWAIT | PCATCH,
			    "flush notification", NULL);
		}
		fngp->fng_flush_notification_pending = 0;
		lck_mtx_unlock(fngp->fng_flush_notification_lock);
		break;

	default:
		error = EINVAL;
		break;
	}

	return (error);
}

/*
 * Opening /dev/autofs_nowait makes you (but not your children) a
 * nowait process; those processes trigger mounts, but don't wait
 * for them to finish - instead, they return ENOENT.  This is used
 * by launchd.
 *
 * Closing /dev/autofs_nowait makes you no longer a nowait process;
 * it's closed on exit, so if you exit, you cease to be a nowait process.
 */
static d_open_t	 autofs_nowait_dev_open;
static d_close_t autofs_nowait_dev_close;

static struct cdevsw autofs_nowait_cdevsw = {
	autofs_nowait_dev_open,
	autofs_nowait_dev_close,
	eno_rdwrt,	/* d_read */
	eno_rdwrt,	/* d_write */
	eno_ioctl,
	eno_stop,
	eno_reset,
	0,		/* struct tty ** d_ttys */
	eno_select,
	eno_mmap,
	eno_strat,
	eno_getc,
	eno_putc,
	0		/* d_type */
};

static int	autofs_nowait_major = -1;
static void	*autofs_nowait_devfs;

/*
 * Structure representing a process that has registered itself as an
 * nowait process by opening a cloning autofs device.
 */
struct nowait_process {
	LIST_ENTRY(nowait_process) entries;
	pid_t	pid;			/* PID of the nowait process */
	int	minor;			/* minor device they opened */
};

static LIST_HEAD(nowaitproclist, nowait_process) nowait_processes;
static lck_rw_t *autofs_nowait_processes_rwlock;

/*
 * Given the dev entry that's being opened, we clone the device.  This driver
 * doesn't actually use the dev entry, since we alreaqdy know who we are by
 * being called from this code.  This routine is a callback registered from
 * devfs_make_node_clone() in autofs_init(); its purpose is to provide a new
 * minor number, or to return -1, if one can't be provided.
 *
 * Parameters:	dev			The device we are cloning from
 *
 * Returns:	>= 0			A new minor device number
 *		-1			Error: ENOMEM ("Can't alloc device")
 *
 * NOTE:	Called with DEVFS_LOCK() held
 */
static int
autofs_nowait_dev_clone(__unused dev_t dev, int action)
{
	int minor;
	struct nowait_process *nowait_process;

	if (action == DEVFS_CLONE_ALLOC) {
		minor = 0;	/* tentative choice of minor */
		lck_rw_lock_exclusive(autofs_nowait_processes_rwlock);
		LIST_FOREACH(nowait_process, &nowait_processes, entries) {
			if (minor < nowait_process->minor) {
				/*
				 * None of the nowait processes we've looked
				 * at so far have this minor, and this
				 * minor is less than all of the minors
				 * later in the list (which is always
				 * sorted in increasing order by minor
				 * device number).
				 *
				 * Therefore, it's not in use.
				 */
				break;
			}

			/*
			 * All minors <= nowait_process->minor are in use.
			 * Try the next one after nowait_process->minor.
			 */
			minor = nowait_process->minor + 1;
		}
		lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
		return (minor);
	}

	return (-1);
}

static int
autofs_nowait_dev_open(dev_t dev, __unused int oflags, __unused int devtype,
    struct proc *p)
{
	struct nowait_process *newnowait_process, *nowait_process, *lastnowait_process;

	MALLOC(newnowait_process, struct nowait_process *, sizeof(*newnowait_process),
	    M_AUTOFS, M_WAITOK);
	if (newnowait_process == NULL)
		return (ENOMEM);
	newnowait_process->pid = proc_pid(p);
	newnowait_process->minor = minor(dev);
	/*
	 * Insert the structure in the list of nowait processes in order by
	 * minor device number.
	 */
	lck_rw_lock_exclusive(autofs_nowait_processes_rwlock);
	if (LIST_EMPTY(&nowait_processes)) {
		/*
		 * List is empty, insert at the head.
		 */
		LIST_INSERT_HEAD(&nowait_processes, newnowait_process, entries);
	} else {
		/*
		 * List isn't empty, insert in front of the first entry
		 * with a larger minor device number.
		 */
		LIST_FOREACH(nowait_process, &nowait_processes, entries) {
			if (newnowait_process->minor < nowait_process->minor) {
				/*
				 * This entry is the first one with a larger
				 * minor device number.
				 */
				LIST_INSERT_BEFORE(nowait_process,
				    newnowait_process, entries);
				goto done;
			}
			lastnowait_process = nowait_process;
		}
		/*
		 * lastnowait_process is the last entry in the list, and it
		 * doesn't have a larger minor device number than the new
		 * entry; insert the new entry after it.
		 */
		LIST_INSERT_AFTER(lastnowait_process, newnowait_process,
		    entries);
	}
done:
	lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
	return (0);
}

static int
autofs_nowait_dev_close(dev_t dev, __unused int flag, __unused int fmt,
    __unused struct proc *p)
{
	struct nowait_process *nowait_process;

	/*
	 * Remove the nowait_process structure for this device from the
	 * list of nowait processes.
	 */
	lck_rw_lock_exclusive(autofs_nowait_processes_rwlock);
	LIST_FOREACH(nowait_process, &nowait_processes, entries) {
		if (minor(dev) == nowait_process->minor) {
			LIST_REMOVE(nowait_process, entries);
			FREE(nowait_process, M_AUTOFS);
			break;
		}
	}
	lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
	return (0);
}

/*
 * Opening /dev/autofs_control when nobody else has it open lets you perform
 * various ioctls to control autofs.
 *
 * Opening /dev/autofs when somebody else has it open fails with EBUSY.
 * This is used to ensure that only one instance of the automount command
 * is running at a time.
 */
static d_open_t	 auto_control_dev_open;
static d_close_t auto_control_dev_close;
static d_ioctl_t auto_control_ioctl;

static struct cdevsw autofs_control_cdevsw = {
	auto_control_dev_open,
	auto_control_dev_close,
	eno_rdwrt,	/* d_read */
	eno_rdwrt,	/* d_write */
	auto_control_ioctl,
	eno_stop,
	eno_reset,
	0,		/* struct tty ** d_ttys */
	eno_select,
	eno_mmap,
	eno_strat,
	eno_getc,
	eno_putc,
	0		/* d_type */
};

static int	autofs_control_major = -1;
static void	*autofs_control_devfs;

static int	autofs_control_isopen;
static lck_mtx_t *autofs_control_isopen_lock;

static int
auto_control_dev_open(__unused dev_t dev, __unused int oflags,
    __unused int devtype, struct proc *p)
{
	lck_mtx_lock(autofs_control_isopen_lock);
	if (autofs_control_isopen) {
		lck_mtx_unlock(autofs_control_isopen_lock);
		return (EBUSY);
	}
	autofs_control_isopen = 1;
	lck_mtx_unlock(autofs_control_isopen_lock);
	return (0);
}

static int
auto_control_dev_close(__unused dev_t dev, __unused int flag,
    __unused int fmt, __unused struct proc *p)
{
	lck_mtx_lock(autofs_control_isopen_lock);
	autofs_control_isopen = 0;
	lck_mtx_unlock(autofs_control_isopen_lock);
	return (0);
}

static int
auto_control_ioctl(__unused dev_t dev, u_long cmd, caddr_t data,
    __unused int flag, proc_t p)
{
	int error;
	struct autofs_update_args *update_argsp_32;
	struct autofs_update_args_64 update_args;
	mount_t mp;
	fninfo_t *fnip;

	switch (cmd) {

	case AUTOFS_UPDATE_OPTIONS:
		update_argsp_32 = (struct autofs_update_args *)data;
		update_args.fsid = update_argsp_32->fsid;
		update_args.opts = CAST_USER_ADDR_T(update_argsp_32->opts);
		update_args.map = CAST_USER_ADDR_T(update_argsp_32->map);
		update_args.mntflags = update_argsp_32->mntflags;
		update_args.mount_to = update_argsp_32->mount_to;
		update_args.mach_to = update_argsp_32->mach_to;
		update_args.direct = update_argsp_32->direct;
		error = auto_update_options(&update_args);
		break;
		
	case AUTOFS_UPDATE_OPTIONS_64:
		error = auto_update_options((struct autofs_update_args_64 *)data);
		break;

	case AUTOFS_NOTIFYCHANGE:
		if (fngp != NULL) {
			/*
			 * Post a flush notification, to provoke the
			 * automounter to flush its cache.
			 */
			lck_mtx_lock(fngp->fng_flush_notification_lock);
			fngp->fng_flush_notification_pending = 1;
			wakeup(&fngp->fng_flush_notification_pending);
			lck_mtx_unlock(fngp->fng_flush_notification_lock);
		}
		error = 0;
		break;

	case AUTOFS_UNMOUNT:
		error = 0;
		if (fngp != NULL) {
			/*
			 * Try to unmount the specified autofs subtree
			 * immediately, so that we can unmount the trigger
			 * that mounted it.
			 */
			unmount_tree(fngp, (fsid_t *)data,
			    UNMOUNT_TREE_IMMEDIATE);

			/*
			 * This trigger is being unmounted because automount
			 * didn't find any fstab or automounter map entry
			 * for it.  Don't trigger any mounts on it.
			 */
			mp = vfs_getvfs((fsid_t *)data);
			if (mp == NULL) {
				error = ENOENT;
				break;
			}
			if (!auto_is_autofs(mp)) {
				error = EINVAL;
				break;
			}
			fnip = vfstofni(mp);
			lck_rw_lock_exclusive(fnip->fi_rwlock);
			fnip->fi_flags |= MF_DONTTRIGGER;
			lck_rw_unlock_exclusive(fnip->fi_rwlock);
			
			/*
			 * Wait for any in-progress mounts on the root vnode
			 * of the mount; to complete; otherwise, we can get
			 * a deadlock if:
			 *
			 *    a thread calls VFS_ROOT() on a direct map
			 *    trigger, triggering a mount - at this point,
			 *    that thread has done a vfs_busy() on the
			 *    mount_t for the direct map autofs mount, so
			 *    it's holding a shared lock on the mount_t;
			 *
			 *    automount then tries to unmount that autofs
			 *    mount, which means it tries to grab an
			 *    exclusive lock on the mount_t and blocks;
			 *
			 *    the automounter does a lookup while trying to
			 *    do the mount requested by the thread above,
			 *    and does a vfs_busy(), which blocks because
			 *    it tries to get a shared lock but somebody
			 *    wants an exclusive lock.
			 *
			 * Once this finishes, we know there won't be any
			 * more automounts on it, as we set MF_DONTTRIGGER.
			 */
			auto_wait4mount(vntofn(fnip->fi_rootvp),
			    vfs_context_current());

			/*
			 * Now unmount the trigger.
			 */
			error = vfs_unmountbyfsid((fsid_t *)data, 0,
			    vfs_context_current());
		}
		break;

	default:
		error = EINVAL;
		break;
	}

	return (error);
}

/*
 * Check whether this process is a automounter or an inferior of a automounter.
 */
int
auto_is_automounter(pid_t pid)
{
	int is_automounter;

	lck_rw_lock_shared(autofs_automounter_pid_rwlock);
	is_automounter = (automounter_pid != 0 &&
	    (pid == automounter_pid || proc_isinferior(pid, automounter_pid)));
	lck_rw_unlock_shared(autofs_automounter_pid_rwlock);
	return (is_automounter);
}

/*
 * Check whether this process is a nowait process.
 */
int
auto_is_nowait_process(pid_t pid)
{
	struct nowait_process *nowait_process;

	lck_rw_lock_shared(autofs_nowait_processes_rwlock);
	LIST_FOREACH(nowait_process, &nowait_processes, entries) {
		if (pid == nowait_process->pid) {
			lck_rw_unlock_shared(autofs_nowait_processes_rwlock);
			return (1);
		}
	}
	lck_rw_unlock_shared(autofs_nowait_processes_rwlock);
	return (0);
}

/*
 * Initialize the filesystem
 */
__private_extern__ int
auto_module_start(kmod_info_t *ki, void *data)
{
	errno_t error;

	/*
	 * Set up the lock group and the global locks.
	 */
	autofs_lck_grp = lck_grp_alloc_init("autofs", NULL);
	if (autofs_lck_grp == NULL) {
		log(LOG_ERR, "auto_module_start: Couldn't create autofs lock group\n");
		goto fail;
	}
	autofs_global_lock = lck_mtx_alloc_init(autofs_lck_grp, LCK_ATTR_NULL);
	if (autofs_global_lock == NULL) {
		log(LOG_ERR, "auto_module_start: Couldn't create autofs global lock\n");
		goto fail;
	}
	autofs_nodeid_lock = lck_mtx_alloc_init(autofs_lck_grp, LCK_ATTR_NULL);
	if (autofs_nodeid_lock == NULL) {
		log(LOG_ERR, "auto_module_start: Couldn't create autofs node ID lock\n");
		goto fail;
	}
	autofs_automounter_pid_rwlock = lck_rw_alloc_init(autofs_lck_grp, NULL);
	if (autofs_automounter_pid_rwlock == NULL) {
		log(LOG_ERR, "auto_module_start: Couldn't create autofs automounter pid lock\n");
		goto fail;
	}
	autofs_nowait_processes_rwlock = lck_rw_alloc_init(autofs_lck_grp, NULL);
	if (autofs_nowait_processes_rwlock == NULL) {
		log(LOG_ERR, "auto_module_start: Couldn't create autofs nowait_processes list lock\n");
		goto fail;
	}
	autofs_control_isopen_lock = lck_mtx_alloc_init(autofs_lck_grp, LCK_ATTR_NULL);
	if (autofs_control_isopen_lock == NULL) {
		log(LOG_ERR, "auto_module_start: Couldn't create autofs control device lock\n");
		goto fail;
	}
	
	/*
	 * Add the autofs device.
	 */
	autofs_major = cdevsw_add(-1, &autofs_cdevsw);
	if (autofs_major == -1) {
		log(LOG_ERR, "auto_module_start: cdevsw_add failed on autofs device\n");
		goto fail;
	}
	autofs_devfs = devfs_make_node(makedev(autofs_major, 0),
	    DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0600, AUTOFS_DEVICE);
	if (autofs_devfs == NULL) {
		log(LOG_ERR, "auto_module_start: devfs_make_node failed on autofs device\n");
		goto fail;
	}
	
	/*
	 * Add the autofs nowait device.  Everybody's allowed to open it.
	 */
	autofs_nowait_major = cdevsw_add(-1, &autofs_nowait_cdevsw);
	if (autofs_nowait_major == -1) {
		log(LOG_ERR, "auto_module_start: cdevsw_add failed on autofs_nowait device\n");
		goto fail;
	}
	autofs_nowait_devfs = devfs_make_node_clone(makedev(autofs_nowait_major, 0),
	    DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0666, autofs_nowait_dev_clone,
	    AUTOFS_NOWAIT_DEVICE);
	if (autofs_nowait_devfs == NULL) {
		log(LOG_ERR, "auto_module_start: devfs_make_node failed on autofs nowait device\n");
		goto fail;
	}

	/*
	 * Add the autofs control device.
	 */
	autofs_control_major = cdevsw_add(-1, &autofs_control_cdevsw);
	if (autofs_control_major == -1) {
		log(LOG_ERR, "auto_module_start: cdevsw_add failed on autofs control device\n");
		goto fail;
	}
	autofs_control_devfs = devfs_make_node(makedev(autofs_control_major, 0),
	    DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0600, AUTOFS_CONTROL_DEVICE);
	if (autofs_control_devfs == NULL) {
		log(LOG_ERR, "auto_module_start: devfs_make_node failed on autofs control device\n");
		goto fail;
	}

	/*
	 * Register the file system.
	 */
	error = vfs_fsadd(&autofs_fsentry, &auto_vfsconf);
	if (error != 0) {
		log(LOG_ERR, "auto_module_start: Error %d from vfs_fsadd\n",
		    error);
		goto fail;
	}

	return (KERN_SUCCESS);

fail:
	if (autofs_control_devfs != NULL)
		devfs_remove(autofs_control_devfs);
	if (autofs_control_major != -1) {
		if (cdevsw_remove(autofs_control_major, &autofs_control_cdevsw) == -1)
			panic("auto_module_start: can't remove autofs control device from cdevsw");
	}
	if (autofs_nowait_devfs != NULL)
		devfs_remove(autofs_nowait_devfs);
	if (autofs_nowait_major != -1) {
		if (cdevsw_remove(autofs_nowait_major, &autofs_nowait_cdevsw) == -1)
			panic("auto_module_start: can't remove autofs nowait device from cdevsw");
	}
	if (autofs_devfs != NULL)
		devfs_remove(autofs_devfs);
	if (autofs_major != -1) {
		if (cdevsw_remove(autofs_major, &autofs_cdevsw) == -1)
			panic("auto_module_start: can't remove autofs device from cdevsw");
	}
	if (autofs_control_isopen_lock != NULL)
		lck_mtx_free(autofs_control_isopen_lock, autofs_lck_grp);
	if (autofs_nowait_processes_rwlock != NULL)
		lck_rw_free(autofs_nowait_processes_rwlock, autofs_lck_grp);
	if (autofs_automounter_pid_rwlock != NULL)
		lck_rw_free(autofs_automounter_pid_rwlock, autofs_lck_grp);
	if (autofs_nodeid_lock != NULL)
		lck_mtx_free(autofs_nodeid_lock, autofs_lck_grp);
	if (autofs_global_lock != NULL)
		lck_mtx_free(autofs_global_lock, autofs_lck_grp);
	if (autofs_lck_grp != NULL)
		lck_grp_free(autofs_lck_grp);
	return (KERN_FAILURE);
}

__private_extern__ int
auto_module_stop(kmod_info_t *ki, void *data)
{
	int error;
	
	lck_mtx_lock(autofs_global_lock);
	lck_rw_lock_exclusive(autofs_automounter_pid_rwlock);
	lck_rw_lock_exclusive(autofs_nowait_processes_rwlock);
	lck_mtx_lock(autofs_control_isopen_lock);
	if (autofs_mounts != 0) {
		AUTOFS_DPRINT((2, "auto_module_stop: Can't remove, still %u mounts active\n", autofs_mounts));
		lck_mtx_unlock(autofs_control_isopen_lock);
		lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
		lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
		lck_mtx_unlock(autofs_global_lock);
		return (KERN_NO_ACCESS);
	}
	if (automounter_pid != 0) {
		AUTOFS_DPRINT((2, "auto_module_stop: Can't remove, automounter still running\n"));
		lck_mtx_unlock(autofs_control_isopen_lock);
		lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
		lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
		lck_mtx_unlock(autofs_global_lock);
		return (KERN_NO_ACCESS);
	}
	if (!LIST_EMPTY(&nowait_processes)) {
		AUTOFS_DPRINT((2, "auto_module_stop: Can't remove, still nowait processes running\n"));
		lck_mtx_unlock(autofs_control_isopen_lock);
		lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
		lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
		lck_mtx_unlock(autofs_global_lock);
		return (KERN_NO_ACCESS);
	}
	if (autofs_control_isopen) {
		AUTOFS_DPRINT((2, "auto_module_stop: Can't remove, automount command is running\n"));
		lck_mtx_unlock(autofs_control_isopen_lock);
		lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
		lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
		lck_mtx_unlock(autofs_global_lock);
		return (KERN_NO_ACCESS);
	}
	AUTOFS_DPRINT((10, "auto_module_stop: removing autofs from vfs conf. list...\n"));

	if (fngp) {
		assert(fngp->fng_fnnode_count == 1);
		assert(fngp->fng_unmount_threads == 0);
	}

	error = vfs_fsremove(auto_vfsconf);
	if (error) {
		log(LOG_ERR, "auto_module_stop: Error %d from vfs_remove\n",
		    error);
		return (KERN_FAILURE);
	}

	devfs_remove(autofs_devfs);
	autofs_devfs = NULL;
	if (cdevsw_remove(autofs_major, &autofs_cdevsw) == -1)
		panic("auto_module_stop: can't remove autofs device from cdevsw");
	autofs_major = -1;
	devfs_remove(autofs_nowait_devfs);
	autofs_nowait_devfs = NULL;
	if (cdevsw_remove(autofs_nowait_major, &autofs_nowait_cdevsw) == -1)
		panic("auto_module_stop: can't remove autofs nowait device from cdevsw");
	autofs_nowait_major = -1;
	devfs_remove(autofs_control_devfs);
	autofs_control_devfs = NULL;
	if (cdevsw_remove(autofs_control_major, &autofs_control_cdevsw) == -1)
		panic("auto_module_start: can't remove autofs control device from cdevsw");

	if (fngp) {
		/*
		 * Tell the unmounter thread to go away.
		 */
		lck_mtx_lock(fngp->fng_unmount_threads_lock);
		fngp->fng_terminate_do_unmount_thread = 1;
		wakeup(&fngp->fng_terminate_do_unmount_thread);

		/*
		 * Wait for it to go away.
		 */
		while (!fngp->fng_do_unmount_thread_terminated) {
			msleep(&fngp->fng_do_unmount_thread_terminated,
			    fngp->fng_unmount_threads_lock, PWAIT,
			    "unmount thread terminated", NULL);
		}
		lck_mtx_unlock(fngp->fng_unmount_threads_lock);

		/*
		 * Free up the root fnnode.
		 */
		FREE(fngp->fng_rootfnnodep->fn_name, M_AUTOFS);
		FREE(fngp->fng_rootfnnodep, M_AUTOFS);

		lck_mtx_free(fngp->fng_unmount_threads_lock, autofs_lck_grp);
		lck_mtx_free(fngp->fng_flush_notification_lock, autofs_lck_grp);
		FREE(fngp, M_AUTOFS);
	}

	lck_mtx_unlock(autofs_nodeid_lock);
	lck_mtx_free(autofs_nodeid_lock, autofs_lck_grp);
	lck_mtx_unlock(autofs_global_lock);
	lck_mtx_free(autofs_global_lock, autofs_lck_grp);
	lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
	lck_rw_free(autofs_automounter_pid_rwlock, autofs_lck_grp);
	lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
	lck_rw_free(autofs_nowait_processes_rwlock, autofs_lck_grp);
	lck_mtx_unlock(autofs_control_isopen_lock);
	lck_mtx_free(autofs_control_isopen_lock, autofs_lck_grp);
	lck_grp_free(autofs_lck_grp);
	
	return (KERN_SUCCESS);
}