view usr/src/uts/common/fs/doorfs/door_sys.c @ 3898:c788126f2a20

PSARC/2007/124 Strong Type-Checking for VFS Operation Registration Mechanism 6505923 Need better type checking for vnodeops 6531594 lxpr_readlink() is missing the "cred_t *cr" arg 6532559 vfs_strayops does not use the vnode/vfs operation registration mechanism
author rsb
date Mon, 26 Mar 2007 17:41:06 -0700
parents ff89f8283e6c
children 3047ad28a67b
line wrap: on
line source

/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * System call I/F to doors (outside of vnodes I/F) and misc support
 * routines
 */
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/door.h>
#include <sys/door_data.h>
#include <sys/proc.h>
#include <sys/thread.h>
#include <sys/class.h>
#include <sys/cred.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/stack.h>
#include <sys/debug.h>
#include <sys/cpuvar.h>
#include <sys/file.h>
#include <sys/fcntl.h>
#include <sys/vnode.h>
#include <sys/vfs.h>
#include <sys/vfs_opreg.h>
#include <sys/sobject.h>
#include <sys/schedctl.h>
#include <sys/callb.h>
#include <sys/ucred.h>

#include <sys/mman.h>
#include <sys/sysmacros.h>
#include <sys/vmsystm.h>
#include <vm/as.h>
#include <vm/hat.h>
#include <vm/page.h>
#include <vm/seg.h>
#include <vm/seg_vn.h>
#include <vm/seg_vn.h>

#include <sys/modctl.h>
#include <sys/syscall.h>
#include <sys/pathname.h>
#include <sys/rctl.h>

/*
 * The maximum amount of data (in bytes) that will be transfered using
 * an intermediate kernel buffer.  For sizes greater than this we map
 * in the destination pages and perform a 1-copy transfer.
 */
size_t	door_max_arg = 16 * 1024;

/*
 * Maximum amount of data that will be transfered in a reply to a
 * door_upcall.  Need to guard against a process returning huge amounts
 * of data and getting the kernel stuck in kmem_alloc.
 */
size_t	door_max_upcall_reply = 1024 * 1024;

/*
 * Maximum number of descriptors allowed to be passed in a single
 * door_call or door_return.  We need to allocate kernel memory
 * for all of them at once, so we can't let it scale without limit.
 */
uint_t door_max_desc = 1024;

/*
 * Definition of a door handle, used by other kernel subsystems when
 * calling door functions.  This is really a file structure but we
 * want to hide that fact.
 */
struct __door_handle {
	file_t dh_file;
};

#define	DHTOF(dh) ((file_t *)(dh))
#define	FTODH(fp) ((door_handle_t)(fp))

static int doorfs(long, long, long, long, long, long);

static struct sysent door_sysent = {
	6,
	SE_ARGC | SE_NOUNLOAD,
	(int (*)())doorfs,
};

static struct modlsys modlsys = {
	&mod_syscallops, "doors", &door_sysent
};

#ifdef _SYSCALL32_IMPL

static int
doorfs32(int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4,
    int32_t arg5, int32_t subcode);

static struct sysent door_sysent32 = {
	6,
	SE_ARGC | SE_NOUNLOAD,
	(int (*)())doorfs32,
};

static struct modlsys modlsys32 = {
	&mod_syscallops32,
	"32-bit door syscalls",
	&door_sysent32
};
#endif

static struct modlinkage modlinkage = {
	MODREV_1,
	&modlsys,
#ifdef _SYSCALL32_IMPL
	&modlsys32,
#endif
	NULL
};

dev_t	doordev;

extern	struct vfs door_vfs;
extern	struct vnodeops *door_vnodeops;

int
_init(void)
{
	static const fs_operation_def_t door_vfsops_template[] = {
		NULL, NULL
	};
	extern const fs_operation_def_t door_vnodeops_template[];
	vfsops_t *door_vfsops;
	major_t major;
	int error;

	mutex_init(&door_knob, NULL, MUTEX_DEFAULT, NULL);
	if ((major = getudev()) == (major_t)-1)
		return (ENXIO);
	doordev = makedevice(major, 0);

	/* Create a dummy vfs */
	error = vfs_makefsops(door_vfsops_template, &door_vfsops);
	if (error != 0) {
		cmn_err(CE_WARN, "door init: bad vfs ops");
		return (error);
	}
	vfs_setops(&door_vfs, door_vfsops);
	door_vfs.vfs_flag = VFS_RDONLY;
	door_vfs.vfs_dev = doordev;
	vfs_make_fsid(&(door_vfs.vfs_fsid), doordev, 0);

	error = vn_make_ops("doorfs", door_vnodeops_template, &door_vnodeops);
	if (error != 0) {
		vfs_freevfsops(door_vfsops);
		cmn_err(CE_WARN, "door init: bad vnode ops");
		return (error);
	}
	return (mod_install(&modlinkage));
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

/* system call functions */
static int door_call(int, void *);
static int door_return(caddr_t, size_t, door_desc_t *, uint_t, caddr_t, size_t);
static int door_create(void (*pc_cookie)(void *, char *, size_t, door_desc_t *,
    uint_t), void *data_cookie, uint_t);
static int door_revoke(int);
static int door_info(int, struct door_info *);
static int door_ucred(struct ucred_s *);
static int door_bind(int);
static int door_unbind(void);
static int door_unref(void);
static int door_getparam(int, int, size_t *);
static int door_setparam(int, int, size_t);

#define	DOOR_RETURN_OLD	4		/* historic value, for s10 */

/*
 * System call wrapper for all door related system calls
 */
static int
doorfs(long arg1, long arg2, long arg3, long arg4, long arg5, long subcode)
{
	switch (subcode) {
	case DOOR_CALL:
		return (door_call(arg1, (void *)arg2));
	case DOOR_RETURN: {
		door_return_desc_t *drdp = (door_return_desc_t *)arg3;

		if (drdp != NULL) {
			door_return_desc_t drd;
			if (copyin(drdp, &drd, sizeof (drd)))
				return (EFAULT);
			return (door_return((caddr_t)arg1, arg2, drd.desc_ptr,
			    drd.desc_num, (caddr_t)arg4, arg5));
		}
		return (door_return((caddr_t)arg1, arg2, NULL,
		    0, (caddr_t)arg4, arg5));
	}
	case DOOR_RETURN_OLD:
		/*
		 * In order to support the S10 runtime environment, we
		 * still respond to the old syscall subcode for door_return.
		 * We treat it as having no stack limits.  This code should
		 * be removed when such support is no longer needed.
		 */
		return (door_return((caddr_t)arg1, arg2, (door_desc_t *)arg3,
		    arg4, (caddr_t)arg5, 0));
	case DOOR_CREATE:
		return (door_create((void (*)())arg1, (void *)arg2, arg3));
	case DOOR_REVOKE:
		return (door_revoke(arg1));
	case DOOR_INFO:
		return (door_info(arg1, (struct door_info *)arg2));
	case DOOR_BIND:
		return (door_bind(arg1));
	case DOOR_UNBIND:
		return (door_unbind());
	case DOOR_UNREFSYS:
		return (door_unref());
	case DOOR_UCRED:
		return (door_ucred((struct ucred_s *)arg1));
	case DOOR_GETPARAM:
		return (door_getparam(arg1, arg2, (size_t *)arg3));
	case DOOR_SETPARAM:
		return (door_setparam(arg1, arg2, arg3));
	default:
		return (set_errno(EINVAL));
	}
}

#ifdef _SYSCALL32_IMPL
/*
 * System call wrapper for all door related system calls from 32-bit programs.
 * Needed at the moment because of the casts - they undo some damage
 * that truss causes (sign-extending the stack pointer) when truss'ing
 * a 32-bit program using doors.
 */
static int
doorfs32(int32_t arg1, int32_t arg2, int32_t arg3,
    int32_t arg4, int32_t arg5, int32_t subcode)
{
	switch (subcode) {
	case DOOR_CALL:
		return (door_call(arg1, (void *)(uintptr_t)(caddr32_t)arg2));
	case DOOR_RETURN: {
		door_return_desc32_t *drdp =
		    (door_return_desc32_t *)(uintptr_t)(caddr32_t)arg3;
		if (drdp != NULL) {
			door_return_desc32_t drd;
			if (copyin(drdp, &drd, sizeof (drd)))
				return (EFAULT);
			return (door_return(
			    (caddr_t)(uintptr_t)(caddr32_t)arg1, arg2,
			    (door_desc_t *)(uintptr_t)drd.desc_ptr,
			    drd.desc_num, (caddr_t)(uintptr_t)(caddr32_t)arg4,
			    (size_t)(uintptr_t)(size32_t)arg5));
		}
		return (door_return((caddr_t)(uintptr_t)(caddr32_t)arg1,
		    arg2, NULL, 0, (caddr_t)(uintptr_t)(caddr32_t)arg4,
		    (size_t)(uintptr_t)(size32_t)arg5));
	}
	case DOOR_RETURN_OLD:
		/*
		 * In order to support the S10 runtime environment, we
		 * still respond to the old syscall subcode for door_return.
		 * We treat it as having no stack limits.  This code should
		 * be removed when such support is no longer needed.
		 */
		return (door_return((caddr_t)(uintptr_t)(caddr32_t)arg1, arg2,
		    (door_desc_t *)(uintptr_t)(caddr32_t)arg3, arg4,
		    (caddr_t)(uintptr_t)(caddr32_t)arg5, 0));
	case DOOR_CREATE:
		return (door_create((void (*)())(uintptr_t)(caddr32_t)arg1,
		    (void *)(uintptr_t)(caddr32_t)arg2, arg3));
	case DOOR_REVOKE:
		return (door_revoke(arg1));
	case DOOR_INFO:
		return (door_info(arg1,
		    (struct door_info *)(uintptr_t)(caddr32_t)arg2));
	case DOOR_BIND:
		return (door_bind(arg1));
	case DOOR_UNBIND:
		return (door_unbind());
	case DOOR_UNREFSYS:
		return (door_unref());
	case DOOR_UCRED:
		return (door_ucred(
		    (struct ucred_s *)(uintptr_t)(caddr32_t)arg1));
	case DOOR_GETPARAM:
		return (door_getparam(arg1, arg2,
		    (size_t *)(uintptr_t)(caddr32_t)arg3));
	case DOOR_SETPARAM:
		return (door_setparam(arg1, arg2, (size_t)(size32_t)arg3));

	default:
		return (set_errno(EINVAL));
	}
}
#endif

void shuttle_resume(kthread_t *, kmutex_t *);
void shuttle_swtch(kmutex_t *);
void shuttle_sleep(kthread_t *);

/*
 * Support routines
 */
static int door_create_common(void (*)(), void *, uint_t, int, int *,
    file_t **);
static int door_overflow(kthread_t *, caddr_t, size_t, door_desc_t *, uint_t);
static int door_args(kthread_t *, int);
static int door_results(kthread_t *, caddr_t, size_t, door_desc_t *, uint_t);
static int door_copy(struct as *, caddr_t, caddr_t, uint_t);
static void	door_server_exit(proc_t *, kthread_t *);
static void	door_release_server(door_node_t *, kthread_t *);
static kthread_t	*door_get_server(door_node_t *);
static door_node_t	*door_lookup(int, file_t **);
static int	door_translate_in(void);
static int	door_translate_out(void);
static void	door_fd_rele(door_desc_t *, uint_t, int);
static void	door_list_insert(door_node_t *);
static void	door_info_common(door_node_t *, door_info_t *, file_t *);
static int	door_release_fds(door_desc_t *, uint_t);
static void	door_fd_close(door_desc_t *, uint_t);
static void	door_fp_close(struct file **, uint_t);

static door_data_t *
door_my_data(int create_if_missing)
{
	door_data_t *ddp;

	ddp = curthread->t_door;
	if (create_if_missing && ddp == NULL)
		ddp = curthread->t_door = kmem_zalloc(sizeof (*ddp), KM_SLEEP);

	return (ddp);
}

static door_server_t *
door_my_server(int create_if_missing)
{
	door_data_t *ddp = door_my_data(create_if_missing);

	return ((ddp != NULL)? DOOR_SERVER(ddp) : NULL);
}

static door_client_t *
door_my_client(int create_if_missing)
{
	door_data_t *ddp = door_my_data(create_if_missing);

	return ((ddp != NULL)? DOOR_CLIENT(ddp) : NULL);
}

/*
 * System call to create a door
 */
int
door_create(void (*pc_cookie)(), void *data_cookie, uint_t attributes)
{
	int fd;
	int err;

	if ((attributes & ~DOOR_CREATE_MASK) ||
	    ((attributes & (DOOR_UNREF | DOOR_UNREF_MULTI)) ==
	    (DOOR_UNREF | DOOR_UNREF_MULTI)))
		return (set_errno(EINVAL));

	if ((err = door_create_common(pc_cookie, data_cookie, attributes, 0,
	    &fd, NULL)) != 0)
		return (set_errno(err));

	f_setfd(fd, FD_CLOEXEC);
	return (fd);
}

/*
 * Common code for creating user and kernel doors.  If a door was
 * created, stores a file structure pointer in the location pointed
 * to by fpp (if fpp is non-NULL) and returns 0.  Also, if a non-NULL
 * pointer to a file descriptor is passed in as fdp, allocates a file
 * descriptor representing the door.  If a door could not be created,
 * returns an error.
 */
static int
door_create_common(void (*pc_cookie)(), void *data_cookie, uint_t attributes,
    int from_kernel, int *fdp, file_t **fpp)
{
	door_node_t	*dp;
	vnode_t		*vp;
	struct file	*fp;
	static door_id_t index = 0;
	proc_t		*p = (from_kernel)? &p0 : curproc;

	dp = kmem_zalloc(sizeof (door_node_t), KM_SLEEP);

	dp->door_vnode = vn_alloc(KM_SLEEP);
	dp->door_target = p;
	dp->door_data = data_cookie;
	dp->door_pc = pc_cookie;
	dp->door_flags = attributes;
#ifdef _SYSCALL32_IMPL
	if (!from_kernel && get_udatamodel() != DATAMODEL_NATIVE)
		dp->door_data_max = UINT32_MAX;
	else
#endif
		dp->door_data_max = SIZE_MAX;
	dp->door_data_min = 0UL;
	dp->door_desc_max = (attributes & DOOR_REFUSE_DESC)? 0 : INT_MAX;

	vp = DTOV(dp);
	vn_setops(vp, door_vnodeops);
	vp->v_type = VDOOR;
	vp->v_vfsp = &door_vfs;
	vp->v_data = (caddr_t)dp;
	mutex_enter(&door_knob);
	dp->door_index = index++;
	/* add to per-process door list */
	door_list_insert(dp);
	mutex_exit(&door_knob);

	if (falloc(vp, FREAD | FWRITE, &fp, fdp)) {
		/*
		 * If the file table is full, remove the door from the
		 * per-process list, free the door, and return NULL.
		 */
		mutex_enter(&door_knob);
		door_list_delete(dp);
		mutex_exit(&door_knob);
		vn_free(vp);
		kmem_free(dp, sizeof (door_node_t));
		return (EMFILE);
	}
	vn_exists(vp);
	if (fdp != NULL)
		setf(*fdp, fp);
	mutex_exit(&fp->f_tlock);

	if (fpp != NULL)
		*fpp = fp;
	return (0);
}

static int
door_check_limits(door_node_t *dp, door_arg_t *da, int upcall)
{
	ASSERT(MUTEX_HELD(&door_knob));

	/* we allow unref upcalls through, despite any minimum */
	if (da->data_size < dp->door_data_min &&
	    !(upcall && da->data_ptr == DOOR_UNREF_DATA))
		return (ENOBUFS);

	if (da->data_size > dp->door_data_max)
		return (ENOBUFS);

	if (da->desc_num > 0 && (dp->door_flags & DOOR_REFUSE_DESC))
		return (ENOTSUP);

	if (da->desc_num > dp->door_desc_max)
		return (ENFILE);

	return (0);
}

/*
 * Door invocation.
 */
int
door_call(int did, void *args)
{
	/* Locals */
	door_node_t	*dp;
	kthread_t	*server_thread;
	int		error = 0;
	klwp_t		*lwp;
	door_client_t	*ct;		/* curthread door_data */
	door_server_t	*st;		/* server thread door_data */
	door_desc_t	*start = NULL;
	uint_t		ncopied = 0;
	size_t		dsize;
	/* destructor for data returned by a kernel server */
	void		(*destfn)() = NULL;
	void		*destarg;
	model_t		datamodel;
	int		gotresults = 0;

	lwp = ttolwp(curthread);
	datamodel = lwp_getdatamodel(lwp);

	ct = door_my_client(1);

	/*
	 * Get the arguments
	 */
	if (args) {
		if (datamodel == DATAMODEL_NATIVE) {
			if (copyin(args, &ct->d_args, sizeof (door_arg_t)) != 0)
				return (set_errno(EFAULT));
		} else {
			door_arg32_t    da32;

			if (copyin(args, &da32, sizeof (door_arg32_t)) != 0)
				return (set_errno(EFAULT));
			ct->d_args.data_ptr =
			    (char *)(uintptr_t)da32.data_ptr;
			ct->d_args.data_size = da32.data_size;
			ct->d_args.desc_ptr =
			    (door_desc_t *)(uintptr_t)da32.desc_ptr;
			ct->d_args.desc_num = da32.desc_num;
			ct->d_args.rbuf =
			    (char *)(uintptr_t)da32.rbuf;
			ct->d_args.rsize = da32.rsize;
		}
	} else {
		/* No arguments, and no results allowed */
		ct->d_noresults = 1;
		ct->d_args.data_size = 0;
		ct->d_args.desc_num = 0;
		ct->d_args.rsize = 0;
	}

	if ((dp = door_lookup(did, NULL)) == NULL)
		return (set_errno(EBADF));

	mutex_enter(&door_knob);
	if (DOOR_INVALID(dp)) {
		mutex_exit(&door_knob);
		error = EBADF;
		goto out;
	}

	/*
	 * before we do anything, check that we are not overflowing the
	 * required limits.
	 */
	error = door_check_limits(dp, &ct->d_args, 0);
	if (error != 0) {
		mutex_exit(&door_knob);
		goto out;
	}

	/*
	 * Check for in-kernel door server.
	 */
	if (dp->door_target == &p0) {
		caddr_t rbuf = ct->d_args.rbuf;
		size_t rsize = ct->d_args.rsize;

		dp->door_active++;
		ct->d_kernel = 1;
		ct->d_error = DOOR_WAIT;
		mutex_exit(&door_knob);
		/* translate file descriptors to vnodes */
		if (ct->d_args.desc_num) {
			error = door_translate_in();
			if (error)
				goto out;
		}
		/*
		 * Call kernel door server.  Arguments are passed and
		 * returned as a door_arg pointer.  When called, data_ptr
		 * points to user data and desc_ptr points to a kernel list
		 * of door descriptors that have been converted to file
		 * structure pointers.  It's the server function's
		 * responsibility to copyin the data pointed to by data_ptr
		 * (this avoids extra copying in some cases).  On return,
		 * data_ptr points to a user buffer of data, and desc_ptr
		 * points to a kernel list of door descriptors representing
		 * files.  When a reference is passed to a kernel server,
		 * it is the server's responsibility to release the reference
		 * (by calling closef).  When the server includes a
		 * reference in its reply, it is released as part of the
		 * the call (the server must duplicate the reference if
		 * it wants to retain a copy).  The destfn, if set to
		 * non-NULL, is a destructor to be called when the returned
		 * kernel data (if any) is no longer needed (has all been
		 * translated and copied to user level).
		 */
		(*(dp->door_pc))(dp->door_data, &ct->d_args,
		    &destfn, &destarg, &error);
		mutex_enter(&door_knob);
		/* not implemented yet */
		if (--dp->door_active == 0 && (dp->door_flags & DOOR_DELAY))
			door_deliver_unref(dp);
		mutex_exit(&door_knob);
		if (error)
			goto out;

		/* translate vnodes to files */
		if (ct->d_args.desc_num) {
			error = door_translate_out();
			if (error)
				goto out;
		}
		ct->d_buf = ct->d_args.rbuf;
		ct->d_bufsize = ct->d_args.rsize;
		if (rsize < (ct->d_args.data_size +
		    (ct->d_args.desc_num * sizeof (door_desc_t)))) {
			/* handle overflow */
			error = door_overflow(curthread, ct->d_args.data_ptr,
			    ct->d_args.data_size, ct->d_args.desc_ptr,
			    ct->d_args.desc_num);
			if (error)
				goto out;
			/* door_overflow sets d_args rbuf and rsize */
		} else {
			ct->d_args.rbuf = rbuf;
			ct->d_args.rsize = rsize;
		}
		goto results;
	}

	/*
	 * Get a server thread from the target domain
	 */
	if ((server_thread = door_get_server(dp)) == NULL) {
		if (DOOR_INVALID(dp))
			error = EBADF;
		else
			error = EAGAIN;
		mutex_exit(&door_knob);
		goto out;
	}

	st = DOOR_SERVER(server_thread->t_door);
	if (ct->d_args.desc_num || ct->d_args.data_size) {
		int is_private = (dp->door_flags & DOOR_PRIVATE);
		/*
		 * Move data from client to server
		 */
		DOOR_T_HOLD(st);
		mutex_exit(&door_knob);
		error = door_args(server_thread, is_private);
		mutex_enter(&door_knob);
		DOOR_T_RELEASE(st);
		if (error) {
			/*
			 * We're not going to resume this thread after all
			 */
			door_release_server(dp, server_thread);
			shuttle_sleep(server_thread);
			mutex_exit(&door_knob);
			goto out;
		}
	}

	dp->door_active++;
	ct->d_error = DOOR_WAIT;
	st->d_caller = curthread;
	st->d_active = dp;

	shuttle_resume(server_thread, &door_knob);

	mutex_enter(&door_knob);
shuttle_return:
	if ((error = ct->d_error) < 0) {	/* DOOR_WAIT or DOOR_EXIT */
		/*
		 * Premature wakeup. Find out why (stop, forkall, sig, exit ...)
		 */
		mutex_exit(&door_knob);		/* May block in ISSIG */
		if (ISSIG(curthread, FORREAL) ||
		    lwp->lwp_sysabort || MUSTRETURN(curproc, curthread)) {
			/* Signal, forkall, ... */
			lwp->lwp_sysabort = 0;
			mutex_enter(&door_knob);
			error = EINTR;
			/*
			 * If the server has finished processing our call,
			 * or exited (calling door_slam()), then d_error
			 * will have changed.  If the server hasn't finished
			 * yet, d_error will still be DOOR_WAIT, and we
			 * let it know we are not interested in any
			 * results by sending a SIGCANCEL, unless the door
			 * is marked with DOOR_NO_CANCEL.
			 */
			if (ct->d_error == DOOR_WAIT &&
			    st->d_caller == curthread) {
				proc_t	*p = ttoproc(server_thread);

				st->d_active = NULL;
				st->d_caller = NULL;

				if (!(dp->door_flags & DOOR_NO_CANCEL)) {
					DOOR_T_HOLD(st);
					mutex_exit(&door_knob);

					mutex_enter(&p->p_lock);
					sigtoproc(p, server_thread, SIGCANCEL);
					mutex_exit(&p->p_lock);

					mutex_enter(&door_knob);
					DOOR_T_RELEASE(st);
				}
			}
		} else {
			/*
			 * Return from stop(), server exit...
			 *
			 * Note that the server could have done a
			 * door_return while the client was in stop state
			 * (ISSIG), in which case the error condition
			 * is updated by the server.
			 */
			mutex_enter(&door_knob);
			if (ct->d_error == DOOR_WAIT) {
				/* Still waiting for a reply */
				shuttle_swtch(&door_knob);
				mutex_enter(&door_knob);
				lwp->lwp_asleep = 0;
				goto	shuttle_return;
			} else if (ct->d_error == DOOR_EXIT) {
				/* Server exit */
				error = EINTR;
			} else {
				/* Server did a door_return during ISSIG */
				error = ct->d_error;
			}
		}
		/*
		 * Can't exit if the server is currently copying
		 * results for me.
		 */
		while (DOOR_T_HELD(ct))
			cv_wait(&ct->d_cv, &door_knob);

		/*
		 * Find out if results were successfully copied.
		 */
		if (ct->d_error == 0)
			gotresults = 1;
	}
	lwp->lwp_asleep = 0;		/* /proc */
	lwp->lwp_sysabort = 0;		/* /proc */
	if (--dp->door_active == 0 && (dp->door_flags & DOOR_DELAY))
		door_deliver_unref(dp);
	mutex_exit(&door_knob);

results:
	/*
	 * Move the results to userland (if any)
	 */

	if (ct->d_noresults)
		goto out;

	if (error) {
		/*
		 * If server returned results successfully, then we've
		 * been interrupted and may need to clean up.
		 */
		if (gotresults) {
			ASSERT(error == EINTR);
			door_fp_close(ct->d_fpp, ct->d_args.desc_num);
		}
		goto out;
	}

	/*
	 * Copy back data if we haven't caused an overflow (already
	 * handled) and we are using a 2 copy transfer, or we are
	 * returning data from a kernel server.
	 */
	if (ct->d_args.data_size) {
		ct->d_args.data_ptr = ct->d_args.rbuf;
		if (ct->d_kernel || (!ct->d_overflow &&
		    ct->d_args.data_size <= door_max_arg)) {
			if (copyout(ct->d_buf, ct->d_args.rbuf,
			    ct->d_args.data_size)) {
				door_fp_close(ct->d_fpp, ct->d_args.desc_num);
				error = EFAULT;
				goto out;
			}
		}
	}

	/*
	 * stuff returned doors into our proc, copyout the descriptors
	 */
	if (ct->d_args.desc_num) {
		struct file	**fpp;
		door_desc_t	*didpp;
		uint_t		n = ct->d_args.desc_num;

		dsize = n * sizeof (door_desc_t);
		start = didpp = kmem_alloc(dsize, KM_SLEEP);
		fpp = ct->d_fpp;

		while (n--) {
			if (door_insert(*fpp, didpp) == -1) {
				/* Close remaining files */
				door_fp_close(fpp, n + 1);
				error = EMFILE;
				goto out;
			}
			fpp++; didpp++; ncopied++;
		}

		ct->d_args.desc_ptr = (door_desc_t *)(ct->d_args.rbuf +
		    roundup(ct->d_args.data_size, sizeof (door_desc_t)));

		if (copyout(start, ct->d_args.desc_ptr, dsize)) {
			error = EFAULT;
			goto out;
		}
	}

	/*
	 * Return the results
	 */
	if (datamodel == DATAMODEL_NATIVE) {
		if (copyout(&ct->d_args, args, sizeof (door_arg_t)) != 0)
			error = EFAULT;
	} else {
		door_arg32_t    da32;

		da32.data_ptr = (caddr32_t)(uintptr_t)ct->d_args.data_ptr;
		da32.data_size = ct->d_args.data_size;
		da32.desc_ptr = (caddr32_t)(uintptr_t)ct->d_args.desc_ptr;
		da32.desc_num = ct->d_args.desc_num;
		da32.rbuf = (caddr32_t)(uintptr_t)ct->d_args.rbuf;
		da32.rsize = ct->d_args.rsize;
		if (copyout(&da32, args, sizeof (door_arg32_t)) != 0) {
			error = EFAULT;
		}
	}

out:
	ct->d_noresults = 0;

	/* clean up the overflow buffer if an error occurred */
	if (error != 0 && ct->d_overflow) {
		(void) as_unmap(curproc->p_as, ct->d_args.rbuf,
		    ct->d_args.rsize);
	}
	ct->d_overflow = 0;

	/* call destructor */
	if (destfn) {
		ASSERT(ct->d_kernel);
		(*destfn)(dp->door_data, destarg);
		ct->d_buf = NULL;
		ct->d_bufsize = 0;
	}

	if (dp)
		releasef(did);

	if (ct->d_buf) {
		ASSERT(!ct->d_kernel);
		kmem_free(ct->d_buf, ct->d_bufsize);
		ct->d_buf = NULL;
		ct->d_bufsize = 0;
	}
	ct->d_kernel = 0;

	/* clean up the descriptor copyout buffer */
	if (start != NULL) {
		if (error != 0)
			door_fd_close(start, ncopied);
		kmem_free(start, dsize);
	}

	if (ct->d_fpp) {
		kmem_free(ct->d_fpp, ct->d_fpp_size);
		ct->d_fpp = NULL;
		ct->d_fpp_size = 0;
	}

	if (error)
		return (set_errno(error));

	return (0);
}

static int
door_setparam_common(door_node_t *dp, int from_kernel, int type, size_t val)
{
	int error = 0;

	mutex_enter(&door_knob);

	if (DOOR_INVALID(dp)) {
		mutex_exit(&door_knob);
		return (EBADF);
	}

	/*
	 * door_ki_setparam() can only affect kernel doors.
	 * door_setparam() can only affect doors attached to the current
	 * process.
	 */
	if ((from_kernel && dp->door_target != &p0) ||
	    (!from_kernel && dp->door_target != curproc)) {
		mutex_exit(&door_knob);
		return (EPERM);
	}

	switch (type) {
	case DOOR_PARAM_DESC_MAX:
		if (val > INT_MAX)
			error = ERANGE;
		else if ((dp->door_flags & DOOR_REFUSE_DESC) && val != 0)
			error = ENOTSUP;
		else
			dp->door_desc_max = (uint_t)val;
		break;

	case DOOR_PARAM_DATA_MIN:
		if (val > dp->door_data_max)
			error = EINVAL;
		else
			dp->door_data_min = val;
		break;

	case DOOR_PARAM_DATA_MAX:
		if (val < dp->door_data_min)
			error = EINVAL;
		else
			dp->door_data_max = val;
		break;

	default:
		error = EINVAL;
		break;
	}

	mutex_exit(&door_knob);
	return (error);
}

static int
door_getparam_common(door_node_t *dp, int type, size_t *out)
{
	int error = 0;

	mutex_enter(&door_knob);
	switch (type) {
	case DOOR_PARAM_DESC_MAX:
		*out = (size_t)dp->door_desc_max;
		break;
	case DOOR_PARAM_DATA_MIN:
		*out = dp->door_data_min;
		break;
	case DOOR_PARAM_DATA_MAX:
		*out = dp->door_data_max;
		break;
	default:
		error = EINVAL;
		break;
	}
	mutex_exit(&door_knob);
	return (error);
}

int
door_setparam(int did, int type, size_t val)
{
	door_node_t *dp;
	int error = 0;

	if ((dp = door_lookup(did, NULL)) == NULL)
		return (set_errno(EBADF));

	error = door_setparam_common(dp, 0, type, val);

	releasef(did);

	if (error)
		return (set_errno(error));

	return (0);
}

int
door_getparam(int did, int type, size_t *out)
{
	door_node_t *dp;
	size_t val = 0;
	int error = 0;

	if ((dp = door_lookup(did, NULL)) == NULL)
		return (set_errno(EBADF));

	error = door_getparam_common(dp, type, &val);

	releasef(did);

	if (error)
		return (set_errno(error));

	if (get_udatamodel() == DATAMODEL_NATIVE) {
		if (copyout(&val, out, sizeof (val)))
			return (set_errno(EFAULT));
#ifdef _SYSCALL32_IMPL
	} else {
		size32_t val32 = (size32_t)val;

		if (val != val32)
			return (set_errno(EOVERFLOW));

		if (copyout(&val32, out, sizeof (val32)))
			return (set_errno(EFAULT));
#endif /* _SYSCALL32_IMPL */
	}

	return (0);
}

/*
 * A copyout() which proceeds from high addresses to low addresses.  This way,
 * stack guard pages are effective.
 */
static int
door_stack_copyout(const void *kaddr, void *uaddr, size_t count)
{
	const char *kbase = (const char *)kaddr;
	uintptr_t ubase = (uintptr_t)uaddr;
	size_t pgsize = PAGESIZE;

	if (count <= pgsize)
		return (copyout(kaddr, uaddr, count));

	while (count > 0) {
		uintptr_t start, end, offset, amount;

		end = ubase + count;
		start = P2ALIGN(end - 1, pgsize);
		if (P2ALIGN(ubase, pgsize) == start)
			start = ubase;

		offset = start - ubase;
		amount = end - start;

		ASSERT(amount > 0 && amount <= count && amount <= pgsize);

		if (copyout(kbase + offset, (void *)start, amount))
			return (1);
		count -= amount;
	}
	return (0);
}

/*
 * Writes the stack layout for door_return() into the door_server_t of the
 * server thread.
 */
static int
door_layout(kthread_t *tp, size_t data_size, uint_t ndesc, int info_needed)
{
	door_server_t *st = DOOR_SERVER(tp->t_door);
	door_layout_t *out = &st->d_layout;
	uintptr_t base_sp = (uintptr_t)st->d_sp;
	size_t ssize = st->d_ssize;
	size_t descsz;
	uintptr_t descp, datap, infop, resultsp, finalsp;
	size_t align = STACK_ALIGN;
	size_t results_sz = sizeof (struct door_results);
	model_t datamodel = lwp_getdatamodel(ttolwp(tp));

	ASSERT(!st->d_layout_done);

#ifndef _STACK_GROWS_DOWNWARD
#error stack does not grow downward, door_layout() must change
#endif

#ifdef _SYSCALL32_IMPL
	if (datamodel != DATAMODEL_NATIVE) {
		align = STACK_ALIGN32;
		results_sz = sizeof (struct door_results32);
	}
#endif

	descsz = ndesc * sizeof (door_desc_t);

	/*
	 * To speed up the overflow checking, we do an initial check
	 * that the passed in data size won't cause us to wrap past
	 * base_sp.  Since door_max_desc limits descsz, we can
	 * safely use it here.  65535 is an arbitrary 'bigger than
	 * we need, small enough to not cause trouble' constant;
	 * the only constraint is that it must be > than:
	 *
	 *	5 * STACK_ALIGN +
	 *	    sizeof (door_info_t) +
	 *	    sizeof (door_results_t) +
	 *	    (max adjustment from door_final_sp())
	 *
	 * After we compute the layout, we can safely do a "did we wrap
	 * around" check, followed by a check against the recorded
	 * stack size.
	 */
	if (data_size >= SIZE_MAX - (size_t)65535UL - descsz)
		return (E2BIG);		/* overflow */

	descp = P2ALIGN(base_sp - descsz, align);
	datap = P2ALIGN(descp - data_size, align);

	if (info_needed)
		infop = P2ALIGN(datap - sizeof (door_info_t), align);
	else
		infop = datap;

	resultsp = P2ALIGN(infop - results_sz, align);
	finalsp = door_final_sp(resultsp, align, datamodel);

	if (finalsp > base_sp)
		return (E2BIG);		/* overflow */

	if (ssize != 0 && (base_sp - finalsp) > ssize)
		return (E2BIG);		/* doesn't fit in stack */

	out->dl_descp = (ndesc != 0)? (caddr_t)descp : 0;
	out->dl_datap = (data_size != 0)? (caddr_t)datap : 0;
	out->dl_infop = info_needed? (caddr_t)infop : 0;
	out->dl_resultsp = (caddr_t)resultsp;
	out->dl_sp = (caddr_t)finalsp;

	st->d_layout_done = 1;
	return (0);
}

static int
door_server_dispatch(door_client_t *ct, door_node_t *dp)
{
	door_server_t *st = DOOR_SERVER(curthread->t_door);
	door_layout_t *layout = &st->d_layout;
	int error = 0;

	int is_private = (dp->door_flags & DOOR_PRIVATE);

	door_pool_t *pool = (is_private)? &dp->door_servers :
	    &curproc->p_server_threads;

	int empty_pool = (pool->dp_threads == NULL);

	caddr_t infop = NULL;
	char *datap = NULL;
	size_t datasize = 0;
	size_t descsize;

	file_t **fpp = ct->d_fpp;
	door_desc_t *start = NULL;
	uint_t ndesc = 0;
	uint_t ncopied = 0;

	if (ct != NULL) {
		datap = ct->d_args.data_ptr;
		datasize = ct->d_args.data_size;
		ndesc = ct->d_args.desc_num;
	}

	descsize = ndesc * sizeof (door_desc_t);

	/*
	 * Reset datap to NULL if we aren't passing any data.  Be careful
	 * to let unref notifications through, though.
	 */
	if (datap == DOOR_UNREF_DATA) {
		if (ct->d_upcall)
			datasize = 0;
		else
			datap = NULL;
	} else if (datasize == 0) {
		datap = NULL;
	}

	/*
	 * Get the stack layout, if it hasn't already been done.
	 */
	if (!st->d_layout_done) {
		error = door_layout(curthread, datasize, ndesc,
		    (is_private && empty_pool));
		if (error != 0)
			goto fail;
	}

	/*
	 * fill out the stack, starting from the top.  Layout was already
	 * filled in by door_args() or door_translate_out().
	 */
	if (layout->dl_descp != NULL) {
		ASSERT(ndesc != 0);
		start = kmem_alloc(descsize, KM_SLEEP);

		while (ndesc > 0) {
			if (door_insert(*fpp, &start[ncopied]) == -1) {
				error = EMFILE;
				goto fail;
			}
			ndesc--;
			ncopied++;
			fpp++;
		}
		if (door_stack_copyout(start, layout->dl_descp, descsize)) {
			error = E2BIG;
			goto fail;
		}
	}
	fpp = NULL;			/* finished processing */

	if (layout->dl_datap != NULL) {
		ASSERT(datasize != 0);
		datap = layout->dl_datap;
		if (ct->d_upcall || datasize <= door_max_arg) {
			if (door_stack_copyout(ct->d_buf, datap, datasize)) {
				error = E2BIG;
				goto fail;
			}
		}
	}

	if (is_private && empty_pool) {
		door_info_t di;

		infop = layout->dl_infop;
		ASSERT(infop != NULL);

		di.di_target = curproc->p_pid;
		di.di_proc = (door_ptr_t)(uintptr_t)dp->door_pc;
		di.di_data = (door_ptr_t)(uintptr_t)dp->door_data;
		di.di_uniquifier = dp->door_index;
		di.di_attributes = (dp->door_flags & DOOR_ATTR_MASK) |
		    DOOR_LOCAL;

		if (copyout(&di, infop, sizeof (di))) {
			error = E2BIG;
			goto fail;
		}
	}

	if (get_udatamodel() == DATAMODEL_NATIVE) {
		struct door_results dr;

		dr.cookie = dp->door_data;
		dr.data_ptr = datap;
		dr.data_size = datasize;
		dr.desc_ptr = (door_desc_t *)layout->dl_descp;
		dr.desc_num = ncopied;
		dr.pc = dp->door_pc;
		dr.nservers = !empty_pool;
		dr.door_info = (door_info_t *)infop;

		if (copyout(&dr, layout->dl_resultsp, sizeof (dr))) {
			error = E2BIG;
			goto fail;
		}
#ifdef _SYSCALL32_IMPL
	} else {
		struct door_results32 dr32;

		dr32.cookie = (caddr32_t)(uintptr_t)dp->door_data;
		dr32.data_ptr = (caddr32_t)(uintptr_t)datap;
		dr32.data_size = (size32_t)datasize;
		dr32.desc_ptr = (caddr32_t)(uintptr_t)layout->dl_descp;
		dr32.desc_num = ncopied;
		dr32.pc = (caddr32_t)(uintptr_t)dp->door_pc;
		dr32.nservers = !empty_pool;
		dr32.door_info = (caddr32_t)(uintptr_t)infop;

		if (copyout(&dr32, layout->dl_resultsp, sizeof (dr32))) {
			error = E2BIG;
			goto fail;
		}
#endif
	}

	error = door_finish_dispatch(layout->dl_sp);
fail:
	if (start != NULL) {
		if (error != 0)
			door_fd_close(start, ncopied);
		kmem_free(start, descsize);
	}
	if (fpp != NULL)
		door_fp_close(fpp, ndesc);

	return (error);
}

/*
 * Return the results (if any) to the caller (if any) and wait for the
 * next invocation on a door.
 */
int
door_return(caddr_t data_ptr, size_t data_size,
    door_desc_t *desc_ptr, uint_t desc_num, caddr_t sp, size_t ssize)
{
	kthread_t	*caller;
	klwp_t		*lwp;
	int		error = 0;
	door_node_t	*dp;
	door_server_t	*st;		/* curthread door_data */
	door_client_t	*ct;		/* caller door_data */

	st = door_my_server(1);

	/*
	 * If thread was bound to a door that no longer exists, return
	 * an error.  This can happen if a thread is bound to a door
	 * before the process calls forkall(); in the child, the door
	 * doesn't exist and door_fork() sets the d_invbound flag.
	 */
	if (st->d_invbound)
		return (set_errno(EINVAL));

	st->d_sp = sp;			/* Save base of stack. */
	st->d_ssize = ssize;		/* and its size */

	/*
	 * before we release our stack to the whims of our next caller,
	 * copy in the syscall arguments if we're being traced by /proc.
	 */
	if (curthread->t_post_sys && PTOU(ttoproc(curthread))->u_systrap)
		(void) save_syscall_args();

	/* Make sure the caller hasn't gone away */
	mutex_enter(&door_knob);
	if ((caller = st->d_caller) == NULL || caller->t_door == NULL) {
		if (desc_num != 0) {
			/* close any DOOR_RELEASE descriptors */
			mutex_exit(&door_knob);
			error = door_release_fds(desc_ptr, desc_num);
			if (error)
				return (set_errno(error));
			mutex_enter(&door_knob);
		}
		goto out;
	}
	ct = DOOR_CLIENT(caller->t_door);

	ct->d_args.data_size = data_size;
	ct->d_args.desc_num = desc_num;
	/*
	 * Transfer results, if any, to the client
	 */
	if (data_size != 0 || desc_num != 0) {
		/*
		 * Prevent the client from exiting until we have finished
		 * moving results.
		 */
		DOOR_T_HOLD(ct);
		mutex_exit(&door_knob);
		error = door_results(caller, data_ptr, data_size,
		    desc_ptr, desc_num);
		mutex_enter(&door_knob);
		DOOR_T_RELEASE(ct);
		/*
		 * Pass EOVERFLOW errors back to the client
		 */
		if (error && error != EOVERFLOW) {
			mutex_exit(&door_knob);
			return (set_errno(error));
		}
	}
out:
	/* Put ourselves on the available server thread list */
	door_release_server(st->d_pool, curthread);

	/*
	 * Make sure the caller is still waiting to be resumed
	 */
	if (caller) {
		disp_lock_t *tlp;

		thread_lock(caller);
		ct->d_error = error;		/* Return any errors */
		if (caller->t_state == TS_SLEEP &&
		    SOBJ_TYPE(caller->t_sobj_ops) == SOBJ_SHUTTLE) {
			cpu_t *cp = CPU;

			tlp = caller->t_lockp;
			/*
			 * Setting t_disp_queue prevents erroneous preemptions
			 * if this thread is still in execution on another
			 * processor
			 */
			caller->t_disp_queue = cp->cpu_disp;
			CL_ACTIVE(caller);
			/*
			 * We are calling thread_onproc() instead of
			 * THREAD_ONPROC() because compiler can reorder
			 * the two stores of t_state and t_lockp in
			 * THREAD_ONPROC().
			 */
			thread_onproc(caller, cp);
			disp_lock_exit_high(tlp);
			shuttle_resume(caller, &door_knob);
		} else {
			/* May have been setrun or in stop state */
			thread_unlock(caller);
			shuttle_swtch(&door_knob);
		}
	} else {
		shuttle_swtch(&door_knob);
	}

	/*
	 * We've sprung to life. Determine if we are part of a door
	 * invocation, or just interrupted
	 */
	lwp = ttolwp(curthread);
	mutex_enter(&door_knob);
	if ((dp = st->d_active) != NULL) {
		/*
		 * Normal door invocation. Return any error condition
		 * encountered while trying to pass args to the server
		 * thread.
		 */
		lwp->lwp_asleep = 0;
		/*
		 * Prevent the caller from leaving us while we
		 * are copying out the arguments from it's buffer.
		 */
		ASSERT(st->d_caller != NULL);
		ct = DOOR_CLIENT(st->d_caller->t_door);

		DOOR_T_HOLD(ct);
		mutex_exit(&door_knob);
		error = door_server_dispatch(ct, dp);
		mutex_enter(&door_knob);
		DOOR_T_RELEASE(ct);

		if (error) {
			caller = st->d_caller;
			if (caller)
				ct = DOOR_CLIENT(caller->t_door);
			else
				ct = NULL;
			goto out;
		}
		mutex_exit(&door_knob);
		return (0);
	} else {
		/*
		 * We are not involved in a door_invocation.
		 * Check for /proc related activity...
		 */
		st->d_caller = NULL;
		door_server_exit(curproc, curthread);
		mutex_exit(&door_knob);
		if (ISSIG(curthread, FORREAL) ||
		    lwp->lwp_sysabort || MUSTRETURN(curproc, curthread)) {
			lwp->lwp_asleep = 0;
			lwp->lwp_sysabort = 0;
			return (set_errno(EINTR));
		}
		/* Go back and wait for another request */
		lwp->lwp_asleep = 0;
		mutex_enter(&door_knob);
		caller = NULL;
		goto out;
	}
}

/*
 * Revoke any future invocations on this door
 */
int
door_revoke(int did)
{
	door_node_t	*d;
	int		error;

	if ((d = door_lookup(did, NULL)) == NULL)
		return (set_errno(EBADF));

	mutex_enter(&door_knob);
	if (d->door_target != curproc) {
		mutex_exit(&door_knob);
		releasef(did);
		return (set_errno(EPERM));
	}
	d->door_flags |= DOOR_REVOKED;
	if (d->door_flags & DOOR_PRIVATE)
		cv_broadcast(&d->door_servers.dp_cv);
	else
		cv_broadcast(&curproc->p_server_threads.dp_cv);
	mutex_exit(&door_knob);
	releasef(did);
	/* Invalidate the descriptor */
	if ((error = closeandsetf(did, NULL)) != 0)
		return (set_errno(error));
	return (0);
}

int
door_info(int did, struct door_info *d_info)
{
	door_node_t	*dp;
	door_info_t	di;
	door_server_t	*st;
	file_t		*fp = NULL;

	if (did == DOOR_QUERY) {
		/* Get information on door current thread is bound to */
		if ((st = door_my_server(0)) == NULL ||
		    (dp = st->d_pool) == NULL)
			/* Thread isn't bound to a door */
			return (set_errno(EBADF));
	} else if ((dp = door_lookup(did, &fp)) == NULL) {
		/* Not a door */
		return (set_errno(EBADF));
	}

	door_info_common(dp, &di, fp);

	if (did != DOOR_QUERY)
		releasef(did);

	if (copyout(&di, d_info, sizeof (struct door_info)))
		return (set_errno(EFAULT));
	return (0);
}

/*
 * Common code for getting information about a door either via the
 * door_info system call or the door_ki_info kernel call.
 */
void
door_info_common(door_node_t *dp, struct door_info *dip, file_t *fp)
{
	int unref_count;

	bzero(dip, sizeof (door_info_t));

	mutex_enter(&door_knob);
	if (dp->door_target == NULL)
		dip->di_target = -1;
	else
		dip->di_target = dp->door_target->p_pid;

	dip->di_attributes = dp->door_flags & DOOR_ATTR_MASK;
	if (dp->door_target == curproc)
		dip->di_attributes |= DOOR_LOCAL;
	dip->di_proc = (door_ptr_t)(uintptr_t)dp->door_pc;
	dip->di_data = (door_ptr_t)(uintptr_t)dp->door_data;
	dip->di_uniquifier = dp->door_index;
	/*
	 * If this door is in the middle of having an unreferenced
	 * notification delivered, don't count the VN_HOLD by
	 * door_deliver_unref in determining if it is unreferenced.
	 * This handles the case where door_info is called from the
	 * thread delivering the unref notification.
	 */
	if (dp->door_flags & DOOR_UNREF_ACTIVE)
		unref_count = 2;
	else
		unref_count = 1;
	mutex_exit(&door_knob);

	if (fp == NULL) {
		/*
		 * If this thread is bound to the door, then we can just
		 * check the vnode; a ref count of 1 (or 2 if this is
		 * handling an unref notification) means that the hold
		 * from the door_bind is the only reference to the door
		 * (no file descriptor refers to it).
		 */
		if (DTOV(dp)->v_count == unref_count)
			dip->di_attributes |= DOOR_IS_UNREF;
	} else {
		/*
		 * If we're working from a file descriptor or door handle
		 * we need to look at the file structure count.  We don't
		 * need to hold the vnode lock since this is just a snapshot.
		 */
		mutex_enter(&fp->f_tlock);
		if (fp->f_count == 1 && DTOV(dp)->v_count == unref_count)
			dip->di_attributes |= DOOR_IS_UNREF;
		mutex_exit(&fp->f_tlock);
	}
}

/*
 * Return credentials of the door caller (if any) for this invocation
 */
int
door_ucred(struct ucred_s *uch)
{
	kthread_t	*caller;
	door_server_t	*st;
	door_client_t	*ct;
	struct proc	*p;
	struct ucred_s	*res;
	int		err;

	mutex_enter(&door_knob);
	if ((st = door_my_server(0)) == NULL ||
	    (caller = st->d_caller) == NULL) {
		mutex_exit(&door_knob);
		return (set_errno(EINVAL));
	}

	ASSERT(caller->t_door != NULL);
	ct = DOOR_CLIENT(caller->t_door);

	/* Prevent caller from exiting while we examine the cred */
	DOOR_T_HOLD(ct);
	mutex_exit(&door_knob);

	/* Get the credentials of the calling process */
	p = ttoproc(caller);

	res = pgetucred(p);

	mutex_enter(&door_knob);
	DOOR_T_RELEASE(ct);
	mutex_exit(&door_knob);

	err = copyout(res, uch, res->uc_size);

	kmem_free(res, res->uc_size);

	if (err != 0)
		return (set_errno(EFAULT));

	return (0);
}

/*
 * Bind the current lwp to the server thread pool associated with 'did'
 */
int
door_bind(int did)
{
	door_node_t	*dp;
	door_server_t	*st;

	if ((dp = door_lookup(did, NULL)) == NULL) {
		/* Not a door */
		return (set_errno(EBADF));
	}

	/*
	 * Can't bind to a non-private door, and can't bind to a door
	 * served by another process.
	 */
	if ((dp->door_flags & DOOR_PRIVATE) == 0 ||
	    dp->door_target != curproc) {
		releasef(did);
		return (set_errno(EINVAL));
	}

	st = door_my_server(1);
	if (st->d_pool)
		door_unbind_thread(st->d_pool);
	st->d_pool = dp;
	st->d_invbound = 0;
	door_bind_thread(dp);
	releasef(did);

	return (0);
}

/*
 * Unbind the current lwp from it's server thread pool
 */
int
door_unbind(void)
{
	door_server_t *st;

	if ((st = door_my_server(0)) == NULL)
		return (set_errno(EBADF));

	if (st->d_invbound) {
		ASSERT(st->d_pool == NULL);
		st->d_invbound = 0;
		return (0);
	}
	if (st->d_pool == NULL)
		return (set_errno(EBADF));
	door_unbind_thread(st->d_pool);
	st->d_pool = NULL;
	return (0);
}

/*
 * Create a descriptor for the associated file and fill in the
 * attributes associated with it.
 *
 * Return 0 for success, -1 otherwise;
 */
int
door_insert(struct file *fp, door_desc_t *dp)
{
	struct vnode *vp;
	int	fd;
	door_attr_t attributes = DOOR_DESCRIPTOR;

	ASSERT(MUTEX_NOT_HELD(&door_knob));
	if ((fd = ufalloc(0)) == -1)
		return (-1);
	setf(fd, fp);
	dp->d_data.d_desc.d_descriptor = fd;

	/* Fill in the attributes */
	if (VOP_REALVP(fp->f_vnode, &vp))
		vp = fp->f_vnode;
	if (vp && vp->v_type == VDOOR) {
		if (VTOD(vp)->door_target == curproc)
			attributes |= DOOR_LOCAL;
		attributes |= VTOD(vp)->door_flags & DOOR_ATTR_MASK;
		dp->d_data.d_desc.d_id = VTOD(vp)->door_index;
	}
	dp->d_attributes = attributes;
	return (0);
}

/*
 * Return an available thread for this server.  A NULL return value indicates
 * that either:
 *	The door has been revoked, or
 *	a signal was received.
 * The two conditions can be differentiated using DOOR_INVALID(dp).
 */
static kthread_t *
door_get_server(door_node_t *dp)
{
	kthread_t **ktp;
	kthread_t *server_t;
	door_pool_t *pool;
	door_server_t *st;
	int signalled;

	disp_lock_t *tlp;
	cpu_t *cp;

	ASSERT(MUTEX_HELD(&door_knob));

	if (dp->door_flags & DOOR_PRIVATE)
		pool = &dp->door_servers;
	else
		pool = &dp->door_target->p_server_threads;

	for (;;) {
		/*
		 * We search the thread pool, looking for a server thread
		 * ready to take an invocation (i.e. one which is still
		 * sleeping on a shuttle object).  If none are available,
		 * we sleep on the pool's CV, and will be signaled when a
		 * thread is added to the pool.
		 *
		 * This relies on the fact that once a thread in the thread
		 * pool wakes up, it *must* remove and add itself to the pool
		 * before it can receive door calls.
		 */
		if (DOOR_INVALID(dp))
			return (NULL);	/* Target has become invalid */

		for (ktp = &pool->dp_threads;
		    (server_t = *ktp) != NULL;
		    ktp = &st->d_servers) {
			st = DOOR_SERVER(server_t->t_door);

			thread_lock(server_t);
			if (server_t->t_state == TS_SLEEP &&
			    SOBJ_TYPE(server_t->t_sobj_ops) == SOBJ_SHUTTLE)
				break;
			thread_unlock(server_t);
		}
		if (server_t != NULL)
			break;		/* we've got a live one! */

		if (!cv_wait_sig_swap_core(&pool->dp_cv, &door_knob,
		    &signalled)) {
			/*
			 * If we were signalled and the door is still
			 * valid, pass the signal on to another waiter.
			 */
			if (signalled && !DOOR_INVALID(dp))
				cv_signal(&pool->dp_cv);
			return (NULL);	/* Got a signal */
		}
	}

	/*
	 * We've got a thread_lock()ed thread which is still on the
	 * shuttle.  Take it off the list of available server threads
	 * and mark it as ONPROC.  We are committed to resuming this
	 * thread now.
	 */
	tlp = server_t->t_lockp;
	cp = CPU;

	*ktp = st->d_servers;
	st->d_servers = NULL;
	/*
	 * Setting t_disp_queue prevents erroneous preemptions
	 * if this thread is still in execution on another processor
	 */
	server_t->t_disp_queue = cp->cpu_disp;
	CL_ACTIVE(server_t);
	/*
	 * We are calling thread_onproc() instead of
	 * THREAD_ONPROC() because compiler can reorder
	 * the two stores of t_state and t_lockp in
	 * THREAD_ONPROC().
	 */
	thread_onproc(server_t, cp);
	disp_lock_exit(tlp);
	return (server_t);
}

/*
 * Put a server thread back in the pool.
 */
static void
door_release_server(door_node_t *dp, kthread_t *t)
{
	door_server_t *st = DOOR_SERVER(t->t_door);
	door_pool_t *pool;

	ASSERT(MUTEX_HELD(&door_knob));
	st->d_active = NULL;
	st->d_caller = NULL;
	st->d_layout_done = 0;
	if (dp && (dp->door_flags & DOOR_PRIVATE)) {
		ASSERT(dp->door_target == NULL ||
		    dp->door_target == ttoproc(t));
		pool = &dp->door_servers;
	} else {
		pool = &ttoproc(t)->p_server_threads;
	}

	st->d_servers = pool->dp_threads;
	pool->dp_threads = t;

	/* If someone is waiting for a server thread, wake him up */
	cv_signal(&pool->dp_cv);
}

/*
 * Remove a server thread from the pool if present.
 */
static void
door_server_exit(proc_t *p, kthread_t *t)
{
	door_pool_t *pool;
	kthread_t **next;
	door_server_t *st = DOOR_SERVER(t->t_door);

	ASSERT(MUTEX_HELD(&door_knob));
	if (st->d_pool != NULL) {
		ASSERT(st->d_pool->door_flags & DOOR_PRIVATE);
		pool = &st->d_pool->door_servers;
	} else {
		pool = &p->p_server_threads;
	}

	next = &pool->dp_threads;
	while (*next != NULL) {
		if (*next == t) {
			*next = DOOR_SERVER(t->t_door)->d_servers;
			return;
		}
		next = &(DOOR_SERVER((*next)->t_door)->d_servers);
	}
}

/*
 * Lookup the door descriptor. Caller must call releasef when finished
 * with associated door.
 */
static door_node_t *
door_lookup(int did, file_t **fpp)
{
	vnode_t	*vp;
	file_t *fp;

	ASSERT(MUTEX_NOT_HELD(&door_knob));
	if ((fp = getf(did)) == NULL)
		return (NULL);
	/*
	 * Use the underlying vnode (we may be namefs mounted)
	 */
	if (VOP_REALVP(fp->f_vnode, &vp))
		vp = fp->f_vnode;

	if (vp == NULL || vp->v_type != VDOOR) {
		releasef(did);
		return (NULL);
	}

	if (fpp)
		*fpp = fp;

	return (VTOD(vp));
}

/*
 * The current thread is exiting, so clean up any pending
 * invocation details
 */
void
door_slam(void)
{
	door_node_t *dp;
	door_data_t *dt;
	door_client_t *ct;
	door_server_t *st;

	/*
	 * If we are an active door server, notify our
	 * client that we are exiting and revoke our door.
	 */
	if ((dt = door_my_data(0)) == NULL)
		return;
	ct = DOOR_CLIENT(dt);
	st = DOOR_SERVER(dt);

	mutex_enter(&door_knob);
	for (;;) {
		if (DOOR_T_HELD(ct))
			cv_wait(&ct->d_cv, &door_knob);
		else if (DOOR_T_HELD(st))
			cv_wait(&st->d_cv, &door_knob);
		else
			break;			/* neither flag is set */
	}
	curthread->t_door = NULL;
	if ((dp = st->d_active) != NULL) {
		kthread_t *t = st->d_caller;
		proc_t *p = curproc;

		/* Revoke our door if the process is exiting */
		if (dp->door_target == p && (p->p_flag & SEXITING)) {
			door_list_delete(dp);
			dp->door_target = NULL;
			dp->door_flags |= DOOR_REVOKED;
			if (dp->door_flags & DOOR_PRIVATE)
				cv_broadcast(&dp->door_servers.dp_cv);
			else
				cv_broadcast(&p->p_server_threads.dp_cv);
		}

		if (t != NULL) {
			/*
			 * Let the caller know we are gone
			 */
			DOOR_CLIENT(t->t_door)->d_error = DOOR_EXIT;
			thread_lock(t);
			if (t->t_state == TS_SLEEP &&
			    SOBJ_TYPE(t->t_sobj_ops) == SOBJ_SHUTTLE)
				setrun_locked(t);
			thread_unlock(t);
		}
	}
	mutex_exit(&door_knob);
	if (st->d_pool)
		door_unbind_thread(st->d_pool);	/* Implicit door_unbind */
	kmem_free(dt, sizeof (door_data_t));
}

/*
 * Set DOOR_REVOKED for all doors of the current process. This is called
 * on exit before all lwp's are being terminated so that door calls will
 * return with an error.
 */
void
door_revoke_all()
{
	door_node_t *dp;
	proc_t *p = ttoproc(curthread);

	mutex_enter(&door_knob);
	for (dp = p->p_door_list; dp != NULL; dp = dp->door_list) {
		ASSERT(dp->door_target == p);
		dp->door_flags |= DOOR_REVOKED;
		if (dp->door_flags & DOOR_PRIVATE)
			cv_broadcast(&dp->door_servers.dp_cv);
	}
	cv_broadcast(&p->p_server_threads.dp_cv);
	mutex_exit(&door_knob);
}

/*
 * The process is exiting, and all doors it created need to be revoked.
 */
void
door_exit(void)
{
	door_node_t *dp;
	proc_t *p = ttoproc(curthread);

	ASSERT(p->p_lwpcnt == 1);
	/*
	 * Walk the list of active doors created by this process and
	 * revoke them all.
	 */
	mutex_enter(&door_knob);
	for (dp = p->p_door_list; dp != NULL; dp = dp->door_list) {
		dp->door_target = NULL;
		dp->door_flags |= DOOR_REVOKED;
		if (dp->door_flags & DOOR_PRIVATE)
			cv_broadcast(&dp->door_servers.dp_cv);
	}
	cv_broadcast(&p->p_server_threads.dp_cv);
	/* Clear the list */
	p->p_door_list = NULL;

	/* Clean up the unref list */
	while ((dp = p->p_unref_list) != NULL) {
		p->p_unref_list = dp->door_ulist;
		dp->door_ulist = NULL;
		mutex_exit(&door_knob);
		VN_RELE(DTOV(dp));
		mutex_enter(&door_knob);
	}
	mutex_exit(&door_knob);
}


/*
 * The process is executing forkall(), and we need to flag threads that
 * are bound to a door in the child.  This will make the child threads
 * return an error to door_return unless they call door_unbind first.
 */
void
door_fork(kthread_t *parent, kthread_t *child)
{
	door_data_t *pt = parent->t_door;
	door_server_t *st = DOOR_SERVER(pt);
	door_data_t *dt;

	ASSERT(MUTEX_NOT_HELD(&door_knob));
	if (pt != NULL && (st->d_pool != NULL || st->d_invbound)) {
		/* parent thread is bound to a door */
		dt = child->t_door =
		    kmem_zalloc(sizeof (door_data_t), KM_SLEEP);
		DOOR_SERVER(dt)->d_invbound = 1;
	}
}

/*
 * Deliver queued unrefs to appropriate door server.
 */
static int
door_unref(void)
{
	door_node_t	*dp;
	static door_arg_t unref_args = { DOOR_UNREF_DATA, 0, 0, 0, 0, 0 };
	proc_t *p = ttoproc(curthread);

	/* make sure there's only one unref thread per process */
	mutex_enter(&door_knob);
	if (p->p_unref_thread) {
		mutex_exit(&door_knob);
		return (set_errno(EALREADY));
	}
	p->p_unref_thread = 1;
	mutex_exit(&door_knob);

	(void) door_my_data(1);			/* create info, if necessary */

	for (;;) {
		mutex_enter(&door_knob);

		/* Grab a queued request */
		while ((dp = p->p_unref_list) == NULL) {
			if (!cv_wait_sig(&p->p_unref_cv, &door_knob)) {
				/*
				 * Interrupted.
				 * Return so we can finish forkall() or exit().
				 */
				p->p_unref_thread = 0;
				mutex_exit(&door_knob);
				return (set_errno(EINTR));
			}
		}
		p->p_unref_list = dp->door_ulist;
		dp->door_ulist = NULL;
		dp->door_flags |= DOOR_UNREF_ACTIVE;
		mutex_exit(&door_knob);

		(void) door_upcall(DTOV(dp), &unref_args);

		mutex_enter(&door_knob);
		ASSERT(dp->door_flags & DOOR_UNREF_ACTIVE);
		dp->door_flags &= ~DOOR_UNREF_ACTIVE;
		mutex_exit(&door_knob);
		VN_RELE(DTOV(dp));
	}
}


/*
 * Deliver queued unrefs to kernel door server.
 */
/* ARGSUSED */
static void
door_unref_kernel(caddr_t arg)
{
	door_node_t	*dp;
	static door_arg_t unref_args = { DOOR_UNREF_DATA, 0, 0, 0, 0, 0 };
	proc_t *p = ttoproc(curthread);
	callb_cpr_t cprinfo;

	/* should only be one of these */
	mutex_enter(&door_knob);
	if (p->p_unref_thread) {
		mutex_exit(&door_knob);
		return;
	}
	p->p_unref_thread = 1;
	mutex_exit(&door_knob);

	(void) door_my_data(1);		/* make sure we have a door_data_t */

	CALLB_CPR_INIT(&cprinfo, &door_knob, callb_generic_cpr, "door_unref");
	for (;;) {
		mutex_enter(&door_knob);
		/* Grab a queued request */
		while ((dp = p->p_unref_list) == NULL) {
			CALLB_CPR_SAFE_BEGIN(&cprinfo);
			cv_wait(&p->p_unref_cv, &door_knob);
			CALLB_CPR_SAFE_END(&cprinfo, &door_knob);
		}
		p->p_unref_list = dp->door_ulist;
		dp->door_ulist = NULL;
		dp->door_flags |= DOOR_UNREF_ACTIVE;
		mutex_exit(&door_knob);

		(*(dp->door_pc))(dp->door_data, &unref_args, NULL, NULL, NULL);

		mutex_enter(&door_knob);
		ASSERT(dp->door_flags & DOOR_UNREF_ACTIVE);
		dp->door_flags &= ~DOOR_UNREF_ACTIVE;
		mutex_exit(&door_knob);
		VN_RELE(DTOV(dp));
	}
}


/*
 * Queue an unref invocation for processing for the current process
 * The door may or may not be revoked at this point.
 */
void
door_deliver_unref(door_node_t *d)
{
	struct proc *server = d->door_target;

	ASSERT(MUTEX_HELD(&door_knob));
	ASSERT(d->door_active == 0);

	if (server == NULL)
		return;
	/*
	 * Create a lwp to deliver unref calls if one isn't already running.
	 *
	 * A separate thread is used to deliver unrefs since the current
	 * thread may be holding resources (e.g. locks) in user land that
	 * may be needed by the unref processing. This would cause a
	 * deadlock.
	 */
	if (d->door_flags & DOOR_UNREF_MULTI) {
		/* multiple unrefs */
		d->door_flags &= ~DOOR_DELAY;
	} else {
		/* Only 1 unref per door */
		d->door_flags &= ~(DOOR_UNREF|DOOR_DELAY);
	}
	mutex_exit(&door_knob);

	/*
	 * Need to bump the vnode count before putting the door on the
	 * list so it doesn't get prematurely released by door_unref.
	 */
	VN_HOLD(DTOV(d));

	mutex_enter(&door_knob);
	/* is this door already on the unref list? */
	if (d->door_flags & DOOR_UNREF_MULTI) {
		door_node_t *dp;
		for (dp = server->p_unref_list; dp != NULL;
		    dp = dp->door_ulist) {
			if (d == dp) {
				/* already there, don't need to add another */
				mutex_exit(&door_knob);
				VN_RELE(DTOV(d));
				mutex_enter(&door_knob);
				return;
			}
		}
	}
	ASSERT(d->door_ulist == NULL);
	d->door_ulist = server->p_unref_list;
	server->p_unref_list = d;
	cv_broadcast(&server->p_unref_cv);
}

/*
 * The callers buffer isn't big enough for all of the data/fd's. Allocate
 * space in the callers address space for the results and copy the data
 * there.
 *
 * For EOVERFLOW, we must clean up the server's door descriptors.
 */
static int
door_overflow(
	kthread_t	*caller,
	caddr_t		data_ptr,	/* data location */
	size_t		data_size,	/* data size */
	door_desc_t	*desc_ptr,	/* descriptor location */
	uint_t		desc_num)	/* descriptor size */
{
	proc_t *callerp = ttoproc(caller);
	struct as *as = callerp->p_as;
	door_client_t *ct = DOOR_CLIENT(caller->t_door);
	caddr_t	addr;			/* Resulting address in target */
	size_t	rlen;			/* Rounded len */
	size_t	len;
	uint_t	i;
	size_t	ds = desc_num * sizeof (door_desc_t);

	ASSERT(MUTEX_NOT_HELD(&door_knob));
	ASSERT(DOOR_T_HELD(ct) || ct->d_kernel);

	/* Do initial overflow check */
	if (!ufcanalloc(callerp, desc_num))
		return (EMFILE);

	/*
	 * Allocate space for this stuff in the callers address space
	 */
	rlen = roundup(data_size + ds, PAGESIZE);
	as_rangelock(as);
	map_addr_proc(&addr, rlen, 0, 1, as->a_userlimit, ttoproc(caller), 0);
	if (addr == NULL ||
	    as_map(as, addr, rlen, segvn_create, zfod_argsp) != 0) {
		/* No virtual memory available, or anon mapping failed */
		as_rangeunlock(as);
		if (!ct->d_kernel && desc_num > 0) {
			int error = door_release_fds(desc_ptr, desc_num);
			if (error)
				return (error);
		}
		return (EOVERFLOW);
	}
	as_rangeunlock(as);

	if (ct->d_kernel)
		goto out;

	if (data_size != 0) {
		caddr_t	src = data_ptr;
		caddr_t saddr = addr;

		/* Copy any data */
		len = data_size;
		while (len != 0) {
			int	amount;
			int	error;

			amount = len > PAGESIZE ? PAGESIZE : len;
			if ((error = door_copy(as, src, saddr, amount)) != 0) {
				(void) as_unmap(as, addr, rlen);
				return (error);
			}
			saddr += amount;
			src += amount;
			len -= amount;
		}
	}
	/* Copy any fd's */
	if (desc_num != 0) {
		door_desc_t	*didpp, *start;
		struct file	**fpp;
		int		fpp_size;

		start = didpp = kmem_alloc(ds, KM_SLEEP);
		if (copyin(desc_ptr, didpp, ds)) {
			kmem_free(start, ds);
			(void) as_unmap(as, addr, rlen);
			return (EFAULT);
		}

		fpp_size = desc_num * sizeof (struct file *);
		if (fpp_size > ct->d_fpp_size) {
			/* make more space */
			if (ct->d_fpp_size)
				kmem_free(ct->d_fpp, ct->d_fpp_size);
			ct->d_fpp_size = fpp_size;
			ct->d_fpp = kmem_alloc(ct->d_fpp_size, KM_SLEEP);
		}
		fpp = ct->d_fpp;

		for (i = 0; i < desc_num; i++) {
			struct file *fp;
			int fd = didpp->d_data.d_desc.d_descriptor;

			if (!(didpp->d_attributes & DOOR_DESCRIPTOR) ||
			    (fp = getf(fd)) == NULL) {
				/* close translated references */
				door_fp_close(ct->d_fpp, fpp - ct->d_fpp);
				/* close untranslated references */
				door_fd_rele(didpp, desc_num - i, 0);
				kmem_free(start, ds);
				(void) as_unmap(as, addr, rlen);
				return (EINVAL);
			}
			mutex_enter(&fp->f_tlock);
			fp->f_count++;
			mutex_exit(&fp->f_tlock);

			*fpp = fp;
			releasef(fd);

			if (didpp->d_attributes & DOOR_RELEASE) {
				/* release passed reference */
				(void) closeandsetf(fd, NULL);
			}

			fpp++; didpp++;
		}
		kmem_free(start, ds);
	}

out:
	ct->d_overflow = 1;
	ct->d_args.rbuf = addr;
	ct->d_args.rsize = rlen;
	return (0);
}

/*
 * Transfer arguments from the client to the server.
 */
static int
door_args(kthread_t *server, int is_private)
{
	door_server_t *st = DOOR_SERVER(server->t_door);
	door_client_t *ct = DOOR_CLIENT(curthread->t_door);
	uint_t	ndid;
	size_t	dsize;
	int	error;

	ASSERT(DOOR_T_HELD(st));
	ASSERT(MUTEX_NOT_HELD(&door_knob));

	ndid = ct->d_args.desc_num;
	if (ndid > door_max_desc)
		return (E2BIG);

	/*
	 * Get the stack layout, and fail now if it won't fit.
	 */
	error = door_layout(server, ct->d_args.data_size, ndid, is_private);
	if (error != 0)
		return (error);

	dsize = ndid * sizeof (door_desc_t);
	if (ct->d_args.data_size != 0) {
		if (ct->d_args.data_size <= door_max_arg) {
			/*
			 * Use a 2 copy method for small amounts of data
			 *
			 * Allocate a little more than we need for the
			 * args, in the hope that the results will fit
			 * without having to reallocate a buffer
			 */
			ASSERT(ct->d_buf == NULL);
			ct->d_bufsize = roundup(ct->d_args.data_size,
			    DOOR_ROUND);
			ct->d_buf = kmem_alloc(ct->d_bufsize, KM_SLEEP);
			if (copyin(ct->d_args.data_ptr,
			    ct->d_buf, ct->d_args.data_size) != 0) {
				kmem_free(ct->d_buf, ct->d_bufsize);
				ct->d_buf = NULL;
				ct->d_bufsize = 0;
				return (EFAULT);
			}
		} else {
			struct as	*as;
			caddr_t		src;
			caddr_t		dest;
			size_t		len = ct->d_args.data_size;
			uintptr_t	base;

			/*
			 * Use a 1 copy method
			 */
			as = ttoproc(server)->p_as;
			src = ct->d_args.data_ptr;

			dest = st->d_layout.dl_datap;
			base = (uintptr_t)dest;

			/*
			 * Copy data directly into server.  We proceed
			 * downward from the top of the stack, to mimic
			 * normal stack usage. This allows the guard page
			 * to stop us before we corrupt anything.
			 */
			while (len != 0) {
				uintptr_t start;
				uintptr_t end;
				uintptr_t offset;
				size_t	amount;

				/*
				 * Locate the next part to copy.
				 */
				end = base + len;
				start = P2ALIGN(end - 1, PAGESIZE);

				/*
				 * if we are on the final (first) page, fix
				 * up the start position.
				 */
				if (P2ALIGN(base, PAGESIZE) == start)
					start = base;

				offset = start - base;	/* the copy offset */
				amount = end - start;	/* # bytes to copy */

				ASSERT(amount > 0 && amount <= len &&
				    amount <= PAGESIZE);

				error = door_copy(as, src + offset,
				    dest + offset, amount);
				if (error != 0)
					return (error);
				len -= amount;
			}
		}
	}
	/*
	 * Copyin the door args and translate them into files
	 */
	if (ndid != 0) {
		door_desc_t	*didpp;
		door_desc_t	*start;
		struct file	**fpp;

		start = didpp = kmem_alloc(dsize, KM_SLEEP);

		if (copyin(ct->d_args.desc_ptr, didpp, dsize)) {
			kmem_free(start, dsize);
			return (EFAULT);
		}
		ct->d_fpp_size = ndid * sizeof (struct file *);
		ct->d_fpp = kmem_alloc(ct->d_fpp_size, KM_SLEEP);
		fpp = ct->d_fpp;
		while (ndid--) {
			struct file *fp;
			int fd = didpp->d_data.d_desc.d_descriptor;

			/* We only understand file descriptors as passed objs */
			if (!(didpp->d_attributes & DOOR_DESCRIPTOR) ||
			    (fp = getf(fd)) == NULL) {
				/* close translated references */
				door_fp_close(ct->d_fpp, fpp - ct->d_fpp);
				/* close untranslated references */
				door_fd_rele(didpp, ndid + 1, 0);
				kmem_free(start, dsize);
				kmem_free(ct->d_fpp, ct->d_fpp_size);
				ct->d_fpp = NULL;
				ct->d_fpp_size = 0;
				return (EINVAL);
			}
			/* Hold the fp */
			mutex_enter(&fp->f_tlock);
			fp->f_count++;
			mutex_exit(&fp->f_tlock);

			*fpp = fp;
			releasef(fd);

			if (didpp->d_attributes & DOOR_RELEASE) {
				/* release passed reference */
				(void) closeandsetf(fd, NULL);
			}

			fpp++; didpp++;
		}
		kmem_free(start, dsize);
	}
	return (0);
}

/*
 * Transfer arguments from a user client to a kernel server.  This copies in
 * descriptors and translates them into door handles.  It doesn't touch the
 * other data, letting the kernel server deal with that (to avoid needing
 * to copy the data twice).
 */
static int
door_translate_in(void)
{
	door_client_t *ct = DOOR_CLIENT(curthread->t_door);
	uint_t	ndid;

	ASSERT(MUTEX_NOT_HELD(&door_knob));
	ndid = ct->d_args.desc_num;
	if (ndid > door_max_desc)
		return (E2BIG);
	/*
	 * Copyin the door args and translate them into door handles.
	 */
	if (ndid != 0) {
		door_desc_t	*didpp;
		door_desc_t	*start;
		size_t		dsize = ndid * sizeof (door_desc_t);
		struct file	*fp;

		start = didpp = kmem_alloc(dsize, KM_SLEEP);

		if (copyin(ct->d_args.desc_ptr, didpp, dsize)) {
			kmem_free(start, dsize);
			return (EFAULT);
		}
		while (ndid--) {
			vnode_t	*vp;
			int fd = didpp->d_data.d_desc.d_descriptor;

			/*
			 * We only understand file descriptors as passed objs
			 */
			if ((didpp->d_attributes & DOOR_DESCRIPTOR) &&
			    (fp = getf(fd)) != NULL) {
				didpp->d_data.d_handle = FTODH(fp);
				/* Hold the door */
				door_ki_hold(didpp->d_data.d_handle);

				releasef(fd);

				if (didpp->d_attributes & DOOR_RELEASE) {
					/* release passed reference */
					(void) closeandsetf(fd, NULL);
				}

				if (VOP_REALVP(fp->f_vnode, &vp))
					vp = fp->f_vnode;

				/* Set attributes */
				didpp->d_attributes = DOOR_HANDLE |
				    (VTOD(vp)->door_flags & DOOR_ATTR_MASK);
			} else {
				/* close translated references */
				door_fd_close(start, didpp - start);
				/* close untranslated references */
				door_fd_rele(didpp, ndid + 1, 0);
				kmem_free(start, dsize);
				return (EINVAL);
			}
			didpp++;
		}
		ct->d_args.desc_ptr = start;
	}
	return (0);
}

/*
 * Translate door arguments from kernel to user.  This copies the passed
 * door handles.  It doesn't touch other data.  It is used by door_upcall,
 * and for data returned by a door_call to a kernel server.
 */
static int
door_translate_out(void)
{
	door_client_t *ct = DOOR_CLIENT(curthread->t_door);
	uint_t	ndid;

	ASSERT(MUTEX_NOT_HELD(&door_knob));
	ndid = ct->d_args.desc_num;
	if (ndid > door_max_desc) {
		door_fd_rele(ct->d_args.desc_ptr, ndid, 1);
		return (E2BIG);
	}
	/*
	 * Translate the door args into files
	 */
	if (ndid != 0) {
		door_desc_t	*didpp = ct->d_args.desc_ptr;
		struct file	**fpp;

		ct->d_fpp_size = ndid * sizeof (struct file *);
		fpp = ct->d_fpp = kmem_alloc(ct->d_fpp_size, KM_SLEEP);
		while (ndid--) {
			struct file *fp = NULL;
			int fd = -1;

			/*
			 * We understand file descriptors and door
			 * handles as passed objs.
			 */
			if (didpp->d_attributes & DOOR_DESCRIPTOR) {
				fd = didpp->d_data.d_desc.d_descriptor;
				fp = getf(fd);
			} else if (didpp->d_attributes & DOOR_HANDLE)
				fp = DHTOF(didpp->d_data.d_handle);
			if (fp != NULL) {
				/* Hold the fp */
				mutex_enter(&fp->f_tlock);
				fp->f_count++;
				mutex_exit(&fp->f_tlock);

				*fpp = fp;
				if (didpp->d_attributes & DOOR_DESCRIPTOR)
					releasef(fd);
				if (didpp->d_attributes & DOOR_RELEASE) {
					/* release passed reference */
					if (fd >= 0)
						(void) closeandsetf(fd, NULL);
					else
						(void) closef(fp);
				}
			} else {
				/* close translated references */
				door_fp_close(ct->d_fpp, fpp - ct->d_fpp);
				/* close untranslated references */
				door_fd_rele(didpp, ndid + 1, 1);
				kmem_free(ct->d_fpp, ct->d_fpp_size);
				ct->d_fpp = NULL;
				ct->d_fpp_size = 0;
				return (EINVAL);
			}
			fpp++; didpp++;
		}
	}
	return (0);
}

/*
 * Move the results from the server to the client
 */
static int
door_results(kthread_t *caller, caddr_t data_ptr, size_t data_size,
		door_desc_t *desc_ptr, uint_t desc_num)
{
	door_client_t	*ct = DOOR_CLIENT(caller->t_door);
	size_t		dsize;
	size_t		rlen;
	size_t		result_size;

	ASSERT(DOOR_T_HELD(ct));
	ASSERT(MUTEX_NOT_HELD(&door_knob));

	if (ct->d_noresults)
		return (E2BIG);		/* No results expected */

	if (desc_num > door_max_desc)
		return (E2BIG);		/* Too many descriptors */

	dsize = desc_num * sizeof (door_desc_t);
	/*
	 * Check if the results are bigger than the clients buffer
	 */
	if (dsize)
		rlen = roundup(data_size, sizeof (door_desc_t));
	else
		rlen = data_size;
	if ((result_size = rlen + dsize) == 0)
		return (0);

	if (ct->d_upcall) {
		/*
		 * Handle upcalls
		 */
		if (ct->d_args.rbuf == NULL || ct->d_args.rsize < result_size) {
			/*
			 * If there's no return buffer or the buffer is too
			 * small, allocate a new one.  The old buffer (if it
			 * exists) will be freed by the upcall client.
			 */
			if (result_size > door_max_upcall_reply)
				return (E2BIG);
			ct->d_args.rsize = result_size;
			ct->d_args.rbuf = kmem_alloc(result_size, KM_SLEEP);
		}
		ct->d_args.data_ptr = ct->d_args.rbuf;
		if (data_size != 0 &&
		    copyin(data_ptr, ct->d_args.data_ptr, data_size) != 0)
			return (EFAULT);
	} else if (result_size > ct->d_args.rsize) {
		return (door_overflow(caller, data_ptr, data_size,
		    desc_ptr, desc_num));
	} else if (data_size != 0) {
		if (data_size <= door_max_arg) {
			/*
			 * Use a 2 copy method for small amounts of data
			 */
			if (ct->d_buf == NULL) {
				ct->d_bufsize = data_size;
				ct->d_buf = kmem_alloc(ct->d_bufsize, KM_SLEEP);
			} else if (ct->d_bufsize < data_size) {
				kmem_free(ct->d_buf, ct->d_bufsize);
				ct->d_bufsize = data_size;
				ct->d_buf = kmem_alloc(ct->d_bufsize, KM_SLEEP);
			}
			if (copyin(data_ptr, ct->d_buf, data_size) != 0)
				return (EFAULT);
		} else {
			struct as *as = ttoproc(caller)->p_as;
			caddr_t	dest = ct->d_args.rbuf;
			caddr_t	src = data_ptr;
			size_t	len = data_size;

			/* Copy data directly into client */
			while (len != 0) {
				uint_t	amount;
				uint_t	max;
				uint_t	off;
				int	error;

				off = (uintptr_t)dest & PAGEOFFSET;
				if (off)
					max = PAGESIZE - off;
				else
					max = PAGESIZE;
				amount = len > max ? max : len;
				error = door_copy(as, src, dest, amount);
				if (error != 0)
					return (error);
				dest += amount;
				src += amount;
				len -= amount;
			}
		}
	}

	/*
	 * Copyin the returned door ids and translate them into door_node_t
	 */
	if (desc_num != 0) {
		door_desc_t *start;
		door_desc_t *didpp;
		struct file **fpp;
		size_t	fpp_size;
		uint_t	i;

		/* First, check if we would overflow client */
		if (!ufcanalloc(ttoproc(caller), desc_num))
			return (EMFILE);

		start = didpp = kmem_alloc(dsize, KM_SLEEP);
		if (copyin(desc_ptr, didpp, dsize)) {
			kmem_free(start, dsize);
			return (EFAULT);
		}
		fpp_size = desc_num * sizeof (struct file *);
		if (fpp_size > ct->d_fpp_size) {
			/* make more space */
			if (ct->d_fpp_size)
				kmem_free(ct->d_fpp, ct->d_fpp_size);
			ct->d_fpp_size = fpp_size;
			ct->d_fpp = kmem_alloc(fpp_size, KM_SLEEP);
		}
		fpp = ct->d_fpp;

		for (i = 0; i < desc_num; i++) {
			struct file *fp;
			int fd = didpp->d_data.d_desc.d_descriptor;

			/* Only understand file descriptor results */
			if (!(didpp->d_attributes & DOOR_DESCRIPTOR) ||
			    (fp = getf(fd)) == NULL) {
				/* close translated references */
				door_fp_close(ct->d_fpp, fpp - ct->d_fpp);
				/* close untranslated references */
				door_fd_rele(didpp, desc_num - i, 0);
				kmem_free(start, dsize);
				return (EINVAL);
			}

			mutex_enter(&fp->f_tlock);
			fp->f_count++;
			mutex_exit(&fp->f_tlock);

			*fpp = fp;
			releasef(fd);

			if (didpp->d_attributes & DOOR_RELEASE) {
				/* release passed reference */
				(void) closeandsetf(fd, NULL);
			}

			fpp++; didpp++;
		}
		kmem_free(start, dsize);
	}
	return (0);
}

/*
 * Close all the descriptors.
 */
static void
door_fd_close(door_desc_t *d, uint_t n)
{
	uint_t	i;

	ASSERT(MUTEX_NOT_HELD(&door_knob));
	for (i = 0; i < n; i++) {
		if (d->d_attributes & DOOR_DESCRIPTOR) {
			(void) closeandsetf(
			    d->d_data.d_desc.d_descriptor, NULL);
		} else if (d->d_attributes & DOOR_HANDLE) {
			door_ki_rele(d->d_data.d_handle);
		}
		d++;
	}
}

/*
 * Close descriptors that have the DOOR_RELEASE attribute set.
 */
void
door_fd_rele(door_desc_t *d, uint_t n, int from_kernel)
{
	uint_t	i;

	ASSERT(MUTEX_NOT_HELD(&door_knob));
	for (i = 0; i < n; i++) {
		if (d->d_attributes & DOOR_RELEASE) {
			if (d->d_attributes & DOOR_DESCRIPTOR) {
				(void) closeandsetf(
				    d->d_data.d_desc.d_descriptor, NULL);
			} else if (from_kernel &&
			    (d->d_attributes & DOOR_HANDLE)) {
				door_ki_rele(d->d_data.d_handle);
			}
		}
		d++;
	}
}

/*
 * Copy descriptors into the kernel so we can release any marked
 * DOOR_RELEASE.
 */
int
door_release_fds(door_desc_t *desc_ptr, uint_t ndesc)
{
	size_t dsize;
	door_desc_t *didpp;
	uint_t desc_num;

	ASSERT(MUTEX_NOT_HELD(&door_knob));
	ASSERT(ndesc != 0);

	desc_num = MIN(ndesc, door_max_desc);

	dsize = desc_num * sizeof (door_desc_t);
	didpp = kmem_alloc(dsize, KM_SLEEP);

	while (ndesc > 0) {
		uint_t count = MIN(ndesc, desc_num);

		if (copyin(desc_ptr, didpp, count * sizeof (door_desc_t))) {
			kmem_free(didpp, dsize);
			return (EFAULT);
		}
		door_fd_rele(didpp, count, 0);

		ndesc -= count;
		desc_ptr += count;
	}
	kmem_free(didpp, dsize);
	return (0);
}

/*
 * Decrement ref count on all the files passed
 */
static void
door_fp_close(struct file **fp, uint_t n)
{
	uint_t	i;

	ASSERT(MUTEX_NOT_HELD(&door_knob));

	for (i = 0; i < n; i++)
		(void) closef(fp[i]);
}

/*
 * Copy data from 'src' in current address space to 'dest' in 'as' for 'len'
 * bytes.
 *
 * Performs this using 1 mapin and 1 copy operation.
 *
 * We really should do more than 1 page at a time to improve
 * performance, but for now this is treated as an anomalous condition.
 */
static int
door_copy(struct as *as, caddr_t src, caddr_t dest, uint_t len)
{
	caddr_t	kaddr;
	caddr_t	rdest;
	uint_t	off;
	page_t	**pplist;
	page_t	*pp = NULL;
	int	error = 0;

	ASSERT(len <= PAGESIZE);
	off = (uintptr_t)dest & PAGEOFFSET;	/* offset within the page */
	rdest = (caddr_t)((uintptr_t)dest &
	    (uintptr_t)PAGEMASK);	/* Page boundary */
	ASSERT(off + len <= PAGESIZE);

	/*
	 * Lock down destination page.
	 */
	if (as_pagelock(as, &pplist, rdest, PAGESIZE, S_WRITE))
		return (E2BIG);
	/*
	 * Check if we have a shadow page list from as_pagelock. If not,
	 * we took the slow path and have to find our page struct the hard
	 * way.
	 */
	if (pplist == NULL) {
		pfn_t	pfnum;

		/* MMU mapping is already locked down */
		AS_LOCK_ENTER(as, &as->a_lock, RW_READER);
		pfnum = hat_getpfnum(as->a_hat, rdest);
		AS_LOCK_EXIT(as, &as->a_lock);

		/*
		 * TODO: The pfn step should not be necessary - need
		 * a hat_getpp() function.
		 */
		if (pf_is_memory(pfnum)) {
			pp = page_numtopp_nolock(pfnum);
			ASSERT(pp == NULL || PAGE_LOCKED(pp));
		} else
			pp = NULL;
		if (pp == NULL) {
			as_pageunlock(as, pplist, rdest, PAGESIZE, S_WRITE);
			return (E2BIG);
		}
	} else {
		pp = *pplist;
	}
	/*
	 * Map destination page into kernel address
	 */
	kaddr = (caddr_t)ppmapin(pp, PROT_READ | PROT_WRITE, (caddr_t)-1);

	/*
	 * Copy from src to dest
	 */
	if (copyin(src, kaddr + off, len) != 0)
		error = EFAULT;
	/*
	 * Unmap destination page from kernel
	 */
	ppmapout(kaddr);
	/*
	 * Unlock destination page
	 */
	as_pageunlock(as, pplist, rdest, PAGESIZE, S_WRITE);
	return (error);
}

/*
 * General kernel upcall using doors
 *	Returns 0 on success, errno for failures.
 *	Caller must have a hold on the door based vnode, and on any
 *	references passed in desc_ptr.  The references are released
 *	in the event of an error, and passed without duplication
 *	otherwise.  Note that param->rbuf must be 64-bit aligned in
 *	a 64-bit kernel, since it may be used to store door descriptors
 *	if they are returned by the server.
 */
int
door_upcall(vnode_t *vp, door_arg_t *param)
{
	/* Locals */
	door_node_t	*dp;
	kthread_t	*server_thread;
	int		error = 0;
	klwp_t		*lwp;
	door_client_t	*ct;		/* curthread door_data */
	door_server_t	*st;		/* server thread door_data */
	int		gotresults = 0;

	if (vp->v_type != VDOOR) {
		if (param->desc_num)
			door_fd_rele(param->desc_ptr, param->desc_num, 1);
		return (EINVAL);
	}

	lwp = ttolwp(curthread);
	ct = door_my_client(1);
	dp = VTOD(vp);	/* Convert to a door_node_t */

	mutex_enter(&door_knob);
	if (DOOR_INVALID(dp)) {
		mutex_exit(&door_knob);
		if (param->desc_num)
			door_fd_rele(param->desc_ptr, param->desc_num, 1);
		error = EBADF;
		goto out;
	}

	if (dp->door_target == &p0) {
		/* Can't do an upcall to a kernel server */
		mutex_exit(&door_knob);
		if (param->desc_num)
			door_fd_rele(param->desc_ptr, param->desc_num, 1);
		error = EINVAL;
		goto out;
	}

	error = door_check_limits(dp, param, 1);
	if (error != 0) {
		mutex_exit(&door_knob);
		if (param->desc_num)
			door_fd_rele(param->desc_ptr, param->desc_num, 1);
		goto out;
	}

	/*
	 * Get a server thread from the target domain
	 */
	if ((server_thread = door_get_server(dp)) == NULL) {
		if (DOOR_INVALID(dp))
			error = EBADF;
		else
			error = EAGAIN;
		mutex_exit(&door_knob);
		if (param->desc_num)
			door_fd_rele(param->desc_ptr, param->desc_num, 1);
		goto out;
	}

	st = DOOR_SERVER(server_thread->t_door);
	ct->d_buf = param->data_ptr;
	ct->d_bufsize = param->data_size;
	ct->d_args = *param;	/* structure assignment */

	if (ct->d_args.desc_num) {
		/*
		 * Move data from client to server
		 */
		DOOR_T_HOLD(st);
		mutex_exit(&door_knob);
		error = door_translate_out();
		mutex_enter(&door_knob);
		DOOR_T_RELEASE(st);
		if (error) {
			/*
			 * We're not going to resume this thread after all
			 */
			door_release_server(dp, server_thread);
			shuttle_sleep(server_thread);
			mutex_exit(&door_knob);
			goto out;
		}
	}

	ct->d_upcall = 1;
	if (param->rsize == 0)
		ct->d_noresults = 1;
	else
		ct->d_noresults = 0;

	dp->door_active++;

	ct->d_error = DOOR_WAIT;
	st->d_caller = curthread;
	st->d_active = dp;

	shuttle_resume(server_thread, &door_knob);

	mutex_enter(&door_knob);
shuttle_return:
	if ((error = ct->d_error) < 0) {	/* DOOR_WAIT or DOOR_EXIT */
		/*
		 * Premature wakeup. Find out why (stop, forkall, sig, exit ...)
		 */
		mutex_exit(&door_knob);		/* May block in ISSIG */
		if (lwp && (ISSIG(curthread, FORREAL) ||
		    lwp->lwp_sysabort || MUSTRETURN(curproc, curthread))) {
			/* Signal, forkall, ... */
			lwp->lwp_sysabort = 0;
			mutex_enter(&door_knob);
			error = EINTR;
			/*
			 * If the server has finished processing our call,
			 * or exited (calling door_slam()), then d_error
			 * will have changed.  If the server hasn't finished
			 * yet, d_error will still be DOOR_WAIT, and we
			 * let it know we are not interested in any
			 * results by sending a SIGCANCEL, unless the door
			 * is marked with DOOR_NO_CANCEL.
			 */
			if (ct->d_error == DOOR_WAIT &&
			    st->d_caller == curthread) {
				proc_t	*p = ttoproc(server_thread);

				st->d_active = NULL;
				st->d_caller = NULL;
				if (!(dp->door_flags & DOOR_NO_CANCEL)) {
					DOOR_T_HOLD(st);
					mutex_exit(&door_knob);

					mutex_enter(&p->p_lock);
					sigtoproc(p, server_thread, SIGCANCEL);
					mutex_exit(&p->p_lock);

					mutex_enter(&door_knob);
					DOOR_T_RELEASE(st);
				}
			}
		} else {
			/*
			 * Return from stop(), server exit...
			 *
			 * Note that the server could have done a
			 * door_return while the client was in stop state
			 * (ISSIG), in which case the error condition
			 * is updated by the server.
			 */
			mutex_enter(&door_knob);
			if (ct->d_error == DOOR_WAIT) {
				/* Still waiting for a reply */
				shuttle_swtch(&door_knob);
				mutex_enter(&door_knob);
				if (lwp)
					lwp->lwp_asleep = 0;
				goto	shuttle_return;
			} else if (ct->d_error == DOOR_EXIT) {
				/* Server exit */
				error = EINTR;
			} else {
				/* Server did a door_return during ISSIG */
				error = ct->d_error;
			}
		}
		/*
		 * Can't exit if the server is currently copying
		 * results for me
		 */
		while (DOOR_T_HELD(ct))
			cv_wait(&ct->d_cv, &door_knob);

		/*
		 * Find out if results were successfully copied.
		 */
		if (ct->d_error == 0)
			gotresults = 1;
	}
	if (lwp) {
		lwp->lwp_asleep = 0;		/* /proc */
		lwp->lwp_sysabort = 0;		/* /proc */
	}
	if (--dp->door_active == 0 && (dp->door_flags & DOOR_DELAY))
		door_deliver_unref(dp);
	mutex_exit(&door_knob);

	/*
	 * Translate returned doors (if any)
	 */

	if (ct->d_noresults)
		goto out;

	if (error) {
		/*
		 * If server returned results successfully, then we've
		 * been interrupted and may need to clean up.
		 */
		if (gotresults) {
			ASSERT(error == EINTR);
			door_fp_close(ct->d_fpp, ct->d_args.desc_num);
		}
		goto out;
	}

	if (ct->d_args.desc_num) {
		struct file	**fpp;
		door_desc_t	*didpp;
		vnode_t		*vp;
		uint_t		n = ct->d_args.desc_num;

		didpp = ct->d_args.desc_ptr = (door_desc_t *)(ct->d_args.rbuf +
		    roundup(ct->d_args.data_size, sizeof (door_desc_t)));
		fpp = ct->d_fpp;

		while (n--) {
			struct file *fp;

			fp = *fpp;
			if (VOP_REALVP(fp->f_vnode, &vp))
				vp = fp->f_vnode;

			didpp->d_attributes = DOOR_HANDLE |
			    (VTOD(vp)->door_flags & DOOR_ATTR_MASK);
			didpp->d_data.d_handle = FTODH(fp);

			fpp++; didpp++;
		}
	}

	/* on return data is in rbuf */
	*param = ct->d_args;		/* structure assignment */

out:
	if (ct->d_fpp) {
		kmem_free(ct->d_fpp, ct->d_fpp_size);
		ct->d_fpp = NULL;
		ct->d_fpp_size = 0;
	}

	ct->d_upcall = 0;
	ct->d_noresults = 0;
	ct->d_buf = NULL;
	ct->d_bufsize = 0;
	return (error);
}

/*
 * Add a door to the per-process list of active doors for which the
 * process is a server.
 */
static void
door_list_insert(door_node_t *dp)
{
	proc_t *p = dp->door_target;

	ASSERT(MUTEX_HELD(&door_knob));
	dp->door_list = p->p_door_list;
	p->p_door_list = dp;
}

/*
 * Remove a door from the per-process list of active doors.
 */
void
door_list_delete(door_node_t *dp)
{
	door_node_t **pp;

	ASSERT(MUTEX_HELD(&door_knob));
	/*
	 * Find the door in the list.  If the door belongs to another process,
	 * it's OK to use p_door_list since that process can't exit until all
	 * doors have been taken off the list (see door_exit).
	 */
	pp = &(dp->door_target->p_door_list);
	while (*pp != dp)
		pp = &((*pp)->door_list);

	/* found it, take it off the list */
	*pp = dp->door_list;
}


/*
 * External kernel interfaces for doors.  These functions are available
 * outside the doorfs module for use in creating and using doors from
 * within the kernel.
 */

/*
 * door_ki_upcall invokes a user-level door server from the kernel.
 */
int
door_ki_upcall(door_handle_t dh, door_arg_t *param)
{
	file_t *fp = DHTOF(dh);
	vnode_t *realvp;

	if (VOP_REALVP(fp->f_vnode, &realvp))
		realvp = fp->f_vnode;
	return (door_upcall(realvp, param));
}

/*
 * Function call to create a "kernel" door server.  A kernel door
 * server provides a way for a user-level process to invoke a function
 * in the kernel through a door_call.  From the caller's point of
 * view, a kernel door server looks the same as a user-level one
 * (except the server pid is 0).  Unlike normal door calls, the
 * kernel door function is invoked via a normal function call in the
 * same thread and context as the caller.
 */
int
door_ki_create(void (*pc_cookie)(), void *data_cookie, uint_t attributes,
    door_handle_t *dhp)
{
	int err;
	file_t *fp;

	/* no DOOR_PRIVATE */
	if ((attributes & ~DOOR_KI_CREATE_MASK) ||
	    (attributes & (DOOR_UNREF | DOOR_UNREF_MULTI)) ==
	    (DOOR_UNREF | DOOR_UNREF_MULTI))
		return (EINVAL);

	err = door_create_common(pc_cookie, data_cookie, attributes,
	    1, NULL, &fp);
	if (err == 0 && (attributes & (DOOR_UNREF | DOOR_UNREF_MULTI)) &&
	    p0.p_unref_thread == 0) {
		/* need to create unref thread for process 0 */
		(void) thread_create(NULL, 0, door_unref_kernel, NULL, 0, &p0,
		    TS_RUN, minclsyspri);
	}
	if (err == 0) {
		*dhp = FTODH(fp);
	}
	return (err);
}

void
door_ki_hold(door_handle_t dh)
{
	file_t *fp = DHTOF(dh);

	mutex_enter(&fp->f_tlock);
	fp->f_count++;
	mutex_exit(&fp->f_tlock);
}

void
door_ki_rele(door_handle_t dh)
{
	file_t *fp = DHTOF(dh);

	(void) closef(fp);
}

int
door_ki_open(char *pathname, door_handle_t *dhp)
{
	file_t *fp;
	vnode_t *vp;
	int err;

	if ((err = lookupname(pathname, UIO_SYSSPACE, FOLLOW, NULL, &vp)) != 0)
		return (err);
	if (err = VOP_OPEN(&vp, FREAD, kcred)) {
		VN_RELE(vp);
		return (err);
	}
	if (vp->v_type != VDOOR) {
		VN_RELE(vp);
		return (EINVAL);
	}
	if ((err = falloc(vp, FREAD | FWRITE, &fp, NULL)) != 0) {
		VN_RELE(vp);
		return (err);
	}
	/* falloc returns with f_tlock held on success */
	mutex_exit(&fp->f_tlock);
	*dhp = FTODH(fp);
	return (0);
}

int
door_ki_info(door_handle_t dh, struct door_info *dip)
{
	file_t *fp = DHTOF(dh);
	vnode_t *vp;

	if (VOP_REALVP(fp->f_vnode, &vp))
		vp = fp->f_vnode;
	if (vp->v_type != VDOOR)
		return (EINVAL);
	door_info_common(VTOD(vp), dip, fp);
	return (0);
}

door_handle_t
door_ki_lookup(int did)
{
	file_t *fp;
	door_handle_t dh;

	/* is the descriptor really a door? */
	if (door_lookup(did, &fp) == NULL)
		return (NULL);
	/* got the door, put a hold on it and release the fd */
	dh = FTODH(fp);
	door_ki_hold(dh);
	releasef(did);
	return (dh);
}

int
door_ki_setparam(door_handle_t dh, int type, size_t val)
{
	file_t *fp = DHTOF(dh);
	vnode_t *vp;

	if (VOP_REALVP(fp->f_vnode, &vp))
		vp = fp->f_vnode;
	if (vp->v_type != VDOOR)
		return (EINVAL);
	return (door_setparam_common(VTOD(vp), 1, type, val));
}

int
door_ki_getparam(door_handle_t dh, int type, size_t *out)
{
	file_t *fp = DHTOF(dh);
	vnode_t *vp;

	if (VOP_REALVP(fp->f_vnode, &vp))
		vp = fp->f_vnode;
	if (vp->v_type != VDOOR)
		return (EINVAL);
	return (door_getparam_common(VTOD(vp), type, out));
}