root/usr/src/uts/common/fs/doorfs/door_sys.c
/*
 * 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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 * Copyright 2021 Tintri by DDN, Inc. All rights reserved.
 */

/*
 * 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/prsystm.h>
#include <sys/procfs.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 <vm/seg_kpm.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 transferred 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 transferred 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 = 4 * 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_INIT(&door_vfs, door_vfsops, NULL);
        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_or(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;
        int             needcleanup = 0;
        int             cancel_pending;

        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));

        /*
         * We don't want to hold the door FD over the entire operation;
         * instead, we put a hold on the door vnode and release the FD
         * immediately
         */
        VN_HOLD(DTOV(dp));
        releasef(did);

        /*
         * This should be done in shuttle_resume(), just before going to
         * sleep, but we want to avoid overhead while holding door_knob.
         * prstop() is just a no-op if we don't really go to sleep.
         * We test not-kernel-address-space for the sake of clustering code.
         */
        if (lwp && lwp->lwp_nostop == 0 && curproc->p_as != &kas)
                prstop(PR_REQUESTED, 0);

        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;
        ct->d_args_done = 0;
        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 */
                cancel_pending = 0;
                if (ISSIG(curthread, FORREAL) || lwp->lwp_sysabort ||
                    MUSTRETURN(curproc, curthread) ||
                    (cancel_pending = schedctl_cancel_pending()) != 0) {
                        /* Signal, forkall, ... */
                        lwp->lwp_sysabort = 0;
                        if (cancel_pending)
                                schedctl_cancel_eintr();
                        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);

                /*
                 * If the server has not processed our message, free the
                 * descriptors.
                 */
                if (!ct->d_args_done) {
                        needcleanup = 1;
                        ct->d_args_done = 1;
                }

                /*
                 * Find out if results were successfully copied.
                 */
                if (ct->d_error == 0)
                        gotresults = 1;
        }
        ASSERT(ct->d_args_done);
        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);

        if (needcleanup)
                door_fp_close(ct->d_fpp, ct->d_args.desc_num);

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_nowatch(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_nowatch(start, ct->d_args.desc_ptr, dsize)) {
                        error = EFAULT;
                        goto out;
                }
        }

        /*
         * Return the results
         */
        if (datamodel == DATAMODEL_NATIVE) {
                if (copyout_nowatch(&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_nowatch(&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)
                VN_RELE(DTOV(dp));

        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.
 *
 * Note that we use copyout_nowatch();  this is called while the client is
 * held.
 */
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_nowatch(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_nowatch(kbase + offset, (void *)start, amount))
                        return (1);
                count -= amount;
        }
        return (0);
}

/*
 * The IA32 ABI supplement 1.0 changed the required stack alignment to
 * 16 bytes (from 4 bytes), so that code can make use of SSE instructions.
 * This is already done for process entry, thread entry, and makecontext();
 * We need to do this for door_return as well. The stack will be aligned to
 * whatever the door_results is aligned.
 * See: usr/src/lib/libc/i386/gen/makectxt.c for more details.
 */
#if defined(__amd64)
#undef STACK_ALIGN32
#define STACK_ALIGN32 16
#endif

/*
 * 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 != NULL)
                        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 != NULL || 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 (door_stack_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 (door_stack_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 (door_stack_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 */
        int             cancel_pending;

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

        /*
         * This should be done in shuttle_resume(), just before going to
         * sleep, but we want to avoid overhead while holding door_knob.
         * prstop() is just a no-op if we don't really go to sleep.
         * We test not-kernel-address-space for the sake of clustering code.
         */
        lwp = ttolwp(curthread);
        if (lwp && lwp->lwp_nostop == 0 && curproc->p_as != &kas)
                prstop(PR_REQUESTED, 0);

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

                /* let the client know we have processed its message */
                ct->d_args_done = 1;

                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);
                cancel_pending = 0;
                if (ISSIG(curthread, FORREAL) || lwp->lwp_sysabort ||
                    MUSTRETURN(curproc, curthread) ||
                    (cancel_pending = schedctl_cancel_pending()) != 0) {
                        if (cancel_pending)
                                schedctl_cancel_eintr();
                        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;
        door_upcall_t   *dup;
        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);

        p = ttoproc(caller);

        /*
         * If the credentials are not specified by the client, get the one
         * associated with the calling process.
         */
        if ((dup = ct->d_upcall) != NULL)
                res = cred2ucred(dup->du_cred, p0.p_pid, NULL, CRED());
        else
                res = cred2ucred(caller->t_cred, p->p_pid, NULL, CRED());

        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, NULL))
                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 signaled 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, NULL))
                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, NULL, SIZE_MAX, 0);

                if (unref_args.rbuf != 0) {
                        kmem_free(unref_args.rbuf, unref_args.rsize);
                        unref_args.rbuf = NULL;
                        unref_args.rsize = 0;
                }

                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_nowatch(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_nowatch(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_nowatch(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_nowatch(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, NULL))
                                        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);
        door_upcall_t   *dup = ct->d_upcall;
        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 (dup != NULL) {
                if (desc_num > dup->du_max_descs)
                        return (EMFILE);

                if (data_size > dup->du_max_data)
                        return (E2BIG);

                /*
                 * 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_nowatch(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_nowatch(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_nowatch(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_nowatch(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, RW_READER);
                pfnum = hat_getpfnum(as->a_hat, rdest);
                AS_LOCK_EXIT(as);

                /*
                 * 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
         */
        if (kpm_enable)
                kaddr = (caddr_t)hat_kpm_mapin(pp, (struct kpme *)NULL);
        else
                kaddr = (caddr_t)ppmapin(pp, PROT_READ | PROT_WRITE,
                    (caddr_t)-1);

        /*
         * Copy from src to dest
         */
        if (copyin_nowatch(src, kaddr + off, len) != 0)
                error = EFAULT;
        /*
         * Unmap destination page from kernel
         */
        if (kpm_enable)
                hat_kpm_mapout(pp, (struct kpme *)NULL, kaddr);
        else
                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.  The caller is responsible
 *      for holding a reference to the cred passed in.
 */
int
door_upcall(vnode_t *vp, door_arg_t *param, struct cred *cred,
    size_t max_data, uint_t max_descs)
{
        /* Locals */
        door_upcall_t   *dup;
        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;
        int             cancel_pending;

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

        dup = kmem_zalloc(sizeof (*dup), KM_SLEEP);
        dup->du_cred = (cred != NULL) ? cred : curthread->t_cred;
        dup->du_max_data = max_data;
        dup->du_max_descs = max_descs;

        /*
         * This should be done in shuttle_resume(), just before going to
         * sleep, but we want to avoid overhead while holding door_knob.
         * prstop() is just a no-op if we don't really go to sleep.
         * We test not-kernel-address-space for the sake of clustering code.
         */
        if (lwp && lwp->lwp_nostop == 0 && curproc->p_as != &kas)
                prstop(PR_REQUESTED, 0);

        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 = dup;
        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 */
                cancel_pending = 0;
                if (lwp && (ISSIG(curthread, FORREAL) || lwp->lwp_sysabort ||
                    MUSTRETURN(curproc, curthread) ||
                    (cancel_pending = schedctl_cancel_pending()) != 0)) {
                        /* Signal, forkall, ... */
                        if (cancel_pending)
                                schedctl_cancel_eintr();
                        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, NULL))
                                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:
        kmem_free(dup, sizeof (*dup));

        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 = NULL;
        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, with
 * the credentials associated with curthread.
 */
int
door_ki_upcall(door_handle_t dh, door_arg_t *param)
{
        return (door_ki_upcall_limited(dh, param, NULL, SIZE_MAX, UINT_MAX));
}

/*
 * door_ki_upcall_limited invokes a user-level door server from the
 * kernel with the given credentials and reply limits.  If the "cred"
 * argument is NULL, uses the credentials associated with current
 * thread.  max_data limits the maximum length of the returned data (the
 * client will get E2BIG if they go over), and max_desc limits the
 * number of returned descriptors (the client will get EMFILE if they
 * go over).
 */
int
door_ki_upcall_limited(door_handle_t dh, door_arg_t *param, struct cred *cred,
    size_t max_data, uint_t max_desc)
{
        file_t *fp = DHTOF(dh);
        vnode_t *realvp;

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

/*
 * 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, NULL)) {
                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, NULL))
                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, NULL))
                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, NULL))
                vp = fp->f_vnode;
        if (vp->v_type != VDOOR)
                return (EINVAL);
        return (door_getparam_common(VTOD(vp), type, out));
}