diff --git a/include/door.h b/include/door.h new file mode 100644 --- /dev/null +++ b/include/door.h @@ -0,0 +1,32 @@ +#ifndef _DOOR_H_ +#define _DOOR_H_ + +#include +#include + +typedef u_int uint_t; + +typedef void *(server_procedure)( + void *udata, char *argp, size_t arg_size, door_desc_t *dp, u_int n_desc); + +int door_create(server_procedure *procedure, void *udata, u_int attributes); +int door_call(int fd, door_arg_t *args); +int door_return( + char *data_ptr, size_t data_size, door_desc_t *desc, size_t num_desc); +int door_attach(int fd, const char *path); +int door_detach(const char *path); +int door_revoke(int fd); +int door_info(int fd, struct door_info *info); +int door_getparam(int fd, int param, size_t *out); +int door_setparam(int fd, int param, size_t val); +int door_bind(int did); +int door_unbind(void); + +typedef void door_server_func_t(door_info_t *); +door_server_func_t *door_server_create(door_server_func_t *); + +/* Solaris compatibility */ +#define fattach(fd, path) door_attach((fd), (path)) +#define fdetach(path) door_detach((path)) + +#endif /* _DOOR_H_ */ diff --git a/sys/amd64/amd64/door_machdep.c b/sys/amd64/amd64/door_machdep.c new file mode 100644 --- /dev/null +++ b/sys/amd64/amd64/door_machdep.c @@ -0,0 +1,17 @@ +#include +#include +#include + +#include +#include + + +void door_set_args(struct thread *td, void *cookiep, char *argp, size_t arg_size, struct door_desc *dp, u_int n_desc){ + td->td_retval[0] = (register_t)cookiep; + td->td_frame->tf_rsi = (register_t)argp; + /* The second arg should go in rdx, but it gets overwritten by td->retval[1] in cpu_set_syscall_retval */ + td->td_retval[1] = arg_size; + td->td_frame->tf_rcx = (register_t)dp; + td->td_frame->tf_r8 = (register_t)n_desc; +} + diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -3604,6 +3604,7 @@ fs/tmpfs/tmpfs_fifoops.c optional tmpfs fs/tmpfs/tmpfs_vfsops.c optional tmpfs fs/tmpfs/tmpfs_subr.c optional tmpfs +fs/doorfs/doorfs_vnops.c standard gdb/gdb_cons.c optional gdb gdb/gdb_main.c optional gdb gdb/gdb_packet.c optional gdb @@ -3770,6 +3771,7 @@ kern/kern_descrip.c standard kern/kern_dtrace.c optional kdtrace_hooks kern/kern_dump.c standard +kern/kern_door.c standard kern/kern_environment.c standard kern/kern_et.c standard kern/kern_event.c standard diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64 --- a/sys/conf/files.amd64 +++ b/sys/conf/files.amd64 @@ -81,6 +81,7 @@ compile-with "${NORMAL_S} -g0" \ no-ctfconvert amd64/amd64/machdep.c standard +amd64/amd64/door_machdep.c standard amd64/amd64/mem.c optional mem amd64/amd64/minidump_machdep.c standard amd64/amd64/mp_machdep.c optional smp diff --git a/sys/fs/cd9660/cd9660_vnops.c b/sys/fs/cd9660/cd9660_vnops.c --- a/sys/fs/cd9660/cd9660_vnops.c +++ b/sys/fs/cd9660/cd9660_vnops.c @@ -107,6 +107,7 @@ return (EROFS); case VCHR: case VBLK: + case VDOOR: case VSOCK: case VFIFO: case VNON: diff --git a/sys/fs/doorfs/doorfs_vnops.c b/sys/fs/doorfs/doorfs_vnops.c new file mode 100644 --- /dev/null +++ b/sys/fs/doorfs/doorfs_vnops.c @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MALLOC_DECLARE(M_DOOR); + +static int +door_open(struct vop_open_args *ap) +{ + struct vnode *vp = ap->a_vp; + KASSERT(vp->v_type == VDOOR, ("door_open invoked on non-door vnode")); + + struct door *d = vp->v_door_vnode_info->v_door; + struct vop_vector *prev_vnops = + ap->a_vp->v_door_vnode_info->v_prev_vnops; + + KASSERT( + vp != d->door_vnode, ("door_open invoked on non-attached vnode")); + + DOOR_LOCK(d); + d->refcount++; + DOOR_UNLOCK(d); + + return prev_vnops->vop_open(ap); +} + +static int +door_close(struct vop_close_args *ap) +{ + + struct vnode *vp = ap->a_vp; + KASSERT(vp->v_type == VDOOR, ("door_close invoked on non-door vnode")); + + struct door *d = vp->v_door_vnode_info->v_door; + + DOOR_LOCK(d); + + d->refcount--; + + if ((d->attr & (DOOR_UNREF | DOOR_UNREF_MULTI)) && d->refcount == 1) { + d->unrefcount++; + + if ((d->attr & DOOR_UNREF_ACTIVE) == 0) { + /* + * If an unref thread is not bound yet + * (can occur if client only opens and closes door file + * shortly after door_create) wait for a while and then + * (hopefully) proceed. + */ + if (d->unref_td == NULL) { + int error = cv_timedwait( + &d->unref_cv, &d->door_lock, hz / 2); + if (error == EWOULDBLOCK) { + // TODO: log error rather than + // panicking + printf("door_close: no unref thread attached"); + return 0; + } + } + + DOOR_UNLOCK(d); + /* Deliver unref */ + wakeup_one(d->unref_td); + } + + } else if (d->refcount == 0) { + + /* + * This close could have come from another process which opened + * an attached node or from the owning process. + */ + if (d->proc) { + PROC_LOCK(d->proc); + LIST_REMOVE(d, entries); + + /* Wake all calling threads waiting for a server */ + cv_broadcast(&d->proc->door_td_pool.pool_cv); + PROC_UNLOCK(d->proc); + + d->proc = NULL; + } + + if (d->attr & DOOR_PRIVATE) { + /* Wake all calling threads waiting for a server */ + cv_broadcast(&d->priv_pool.pool_cv); + } + + /* "Detach" door from vnode if the last reference was held by + * another process */ + if (d->door_vnode != vp) { + vp->v_type = VREG; + vp->v_op = vp->v_door_vnode_info->v_prev_vnops; + } + + struct door_vnode_info *tmp = vp->v_door_vnode_info; + vp->v_door_vnode_info = NULL; + + wakeup(d); + while (d->active_invocations > 0) { + cv_wait(&d->close_cv, &d->door_lock); + } + + DOOR_UNLOCK(d); + + + cv_destroy(&d->unref_cv); + cv_destroy(&d->close_cv); + + free(tmp, M_TEMP); + free(d, M_DOOR); + } else { + DOOR_UNLOCK(d); + } + + return 0; +} + +/* A VDOOR type vnode can only be accessed when attached */ +static int +door_access(struct vop_access_args *ap) +{ + KASSERT(ap->a_vp->v_type == VDOOR, + ("door_access invoked on non-door vnode")); + /* Forward access to underlying fs */ + struct vop_vector *prev_vnops = + ap->a_vp->v_door_vnode_info->v_prev_vnops; + KASSERT(prev_vnops, ("door_access invoked on detached door vnode")); + + return prev_vnops->vop_access(ap); +} + +static int +door_accessx(struct vop_accessx_args *ap) +{ + KASSERT(ap->a_vp->v_type == VDOOR, + ("door_accessx invoked on non-door vnode")); + /* Forward access to underlying fs */ + struct vop_vector *prev_vnops = + ap->a_vp->v_door_vnode_info->v_prev_vnops; + + return prev_vnops->vop_accessx(ap); +} + +static int +door_reclaim(struct vop_reclaim_args *ap) +{ + + struct vnode *vp = ap->a_vp; + KASSERT( + vp->v_type == VDOOR, ("door_reclaim invoked on non-door vnode")); + + return 0; +} + +struct vop_vector door_vnops = { + .vop_default = &default_vnodeops, + + .vop_access = door_access, + .vop_accessx = door_accessx, + .vop_close = door_close, + .vop_reclaim = door_reclaim, + .vop_open = door_open, + + .vop_create = VOP_EBADF, + .vop_getattr = VOP_EBADF, + .vop_advlock = VOP_EBADF, + .vop_ioctl = VOP_EBADF, + .vop_kqfilter = VOP_EBADF, + .vop_link = VOP_EBADF, + .vop_mkdir = VOP_EBADF, + .vop_mknod = VOP_EBADF, + .vop_pathconf = VOP_EBADF, + .vop_print = VOP_EBADF, + .vop_read = VOP_EBADF, + .vop_readdir = VOP_EBADF, + .vop_readlink = VOP_EBADF, + .vop_reallocblks = VOP_EBADF, + .vop_rename = VOP_EBADF, + .vop_rmdir = VOP_EBADF, + .vop_remove = VOP_EBADF, + .vop_setattr = VOP_EBADF, + .vop_symlink = VOP_EBADF, + .vop_write = VOP_EBADF, +}; +VFS_VOP_VECTOR_REGISTER(door_vnops); diff --git a/sys/fs/tmpfs/tmpfs_subr.c b/sys/fs/tmpfs/tmpfs_subr.c --- a/sys/fs/tmpfs/tmpfs_subr.c +++ b/sys/fs/tmpfs/tmpfs_subr.c @@ -647,6 +647,7 @@ case VSOCK: case VLNK: case VREG: + case VDOOR: break; case VNON: case VBAD: diff --git a/sys/fs/udf/udf_vnops.c b/sys/fs/udf/udf_vnops.c --- a/sys/fs/udf/udf_vnops.c +++ b/sys/fs/udf/udf_vnops.c @@ -363,6 +363,7 @@ case VNON: case VBAD: case VMARKER: + case VDOOR: return (0); } } diff --git a/sys/kern/init_sysent.c b/sys/kern/init_sysent.c --- a/sys/kern/init_sysent.c +++ b/sys/kern/init_sysent.c @@ -644,4 +644,5 @@ { .sy_narg = AS(fspacectl_args), .sy_call = (sy_call_t *)sys_fspacectl, .sy_auevent = AUE_FSPACECTL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 580 = fspacectl */ { .sy_narg = 0, .sy_call = (sy_call_t *)sys_sched_getcpu, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 581 = sched_getcpu */ { .sy_narg = AS(swapoff_args), .sy_call = (sy_call_t *)sys_swapoff, .sy_auevent = AUE_SWAPOFF, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 582 = swapoff */ + { .sy_narg = AS(door_args), .sy_call = (sy_call_t *)sys_door, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 583 = door */ }; diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c --- a/sys/kern/kern_descrip.c +++ b/sys/kern/kern_descrip.c @@ -4760,6 +4760,8 @@ return ("eventfd"); case DTYPE_LINUXTFD: return ("ltimer"); + case DTYPE_DOOR: + return ("door"); default: return ("unkn"); } diff --git a/sys/kern/kern_door.c b/sys/kern/kern_door.c new file mode 100644 --- /dev/null +++ b/sys/kern/kern_door.c @@ -0,0 +1,2034 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +MALLOC_DECLARE(M_DOOR); + +MALLOC_DEFINE(M_DOOR, "door", "Solaris Doors IPC kernel descriptor"); +MALLOC_DEFINE(M_DOOR_INFO, "door_info", "Solaris Doors IPC thread info"); + +#define DOOR_DATA_COPY_THRESHOLD (PAGE_SIZE * 4) +#define DOOR_RESULT_MAX_SIZE (PAGE_SIZE * 4) +#define DOOR_MAX_DATA_SIZE (INT_MAX) + +FEATURE(doors, "Solaris Doors IPC support"); + +static u_int __cur_id = 0; + +static int +fd_to_door(struct thread *td, int fd, struct door **d) +{ + struct file *fp; + + int error = fget(td, fd, &cap_read_rights, &fp); + if (error) { + return error; + } + + if (fp->f_type == DTYPE_VNODE) { + + struct vnode *vp = fp->f_vnode; + if (vp && vp->v_type == VDOOR) { + KASSERT( + vp->v_door_vnode_info->v_door != NULL, ("Door not set")); + *d = vp->v_door_vnode_info->v_door; + } + } else if (fp->f_type == DTYPE_DOOR) { /* The server process passed its + * internal door descriptor */ + *d = (struct door *)fp->f_data; + } else { + fdrop(fp, td); + return (EBADF); + } + + fdrop(fp, td); + return 0; +} + +static struct door_td_info * +td_get_door_info(struct thread *td) +{ + + if (!td->td_door) { + struct door_td_info *info = malloc(sizeof(struct door_td_info), + M_DOOR_INFO, M_NOWAIT | M_ZERO); + if (!info) { + return NULL; + } + + td->td_door = info; + + cv_init(&td->td_door->server.hold_cv, "door_server_cv"); + cv_init(&td->td_door->client.hold_cv, "door_client_cv"); + mtx_init(&td->td_door->lock, "door_info_lock", MTX_DEF, 0); + } + + return td->td_door; +} + +/* + * Door thread pool routines. + */ + +static int +door_pool_depleted(struct thread *td) +{ + int empty; + struct door *d = td->td_door->server.cur_door; + + if (d && (d->attr & DOOR_PRIVATE)) { + DOOR_LOCK(d); + empty = SLIST_EMPTY(&d->priv_pool.td_pool); + DOOR_UNLOCK(d); + } else { + PROC_LOCK(td->td_proc); + empty = SLIST_EMPTY(&td->td_proc->door_td_pool.td_pool); + PROC_UNLOCK(td->td_proc); + } + + return empty; +} + +/* + * Tries to fetch a server thread from the corresponding pool. Blocks until a + * thread is available. + */ +static struct thread * +door_fetch_server_thread(struct door *d) +{ + struct thread *ret = NULL; + int error; + +fetch: + if (d->attr & DOOR_PRIVATE) { + DOOR_LOCK_ASSERT(d, MA_OWNED); + + ret = SLIST_FIRST(&d->priv_pool.td_pool); + if (ret) { + SLIST_REMOVE_HEAD(&d->priv_pool.td_pool, td_door_pool); + } else { + error = cv_wait_sig( + &d->priv_pool.pool_cv, &d->door_lock); + if (error == 0 && !DOOR_IS_REVOKED(d)) { + goto fetch; + } else { + return NULL; + } + } + } else { + /* Dropping door lock to avoid LOR */ + DOOR_UNLOCK(d); + PROC_LOCK(d->proc); + + ret = SLIST_FIRST(&d->proc->door_td_pool.td_pool); + if (ret) { + SLIST_REMOVE_HEAD( + &d->proc->door_td_pool.td_pool, td_door_pool); + } else { + error = cv_wait_sig( + &d->proc->door_td_pool.pool_cv, &d->proc->p_mtx); + PROC_UNLOCK(d->proc); + DOOR_LOCK(d); + if (error == 0 && !DOOR_IS_REVOKED(d)) { + goto fetch; + } else { + return NULL; + } + } + + PROC_UNLOCK(d->proc); + DOOR_LOCK(d); + } + ret->td_door->server.queued = 0; + return ret; +} + +/* + * Remove target thread from a corresponding thread pool. + */ +static void +door_detach_server_thread(struct thread *td) +{ + struct door *d = td->td_door->server.cur_door; + + if (d && (d->attr & DOOR_PRIVATE)) { + DOOR_LOCK(d); + if(!SLIST_EMPTY(&d->priv_pool.td_pool)){ + SLIST_REMOVE(&d->priv_pool.td_pool, td, thread, td_door_pool); + } + DOOR_UNLOCK(d); + } else if (td->td_proc) { + PROC_LOCK(td->td_proc); + if(!SLIST_EMPTY(&td->td_proc->door_td_pool.td_pool)){ + SLIST_REMOVE(&td->td_proc->door_td_pool.td_pool, td, thread, + td_door_pool); + } + PROC_UNLOCK(td->td_proc); + } + td->td_door->server.queued = 0; +} + +/* + * Add a new server thread to the relevant thread pool. + * Notifies caller threads waiting on a door thread pool cv. + */ +static void +door_add_server_thread(struct thread *td) +{ + struct door *d = td->td_door->server.cur_door; + + if (d && (d->attr & DOOR_PRIVATE)) { + DOOR_LOCK(d); + SLIST_INSERT_HEAD(&d->priv_pool.td_pool, td, td_door_pool); + + /* Wake one calling thread waiting for a server */ + cv_signal(&d->priv_pool.pool_cv); + DOOR_UNLOCK(d); + } else { + PROC_LOCK(td->td_proc); + SLIST_INSERT_HEAD( + &td->td_proc->door_td_pool.td_pool, td, td_door_pool); + cv_signal(&td->td_proc->door_td_pool.pool_cv); + PROC_UNLOCK(td->td_proc); + } + + td->td_door->server.queued = 1; +} + +extern struct vop_vector door_vnops; +/* + * kern_door_create: + * + * Door creation routine. Upon successful initialization, + * the created door structure is linked to a file structure and + * inserted into the current process door pool. + * Returns a file descriptor representing the door. + * + */ +static int +kern_door_create(struct thread *td, void *procedure, void *udata, long attr) +{ + struct file *fp; + struct filedescent *fde; + struct filedesc *fdp; + + int ret_fd; + int error; + + if ((attr & ~DOOR_CREATE_MASK) || + ((attr & (DOOR_UNREF | DOOR_UNREF_MULTI)) == + (DOOR_UNREF | DOOR_UNREF_MULTI))) + return (EINVAL); + + struct door *new_door = (struct door *)malloc( + sizeof(struct door), M_DOOR, M_ZERO | M_WAITOK); + if (new_door == NULL) { + return -(ENOMEM); + } + + error = falloc_caps(td, &fp, &ret_fd, FREAD, NULL); + if (error) { + free(new_door, M_DOOR); + return -(error); + } + finit(fp, FNONBLOCK, DTYPE_DOOR, new_door, &vnops); + + fdp = td->td_proc->p_fd; + + FILEDESC_XLOCK(fdp); + fde = fdeget_locked(fdp, ret_fd); + if (fde != NULL) { + fde->fde_flags = (fde->fde_flags & ~UF_EXCLOSE) | (UF_EXCLOSE); + } + FILEDESC_XUNLOCK(fdp); + + fp->f_data = (void *)new_door; + + new_door->procedure = procedure; + new_door->udata = udata; + new_door->attr = attr; + new_door->proc = td->td_proc; + new_door->id = __cur_id++; + new_door->desc_max = (attr & DOOR_REFUSE_DESC) ? 0 : INT_MAX; + new_door->data_max = DOOR_MAX_DATA_SIZE; + + new_door->refcount = 1; + fp->f_count = 1; + + SLIST_INIT(&new_door->priv_pool.td_pool); + cv_init(&new_door->priv_pool.pool_cv, "door_pool_cv"); + + cv_init(&new_door->unref_cv, "door_unref_cv"); + cv_init(&new_door->close_cv, "door_close_cv"); + + getnewvnode("doorfs", NULL, &door_vnops, &new_door->door_vnode); + + new_door->attached_vnode_info = (struct door_vnode_info *)malloc( + sizeof(struct door_vnode_info), M_TEMP, M_WAITOK); + + struct door_vnode_info *v_info = (struct door_vnode_info *)malloc( + sizeof(struct door_vnode_info), M_TEMP, M_WAITOK); + + v_info->v_door = new_door; + v_info->v_prev_vnops = NULL; + + new_door->door_vnode->v_type = VDOOR; + new_door->door_vnode->v_door_vnode_info = v_info; + fp->f_vnode = new_door->door_vnode; + + mtx_init(&new_door->door_lock, "door_lock", MTX_DEF, 0); + + PROC_LOCK(td->td_proc); + LIST_INSERT_HEAD(&td->td_proc->p_doors, new_door, entries); + PROC_UNLOCK(td->td_proc); + + return ret_fd; +} + +/* + * Utility routines for common door-related vmspace operations. + */ + +/* + * Attempt to fetch 'count' pages from map 'm'. + * Resulting pages are wired and zeroed if a page had to be allocated. + */ +static int +door_pages_lookup(vm_map_t m, vm_offset_t start, vm_page_t *pgs, int count) +{ + vm_object_t obj; + vm_pindex_t pindex; + vm_map_entry_t entry; + vm_prot_t prot; + boolean_t wired; + + int error = vm_map_lookup( + &m, start, VM_PROT_RW, &entry, &obj, &pindex, &prot, &wired); + if (error != KERN_SUCCESS) { + return (vm_mmap_to_errno(error)); + } + vm_map_lookup_done(m, entry); + + VM_OBJECT_WLOCK(obj); + + int grabbed = vm_page_grab_pages( + obj, pindex, VM_ALLOC_ZERO | VM_ALLOC_WIRED, pgs, count); + if (grabbed < count) { + VM_OBJECT_WUNLOCK(obj); + return (EFAULT); + } + + VM_OBJECT_WUNLOCK(obj); + + return 0; +} + +static inline void +door_unbusy_pages(vm_page_t *pgs, int count) +{ + + for (int i = 0; i < count; i++) { + vm_page_xunbusy(pgs[i]); + } + + return; +} + +/* + * Copy 'src_size' bytes from 'src_page' + 'src_offset' from + * the source process to 'dst_page' + 'src_offset' in the target process's address space. + * Used for shared mapping argument transfers. + */ +static int +door_copy_unaligned_page(vm_map_t src_map, vm_offset_t src_page, + vm_offset_t src_offset, size_t src_size, vm_map_t dst_map, + vm_offset_t dst_page) +{ + vm_map_entry_t _entry; + vm_prot_t _prot; + boolean_t _wired; + + vm_object_t src_obj; + vm_pindex_t src_pindex; + vm_object_t dst_obj; + vm_pindex_t dst_pindex; + + vm_page_t pgs[2] = { 0 }; + + int error = vm_map_lookup(&dst_map, dst_page, VM_PROT_READ, &_entry, + &dst_obj, &dst_pindex, &_prot, &_wired); + if (error != KERN_SUCCESS) { + return (vm_mmap_to_errno(error)); + } + vm_map_lookup_done(dst_map, _entry); + + VM_OBJECT_WLOCK(dst_obj); + vm_page_grab_valid(&pgs[0], dst_obj, dst_pindex, 0); + + VM_OBJECT_WUNLOCK(dst_obj); + + pmap_zero_page(pgs[0]); + + error = vm_map_lookup(&src_map, src_page, VM_PROT_RW, &_entry, &src_obj, + &src_pindex, &_prot, &_wired); + if (error) { + vm_page_xunbusy(pgs[0]); + return (vm_mmap_to_errno(error)); + } + vm_map_lookup_done(src_map, _entry); + + VM_OBJECT_WLOCK(src_obj); + /* + * We have not specified VM_ALLOC_NOCREAT because a client might + * leave some argument pages zeroed + */ + vm_page_grab_valid(&pgs[1], src_obj, src_pindex, 0); + if (!pgs[1]) { + VM_OBJECT_WUNLOCK(src_obj); + vm_page_xunbusy(pgs[0]); + return (EFAULT); + } + + VM_OBJECT_WUNLOCK(src_obj); + + door_unbusy_pages(pgs, 2); + + vm_offset_t kaddr = kva_alloc(2 * PAGE_SIZE); + if (!kaddr) { + return ENOMEM; + } + pmap_qenter(kaddr, &pgs[0], 2); + bcopy((void *)(kaddr + PAGE_SIZE + src_offset), + (void *)(kaddr + src_offset), src_size); + pmap_qremove(kaddr, 2); + + kva_free(kaddr, 2 * PAGE_SIZE); + + return 0; +} + +/* + * Create a shared mapping of a source buffer in the target process. + * If the buffer is unaligned the contents of the buffer stored on + * unaligned pages (the first and/or the last page) are copied to the target process. + * to prevent information leaks. + */ +static __noinline int +door_vm_map_shared(vm_map_t src_map, vm_map_t dst_map, void *src_buf, + size_t src_buf_size, vm_prot_t prot, vm_offset_t *dst_addr) +{ + + vm_object_t src_obj; + vm_pindex_t src_pindex; + vm_map_entry_t entry; + vm_prot_t _prot; + boolean_t _wired; + + vm_offset_t src_end_addr = (vm_offset_t)src_buf + src_buf_size; + vm_offset_t dst_start_addr = 0; + vm_offset_t dst_argp = 0; + + vm_offset_t buf_offset = ((vm_offset_t)src_buf & PAGE_MASK); + vm_offset_t buf_end_offset = (src_end_addr & PAGE_MASK); + + size_t total_mapping_size = round_page(src_buf_size) + + ((buf_offset != 0) * PAGE_SIZE); + + int error = vm_map_lookup(&src_map, trunc_page((vm_offset_t)src_buf), + prot, &entry, &src_obj, &src_pindex, &_prot, &_wired); + if (error) { + return (vm_mmap_to_errno(error)); + } + vm_map_lookup_done(src_map, entry); + + vm_map_lock(dst_map); + dst_start_addr = vm_map_findspace( + dst_map, trunc_page(src_buf), total_mapping_size); + if (dst_start_addr == (vm_map_max(dst_map) - src_buf_size + 1)) { + return (ENOMEM); + } + vm_map_unlock(dst_map); + + dst_argp = dst_start_addr + buf_offset; + + /* + * Explicitly copy unaligned buffer contents to prevent address space + * leaks + */ + + /* Check if src_buffer is aligned */ + if (buf_offset) { + vm_map_lock(dst_map); + + error = vm_map_insert(dst_map, NULL, 0, dst_start_addr, + dst_start_addr + PAGE_SIZE, prot, prot, 0); + if (error) { + vm_map_unlock(dst_map); + + return (vm_mmap_to_errno(error)); + } + vm_map_unlock(dst_map); + + error = door_copy_unaligned_page(src_map, trunc_page(src_buf), + buf_offset, (size_t)(PAGE_SIZE - buf_offset), dst_map, + dst_start_addr); + if (error) { + vm_map_remove(dst_map, dst_start_addr, + dst_start_addr + PAGE_SIZE); + return error; + } + + dst_start_addr += PAGE_SIZE; + total_mapping_size -= PAGE_SIZE; + src_buf_size -= (PAGE_SIZE - buf_offset); + src_pindex++; + } + + /* Check whether the buffer spans across the whole last page */ + if (buf_end_offset) { + vm_offset_t dst_last_page_addr = trunc_page( + dst_start_addr + src_buf_size); + + vm_map_lock(dst_map); + + error = vm_map_insert(dst_map, NULL, 0, dst_last_page_addr, + dst_last_page_addr + PAGE_SIZE, prot, prot, 0); + if (error) { + vm_map_unlock(dst_map); + return (vm_mmap_to_errno(error)); + } + vm_map_unlock(dst_map); + + error = door_copy_unaligned_page(src_map, + trunc_page(src_end_addr), 0, buf_end_offset, dst_map, + dst_last_page_addr); + if (error) { + vm_map_remove(dst_map, dst_last_page_addr, + dst_last_page_addr + PAGE_SIZE); + return error; + } + + total_mapping_size -= PAGE_SIZE; + } + + vm_object_reference(src_obj); + + error = vm_map_find(dst_map, src_obj, IDX_TO_OFF(src_pindex), + &dst_start_addr, total_mapping_size, 0, VMFS_NO_SPACE, + VM_PROT_READ, VM_PROT_READ, 0); + if (error != KERN_SUCCESS) { + return (vm_mmap_to_errno(error)); + } + + *dst_addr = dst_argp; + error = 0; + + return (error); +} + +/* + * Attempts to close previously opened file descriptors. + * This is used when a file descriptors are passed to server threads + * as arguments and when dealing with file descriptor arguments marked + * with DOOR_RELEASE. + */ +static int +door_fd_close(struct thread *td, door_desc_t *descs, size_t n_desc) +{ + for (u_int i = 0; i < n_desc; i++) { + struct file *fp; + int fd = descs[i].d_data.d_desc.d_descriptor; + + int error = fget(td, fd, &cap_read_rights, &fp); + if (error) { + return error; + } + + fdclose(td, fp, fd); + } + + return 0; +} + +/* + * Attempts to map an address range from a process to kernelspace. + */ +static int +door_mapin(vm_map_t src_map, vm_offset_t src, size_t size, vm_page_t **pgs, + int *n_pgs, vm_offset_t *kaddr) +{ + + vm_offset_t kern_addr; + + int src_pgs_count = (round_page(size) + + (((size_t)src & PAGE_MASK) ? PAGE_SIZE : 0)) / + PAGE_SIZE; + size_t mapping_size = src_pgs_count * PAGE_SIZE; + + vm_page_t *src_pgs = (vm_page_t *)malloc( + sizeof(vm_page_t *) * src_pgs_count, M_TEMP, M_NOWAIT); + if (!src_pgs) { + return ENOMEM; + } + + kern_addr = kva_alloc(mapping_size); + if (!kern_addr) { + free(src_pgs, M_TEMP); + return (ENOMEM); + } + + int error = door_pages_lookup( + src_map, trunc_page(src), src_pgs, src_pgs_count); + if (error) { + kva_free(kern_addr, mapping_size); + free(src_pgs, M_TEMP); + return error; + } + + pmap_qenter(kern_addr, src_pgs, src_pgs_count); + + *pgs = src_pgs; + *n_pgs = src_pgs_count; + *kaddr = kern_addr; + + return 0; +} + +/* + * Attempts to unmap a previously mapped process address range + * from the kernel map. + */ +static void +door_mapout(vm_offset_t kaddr, vm_page_t *pgs, size_t n_pgs) +{ + pmap_qremove(kaddr, n_pgs); + kva_free(kaddr, n_pgs * PAGE_SIZE); + door_unbusy_pages(pgs, n_pgs); + + free(pgs, M_TEMP); +} + +/* + * Copies target pages from a source process. + */ +static int +door_copy(vm_offset_t src, vm_map_t dst_map, vm_offset_t dst, size_t size) +{ + vm_offset_t kaddr; + vm_page_t *pgs; + int n_pages; + + int error = door_mapin(dst_map, dst, size, &pgs, &n_pages, &kaddr); + if (error) { + return error; + } + + error = copyin((void *)src, (void *)(kaddr + (dst & PAGE_MASK)), size); + if (error) { + door_mapout(kaddr, pgs, n_pages); + return (error); + } + + door_mapout(kaddr, pgs, n_pages); + + return 0; +} + +/* + * Open files specified by source process descriptors in the + * current process and store new fds. + * Used for transferring file descriptor arguments and results. + */ +static int +door_desc_translate(struct door *d, struct thread *src_td, + struct thread *dst_td, door_desc_t *udesc_p, size_t n_desc, vm_offset_t dst, + door_desc_t *translated_descs) +{ + vm_offset_t dst_kern_addr; + vm_page_t *pgs = NULL; + int n_pgs; + + int error = door_mapin(&dst_td->td_proc->p_vmspace->vm_map, dst, + n_desc * sizeof(door_desc_t), &pgs, &n_pgs, &dst_kern_addr); + if (error) { + return error; + } + + struct filedesc *fdp = src_td->td_proc->p_fd; + struct filedescent *fde; + + door_desc_t *dst_descs = (door_desc_t *)dst_kern_addr; + u_int n_dst_descs = 0; + door_desc_t *udescs = (door_desc_t *)malloc( + sizeof(door_desc_t) * n_desc, M_TEMP, M_NOWAIT); + if (!udescs) { + door_mapout(dst_kern_addr, pgs, n_pgs); + return (ENOMEM); + } + + error = copyin( + (void *)udesc_p, (void *)(udescs), n_desc * sizeof(door_desc_t)); + if (error) { + error = (EINVAL); + goto err; + } + + for (u_int i = 0; i < n_desc; i++) { + + struct file *fp; + + door_desc_t *src_desc = &udescs[i]; + door_desc_t *dst_desc = &dst_descs[i]; + + int dst_fd; + int dst_fd_flags = O_CLOEXEC; + int src_fd = src_desc->d_data.d_desc.d_descriptor; + + if (src_desc->d_attributes & DOOR_DESCRIPTOR) { + + error = fget(src_td, src_fd, &cap_read_rights, &fp); + if (error) { + goto err; + } + + /* + * Close source fd in src_td if DOOR_RELEASE was + * passed + */ + if (src_desc->d_attributes & DOOR_RELEASE) { + fdclose(src_td, fp, src_fd); + } + + FILEDESC_XLOCK(fdp); + fde = fdeget_locked(fdp, src_fd); + if (fde != NULL) { + dst_fd_flags |= + fde->fde_flags; /* Share descriptor + * flags */ + } + FILEDESC_XUNLOCK(fdp); + + error = finstall( + dst_td, fp, &dst_fd, dst_fd_flags, NULL); + if (error) { + goto err; + } + + dst_desc->d_attributes = DOOR_DESCRIPTOR | + (d->attr & DOOR_ATTR_MASK); + dst_desc->d_data.d_desc.d_descriptor = dst_fd; + dst_desc->d_data.d_desc.d_id = d->id; + + /* Store translated descriptor info in upcall info */ + if (translated_descs) { + translated_descs[i] = *dst_desc; + } + n_dst_descs++; + } + } + + door_mapout(dst_kern_addr, pgs, n_pgs); + free(udescs, M_TEMP); + + return 0; + +err: + if (n_dst_descs) { + error = door_fd_close(dst_td, dst_descs, n_dst_descs); + if (error) { + door_mapout(dst_kern_addr, pgs, n_pgs); + free(udescs, M_TEMP); + return error; + } + } + door_mapout(dst_kern_addr, pgs, n_pgs); + free(udescs, M_TEMP); + return error; +} + +/* + * Transfers arguments from a calling thread to the server thread. + * + * Arguments are either copied when smaller than DOOR_DATA_THRESHOLD or + * transferred by creating a shared mapping for the server process. + * This routine checks target door parameters (e.g. max/min arg size) + * and returns an error if a parameter check is failed. + */ +static int +door_transfer_args(struct thread *server_td, struct thread *client_td, + door_arg_t *args, struct door *d) +{ + + char *dst_argp = NULL; + size_t dst_data_size = 0; + door_desc_t *dst_dp = NULL; + u_int dst_n_desc = 0; + int error; + + vm_map_t st_map; + vm_map_t client_map; + + size_t total_arg_size = args->data_ptr ? args->data_size : 0; + total_arg_size += (args->desc_ptr ? + (args->desc_num * sizeof(door_desc_t)) : + 0); + + struct door_upcall_info *dst_upcall = + &server_td->td_door->client.upcall; + + dst_upcall->translated_desc_ptr = NULL; + dst_upcall->translated_desc_num = 0; + + if (!total_arg_size) { + goto upcall_fill; + } + + DOOR_LOCK(d); + if (total_arg_size > d->data_max || total_arg_size < d->data_min) { + DOOR_UNLOCK(d); + return (ENOBUFS); + } + + if (args->desc_num > d->desc_max) { + DOOR_UNLOCK(d); + return (ENFILE); + } + + DOOR_UNLOCK(d); + + st_map = &server_td->td_proc->p_vmspace->vm_map; + client_map = &client_td->td_proc->p_vmspace->vm_map; + + if (total_arg_size <= DOOR_DATA_COPY_THRESHOLD) { + vm_offset_t dst_arg_addr = 0; + + error = vm_mmap(st_map, &dst_arg_addr, total_arg_size, + VM_PROT_RW, VM_PROT_RW, MAP_PRIVATE, OBJT_DEFAULT, NULL, 0); + if (error) { + error = (vm_mmap_to_errno(error)); + goto err; + } + + if (args->data_ptr) { + error = door_copy((vm_offset_t)args->data_ptr, st_map, + dst_arg_addr, args->data_size); + if (error) { + vm_map_remove(st_map, dst_arg_addr, + dst_arg_addr + total_arg_size); + goto err; + } + + dst_argp = (char *)dst_arg_addr; + dst_data_size = args->data_size; + } + + /* Translate and copy descriptors */ + if (args->desc_ptr) { + vm_offset_t u_dst_descs_addr = dst_arg_addr + + dst_data_size; + + door_desc_t *translated_descs = (door_desc_t *)malloc( + args->desc_num * sizeof(door_desc_t), M_TEMP, + M_NOWAIT); + if (!translated_descs) { + return (ENOMEM); + } + + error = door_desc_translate(d, client_td, server_td, + args->desc_ptr, args->desc_num, u_dst_descs_addr, + translated_descs); + if (error) { + vm_map_remove(st_map, dst_arg_addr, + dst_arg_addr + total_arg_size); + free(translated_descs, M_TEMP); + return error; + } + + dst_dp = (door_desc_t *)u_dst_descs_addr; + dst_n_desc = args->desc_num; + + dst_upcall->translated_desc_ptr = translated_descs; + dst_upcall->translated_desc_num = dst_n_desc; + } + } else { + vm_offset_t st_region_start; + + error = door_vm_map_shared(client_map, st_map, args->data_ptr, + args->data_size, VM_PROT_RW, &st_region_start); + if (error) { + goto err; + } + + dst_argp = (char *)st_region_start; + dst_data_size = args->data_size; + } + +upcall_fill: + dst_upcall->args.data_ptr = dst_argp; + dst_upcall->args.data_size = dst_data_size; + dst_upcall->args.desc_ptr = dst_dp; + dst_upcall->args.desc_num = dst_n_desc; + + server_td->td_door->client.upcall.active = 1; + + return 0; + +err: + return error; +} + + +/* + * kern_door_call: + * + * Invoke a door procedure attached to door 'd'. + * + * Tries to fetch a server thread from a door thread pool, + * transfer arguments to the server address space and wake the + * previously selected server thread. + * Blocks until a server thread is available. + */ +static int __noinline +kern_door_call(struct thread *td, struct door *d, door_arg_t *uargs) +{ + struct thread *server_thread = door_fetch_server_thread(d); + struct door_td_info *d_td_info; + struct door_client_info *td_client; + struct door_server_info *td_server; + int error; + + if (server_thread == NULL) { + DOOR_UNLOCK(d); + return (EAGAIN); + } + + DOOR_UNLOCK(d); + + d_td_info = server_thread->td_door; + KASSERT(d_td_info, ("Thread has no door info attached")); + + DOOR_INFO_LOCK(d_td_info); + + td_client = &d_td_info->client; + td_client->td_caller = td; + + /* Attach door to server thread */ + td_server = &d_td_info->server; + td_server->cur_door = d; + + if (uargs) { + door_arg_t *args = &td_client->uargs; + + error = copyin((void *)uargs, (void *)args, sizeof(door_arg_t)); + if (error) { + DOOR_INFO_UNLOCK(d_td_info); + goto err; + } + + if ((d->attr & DOOR_REFUSE_DESC) && (args->desc_ptr)) { + error = EOPNOTSUPP; + DOOR_INFO_UNLOCK(d_td_info); + goto err; + } + + if (args->rbuf && (args->rsize >= DOOR_RESULT_MAX_SIZE)) { + error = E2BIG; + DOOR_INFO_UNLOCK(d_td_info); + goto err; + } + + DOOR_TD_HOLD(td_server); + DOOR_INFO_UNLOCK(d_td_info); + + error = door_transfer_args(server_thread, td, args, d); + if (error) { + goto err; + } + + DOOR_INFO_LOCK(d_td_info); + td_client->uargs_addr = (void *)uargs; + DOOR_TD_RELEASE(td_server); + } + + DOOR_LOCK(d); + d->active_invocations++; + DOOR_UNLOCK(d); + + wakeup_one((void *)server_thread); + +sleep: + error = mtx_sleep((void *)td, &d_td_info->lock, PCATCH, "drcall", 0); + + /* Check if we have been interrupted */ + if (error) { + td_client->td_caller = NULL; + DOOR_INFO_UNLOCK(d_td_info); + + DOOR_LOCK(d); + d->active_invocations--; + if (d->active_invocations <= 0) { + cv_broadcast(&d->close_cv); + } + DOOR_UNLOCK(d); + + return (error); + } + + if (td_client->error != 0) { + td_client->td_caller = NULL; + + if (td_client->error == DOOR_EXIT) { + DOOR_INFO_UNLOCK(d_td_info); + /* Server thread is exiting, free door info */ + cv_destroy(&d_td_info->server.hold_cv); + cv_destroy(&d_td_info->client.hold_cv); + + free(d_td_info, M_DOOR_INFO); + + DOOR_LOCK(d); + d->active_invocations--; + if (d->active_invocations <= 0) { + cv_broadcast(&d->close_cv); + } + DOOR_UNLOCK(d); + + return (EINTR); + } else if (td_client->error == DOOR_WAIT) { + td_client->error = 0; + goto sleep; + } + } + + while (DOOR_TD_HELD(td_client)) { + cv_wait(&td_client->hold_cv, &d->door_lock); + } + /* Write any changes to client args */ + if (td_client->uargs_changed) { + error = copyout(&td_client->uargs, td_client->uargs_addr, + sizeof(door_arg_t)); + if (error) { + return error; + } + } + + if (td_client->uargs_addr) { + td_client->uargs_addr = 0; + } + + DOOR_INFO_UNLOCK(d_td_info); + + DOOR_LOCK(d); + d->active_invocations--; + if (d->active_invocations <= 0) { + cv_broadcast(&d->close_cv); + } + + DOOR_UNLOCK(d); + + return 0; + +err: + td_client->td_caller = NULL; + return error; +} + +/* + * Transfer results from a server thread to the calling thread. + */ +static int +door_transfer_results(char *rbuf, size_t rbuf_size, door_desc_t *descs, + size_t n_desc, struct thread *server_td, struct thread *client_td, + struct door *d) +{ + int error; + + vm_offset_t rbuf_mapped_addr = 0; + vm_offset_t desc_mapped_addr = 0; + + size_t rbuf_mapped_size = 0; + size_t desc_mapped_size = 0; + + struct door_client_info *client = &server_td->td_door->client; + vm_map_t client_map = &client_td->td_proc->p_vmspace->vm_map; + + if (rbuf) { + char *client_rbuf = client->uargs.rbuf; + size_t client_rbuf_size = client->uargs.rsize; + + if (client_rbuf) { + if (client_rbuf_size < rbuf_size) { + return (E2BIG); + } + } else { + /* Allocate a result region for the client */ + /* This needs to be munmaped later */ + rbuf_mapped_size = round_page(rbuf_size); + + error = vm_mmap(client_map, &rbuf_mapped_addr, + rbuf_mapped_size, VM_PROT_RW, VM_PROT_RW, + MAP_PRIVATE, OBJT_DEFAULT, NULL, 0); + if (error) { + return (vm_mmap_to_errno(error)); + } + + client->uargs.rbuf = client_rbuf = (char *) + rbuf_mapped_addr; + client->uargs.rsize = client_rbuf_size = rbuf_size; + + client->uargs_changed = 1; + } + + error = door_copy((vm_offset_t)rbuf, client_map, + (vm_offset_t)client_rbuf, rbuf_size); + if (error) { + goto err; + } + } + if (descs) { + desc_mapped_size = round_page(n_desc * sizeof(door_desc_t)); + + error = vm_mmap(client_map, &desc_mapped_addr, desc_mapped_size, + VM_PROT_RW, VM_PROT_RW, MAP_PRIVATE, OBJT_DEFAULT, NULL, 0); + if (error) { + error = (vm_mmap_to_errno(error)); + goto err; + } + + error = door_desc_translate(d, server_td, client_td, descs, + n_desc, desc_mapped_addr, NULL); + if (error) { + goto err; + } + + client->uargs.desc_ptr = (door_desc_t *)desc_mapped_addr; + client->uargs.desc_num = n_desc; + + client->uargs_changed = 1; + } + + return 0; + +err: + if (rbuf_mapped_addr) { + vm_map_remove(client_map, rbuf_mapped_addr, + rbuf_mapped_addr + rbuf_mapped_size); + } + if (desc_mapped_addr) { + vm_map_remove(client_map, desc_mapped_addr, + desc_mapped_addr + desc_mapped_size); + } + + return error; +} + +/* + * Remove a previously created shared mapping. + */ +static int +door_vm_unmap(vm_map_t dst_map, struct door_client_info *client) +{ + + struct door_upcall_args *args = &client->upcall.args; + + if (args->data_ptr) { + int error = vm_map_remove(dst_map, trunc_page(args->data_ptr), + trunc_page(args->data_ptr + args->data_size)); + if (error) { + return error; + } + } + + if (args->desc_ptr) { + int error = vm_map_remove(dst_map, trunc_page(args->desc_ptr), + trunc_page(args->desc_ptr + + (args->desc_num * sizeof(door_desc_t)))); + if (error) { + return error; + } + } + return 0; +} + +/* + * Setup door procedure execution by modifying target thread frame. + * Called when a server thread is activated, right before returning + * from kern_door_return. + * Procedure arguments are set via the machine-dependent + * 'door_set_args' routine. + */ +static int +door_set_upcall(struct thread *td, struct door_td_info *d_info, + struct door_upcall_info *upcall) +{ + + struct door_upcall_args *args = &upcall->args; + struct door *d = d_info->server.cur_door; + + cpu_set_upcall( + td, (void (*)(void *))d->procedure, NULL, &d_info->server.stack); + door_set_args(td, d->udata, args->data_ptr, args->data_size, + args->desc_ptr, args->desc_num); + + return 0; +} + +/* + * Houses a single unref thread. + */ +static int +door_unref(struct thread *td, struct door *d) +{ + DOOR_LOCK_ASSERT(d, MA_OWNED); + + if ((d->attr & (DOOR_UNREF | DOOR_UNREF_MULTI))) { + + if (d->unrefcount == 0) { + + int error = mtx_sleep( + (void *)td, &d->door_lock, PCATCH, "drunrf", 0); + if (error) { /* Check for interrupts or exit() */ + d->unref_td = NULL; + DOOR_UNLOCK(d); + + struct door_td_info *info = td->td_door; + + thread_lock(td); + td->td_door = NULL; + thread_unlock(td); + + free(info, M_DOOR_INFO); + return (EINTR); + } + } + + cpu_set_upcall(td, (void (*)(void *))d->procedure, NULL, + &td->td_door->server.stack); + door_set_args(td, d->udata, DOOR_UNREF_DATA, 0, 0, 0); + + if (d->attr & DOOR_UNREF) { + d->attr &= ~DOOR_UNREF; + } + + d->attr |= DOOR_UNREF_ACTIVE; + d->unrefcount--; + } else { + d->unref_td = NULL; + } + + DOOR_UNLOCK(d); + + return (0); +} + +/* + * kern_door_return: + * + * Register a door server thread or return door procedure invocation results to the caller. + * + * A newly created door server thread registers itself by calling door_return where it is + * placed on the appropriate door thread pool and blocks until a caller fetches it from the pool. + * + * Upon returning from a successful door procedure invocation each door procedure must call door_return. + * This routine then copies any results to the client, places the thread on the appropriate thread pool and blocks once again + * + */ +static int __noinline +kern_door_return(struct thread *td, char *result_ptr, size_t result_size, + door_desc_t *dp, long num_desc, void *stack_base, size_t stack_size) +{ + struct thread *server_thread = td; + struct proc *server_proc = td->td_proc; + int error = 0; + + struct door_td_info *d_info = td_get_door_info(server_thread); + if (!d_info) { + return (ENOMEM); + } + + DOOR_INFO_LOCK(d_info); + struct door_client_info *client = &d_info->client; + struct door_server_info *server = &d_info->server; + + /* + * Check if a newly created server thread called door_return or + * door_bind + */ + if (!server->cur_door || + (server->private_pool && client->td_caller == NULL)) { + DOOR_INFO_UNLOCK(d_info); + /* Check stack mapping */ + vm_map_lock(&server_proc->p_vmspace->vm_map); + // TODO: check protection + // if (!vm_map_check_protection(&server_proc->p_vmspace->vm_map, + //(vm_offset_t) stack_base, (vm_offset_t) stack_base + + // stack_size, VM_PROT_RW)) { + if (!vm_map_range_valid(&server_proc->p_vmspace->vm_map, + (vm_offset_t)stack_base, + (vm_offset_t)stack_base + stack_size)) { + server_thread->td_door = NULL; + vm_map_unlock(&server_proc->p_vmspace->vm_map); + + free(d_info, M_DOOR_INFO); + return (EFAULT); + } + vm_map_unlock(&server_proc->p_vmspace->vm_map); + + /* Assume that the thread stack info never changes */ + server->stack.ss_sp = stack_base; + server->stack.ss_size = stack_size; + + goto release; + } + + struct door *d = d_info->server.cur_door; + + DOOR_LOCK(d); + if (__predict_false(td == d->unref_td)) { + + d->attr &= ~DOOR_UNREF_ACTIVE; + if (!(d->attr & (DOOR_UNREF | DOOR_UNREF_MULTI))) { + d->unref_td = NULL; + d->unrefcount--; + + DOOR_UNLOCK(d); + DOOR_INFO_UNLOCK(d_info); + + td->td_door = NULL; + free(d_info, M_DOOR_INFO); + + return 0; + } + DOOR_INFO_UNLOCK(d_info); + + return door_unref(td, d); + } + DOOR_UNLOCK(d); + + /* Check if caller is still active */ + if (client->upcall.active && client->td_caller == NULL) { + DOOR_INFO_UNLOCK(d_info); + if (d->attr & DOOR_NO_CANCEL) { + goto release; + } else { + goto out; + } + } + + if (result_ptr || dp) { + DOOR_TD_HOLD(client); + DOOR_INFO_UNLOCK(d_info); + + error = door_transfer_results(result_ptr, result_size, dp, + num_desc, td, client->td_caller, d); + if (error) { + DOOR_INFO_LOCK(d_info); + client->error = error; + DOOR_TD_RELEASE(client); + DOOR_INFO_UNLOCK(d_info); + + goto out; + } + + DOOR_INFO_LOCK(d_info); + client->error = 0; + DOOR_TD_RELEASE(client); + } + + if (!server->private_pool) { + server->cur_door = NULL; + } + + struct thread *td_caller = client->td_caller; + + client->upcall.active = 0; + client->td_caller = NULL; + client->error = 0; + DOOR_INFO_UNLOCK(d_info); + + wakeup_one((void *)td_caller); + +release: + /* Unmap transferred arguments */ + door_vm_unmap(&server_proc->p_vmspace->vm_map, client); + + /* Free any opened descriptors */ + if (client->upcall.translated_desc_ptr) { + door_fd_close(td, client->upcall.translated_desc_ptr, + client->upcall.translated_desc_num); + + free(client->upcall.translated_desc_ptr, M_TEMP); + client->upcall.translated_desc_ptr = NULL; + client->upcall.translated_desc_num = 0; + } + + door_add_server_thread(server_thread); + +sleep: + DOOR_INFO_LOCK(d_info); + error = mtx_sleep( + (void *)server_thread, &d_info->lock, PCATCH, "drret", 0); + + client = &d_info->client; + + /* Handle wakeup */ + if (error) { + if (client->td_caller) { + client->error = error; + DOOR_INFO_UNLOCK(d_info); + wakeup_one((void *)client->td_caller); + } else { + DOOR_INFO_UNLOCK(d_info); + } + goto out; + } else if (client->td_caller) { + /* Check if client is still copying args */ + door_set_upcall(server_thread, d_info, &client->upcall); + client->error = DOOR_WAIT; + DOOR_INFO_UNLOCK(d_info); + + } else { + goto sleep; + } + + return 0; +out: + door_slam(td); + return error; +} + +/* +* Attaches a door to a regular file. This is achieved by latching onto an existing file's vnode, +* changing its type to VDOOR and its vnops to door_vnops which forwards certain ops to the original vnops vector. +*/ +static int +kern_door_attach( + struct thread *td, long fd, const char *path) +{ + struct nameidata nd; + struct vnode *door_vp; + struct file *door_file; + struct door *door; + + int error = fget(td, fd, &cap_read_rights, &door_file); + if (error) { + return (error); + } + // dont increase reference count + fdrop(door_file, td); + + if (door_file->f_type != DTYPE_DOOR) { + return (EINVAL); + } + door = door_file->f_data; + + if (DOOR_IS_REVOKED(door)) { + return (ECANCELED); + } + + struct door_vnode_info *v_info = door->attached_vnode_info; + + NDPREINIT(&nd); + // restart: + NDINIT(&nd, LOOKUP, + FOLLOW | AUDITVNODE1, UIO_USERSPACE, path); + if ((error = namei(&nd)) != 0) + goto err; + if (nd.ni_vp != NULL) { + NDFREE(&nd, NDF_ONLY_PNBUF); + + door_vp = nd.ni_vp; + VI_LOCK(door_vp); + + /* Check if an existing door is already attached */ + if (door_vp->v_type == VDOOR) { + VI_UNLOCK(door_vp); + error = (EEXIST); + goto err; + } + + /* Only attach to "regular" files */ + if (door_vp->v_type != VREG) { + VI_UNLOCK(door_vp); + error = (EINVAL); + goto err; + } + + door_vp->v_type = VDOOR; + + v_info->v_door = door; + v_info->v_prev_vnops = door_vp->v_op; + + door_vp->v_door_vnode_info = v_info; + door_vp->v_op = &door_vnops; + + VI_UNLOCK(door_vp); + + + DOOR_LOCK(door); + door->attached_vnode = door_vp; + DOOR_UNLOCK(door); + + return (0); + } else { + error = (ENOENT); + } + +err: + free(v_info, M_TEMP); + return (error); +} + +/* +* Detaches a door from a previously attached file. +*/ +static int +kern_door_detach(struct thread *td, const char *path) +{ + struct nameidata nd; + struct vnode *door_vp; + struct door *door; + struct door_vnode_info *v_info; + + int error; + + NDPREINIT(&nd); + // restart: + NDINIT(&nd, LOOKUP, + FOLLOW | AUDITVNODE1, UIO_USERSPACE, path); + if ((error = namei(&nd)) != 0) + return (error); + if (nd.ni_vp != NULL) { + NDFREE(&nd, NDF_ONLY_PNBUF); + + door_vp = nd.ni_vp; + VI_LOCK(door_vp); + + /* Check vnode type */ + if (door_vp->v_type != VDOOR) { + VI_UNLOCK(door_vp); + return (EINVAL); + } + + v_info = door_vp->v_door_vnode_info; + door = v_info->v_door; + + /* Restore previous vnode info */ + door_vp->v_op = v_info->v_prev_vnops; + door_vp->v_type = VREG; + + VI_UNLOCK(door_vp); + + + DOOR_LOCK(door); + door->attached_vnode = NULL; + DOOR_UNLOCK(door); + + return (0); + } else { + error = (ENOENT); + } + + return (error); +} + +/* + * Cleans up all thread door-related resources. Called from + * thread_exit and kern_door_return when exiting due to an error. + */ +void +door_slam(struct thread *td) +{ + struct door_td_info *d_info = td->td_door; + struct door *d = d_info->server.cur_door; + + /* Check if there is an active client */ + DOOR_INFO_LOCK(d_info); + + if (door_pool_depleted(td) && ((td->td_proc->p_flag & P_WEXIT) == 0)) { + td->td_retval[0] = DOOR_POOL_DEPLETED; + } + + if (d_info->client.td_caller) { + d_info->client.error = DOOR_EXIT; + + d = d_info->server.cur_door; + DOOR_LOCK(d); + d->active_invocations--; + + if (d->active_invocations <= 0) { + cv_broadcast(&d->close_cv); + } + DOOR_UNLOCK(d); + + if (d_info->client.upcall.translated_desc_ptr) { + door_fd_close(td, + d_info->client.upcall.translated_desc_ptr, + d_info->client.upcall.translated_desc_num); + DOOR_INFO_UNLOCK(d_info); + free(d_info->client.upcall.translated_desc_ptr, M_TEMP); + + } else { + DOOR_INFO_UNLOCK(d_info); + } + wakeup_one(d_info->client.td_caller); + + } else { + /* We are responsible for deallocation */ + DOOR_INFO_UNLOCK(d_info); + + /* + * Remove thread from respective pool (check for + * unref thread) + */ + if (d_info->server.queued) { + door_detach_server_thread(td); + } + + cv_destroy(&d_info->server.hold_cv); + cv_destroy(&d_info->client.hold_cv); + + free(d_info, M_DOOR_INFO); + } + + thread_lock(td); + td->td_door = NULL; + thread_unlock(td); + + return; +} + +/* + * Removes all doors owned by process p. Called from exit1. + */ +void +door_exit(struct proc *p) +{ + struct door *d; + + LIST_FOREACH (d, &p->p_doors, entries) { + KASSERT( + d->proc == p, ("Called door_exit with non-owning process")); + + DOOR_LOCK(d); + d->attr |= DOOR_REVOKED; + d->proc = NULL; + /* Reset refcount to 1 so + * that the final close frees + * all door structures */ + d->refcount = 1; + + /* Detach door from an attached file, if any */ + if (d->attached_vnode != NULL) { + struct vnode *vp = d->attached_vnode; + VI_LOCK(vp); + vp->v_type = VREG; + vp->v_op = vp->v_door_vnode_info->v_prev_vnops; + vp->v_door_vnode_info = NULL; + + d->attached_vnode = NULL; + VI_UNLOCK(vp); + } + + /* Wake all calling threads waiting for a server */ + if (d->attr & DOOR_PRIVATE) { + cv_broadcast(&d->priv_pool.pool_cv); + } + cv_broadcast(&p->door_td_pool.pool_cv); + + DOOR_UNLOCK(d); + } + + return; +} + +/* + * Registers a single unref thread. + */ + +static int +kern_door_unref( + struct thread *td, struct door *d, void *stack_base, size_t stack_size) +{ + + struct door_td_info *td_info; + + DOOR_LOCK(d); + + if (d->unref_td) { + DOOR_UNLOCK(d); + + return (EALREADY); + } + + if ((d->attr & (DOOR_UNREF | DOOR_UNREF_MULTI)) == 0) { + DOOR_UNLOCK(d); + return (EINVAL); + } + + DOOR_UNLOCK(d); + + td_info = td_get_door_info(td); + if (!td_info) { + return (ENOMEM); + } + + DOOR_LOCK(d); + + d->unref_td = td; + td_info->server.stack.ss_sp = stack_base; + td_info->server.stack.ss_size = stack_size; + + td_info->server.cur_door = d; + + cv_broadcast(&d->unref_cv); + + return door_unref(td, d); +} + +/* +* Prohibits further invocation on target door. +* Does not affect active invocations. +*/ +static int +kern_door_revoke(struct thread *td, struct door *d) +{ + if (td->td_proc != d->proc) { + return (EPERM); + } + + if ((d->attr & DOOR_REVOKED) == 0) { + d->attr |= DOOR_REVOKED; + } + + return 0; +} + +/* +* Provides door info by filling out a user-provided door_info_t structure. +*/ +static int +kern_door_info(struct thread *td, struct door *d, void *u_door_info) +{ + + struct door_info d_info; + + d_info.di_target = d->proc->p_pid; + d_info.di_proc = d->procedure; + d_info.di_data = d->udata; + d_info.di_attributes = (d->attr & DOOR_ATTR_MASK) | + (d->refcount ? 0 : DOOR_IS_UNREF) | + ((td->td_proc == d->proc) ? DOOR_LOCAL : 0); + d_info.di_uniquifier = d->id; + + int error = copyout( + (void *)&d_info, u_door_info, sizeof(struct door_info)); + if (error) { + return error; + } + + return 0; +} + +/* +* Sets various parameters for a door. +*/ +static int +kern_door_setparam(struct thread *td, struct door *d, long param, size_t val) +{ + if (td->td_proc != d->proc) { + return (EPERM); + } + + int error = 0; + + DOOR_LOCK(d); + switch (param) { + case DOOR_PARAM_DESC_MAX: { + if (d->attr & DOOR_REFUSE_DESC) { + error = (EOPNOTSUPP); + break; + } + + if (val > DOOR_MAX_DATA_SIZE) { + error = (ERANGE); + break; + } + + d->desc_max = val; + break; + } + case DOOR_PARAM_DATA_MAX: { + if (val > DOOR_MAX_DATA_SIZE) { + error = (ERANGE); + break; + } + + d->data_max = val; + break; + } + case DOOR_PARAM_DATA_MIN: { + if (val > DOOR_MAX_DATA_SIZE) { + error = (ERANGE); + break; + } + + if (d->data_max < val) { + error = (EINVAL); + break; + } + + d->data_min = val; + break; + } + default: + error = (EINVAL); + } + DOOR_UNLOCK(d); + + return (error); +} + +/* +* Fetches desired parameter from door. +*/ +static int +kern_door_getparam(struct door *d, int param, size_t *u_param_val) +{ + size_t retval; + + DOOR_LOCK(d); + switch (param) { + case DOOR_PARAM_DESC_MAX: + retval = d->desc_max; + break; + case DOOR_PARAM_DATA_MAX: + retval = d->data_max; + break; + case DOOR_PARAM_DATA_MIN: + retval = d->data_min; + break; + default: + DOOR_UNLOCK(d); + return (EINVAL); + } + + DOOR_UNLOCK(d); + int error = copyout((void *)&retval, u_param_val, sizeof(size_t)); + if (error) { + return error; + } + + return (0); +} + +/* +* Binds invoking thread to a private door thread pool. +*/ +static int +kern_door_bind( + struct thread *td, struct door *d) +{ + + DOOR_LOCK(d); + + if (d->proc != td->td_proc) { + DOOR_UNLOCK(d); + return (EPERM); + } + + if (!(d->attr & DOOR_PRIVATE)) { + DOOR_UNLOCK(d); + return (EINVAL); + } + DOOR_UNLOCK(d); + + + if (td->td_door == NULL) { + td_get_door_info(td); + } + + td->td_door->server.cur_door = d; + td->td_door->server.private_pool = 1; + + return 0; +} + +/* +* Releases invoking thread from private door thread pool. +*/ +static int +kern_door_unbind(struct thread *td) +{ + struct door *d = td->td_door->server.cur_door; + + if (d == NULL) { + return (EBADF); + } + + DOOR_LOCK(d); + + if (!(d->attr & DOOR_PRIVATE)) { + DOOR_UNLOCK(d); + return (EINVAL); + } + + td->td_door->server.cur_door = NULL; + + DOOR_UNLOCK(d); + return (0); +} + +/* +* System call wrapper for all door-related system calls. +*/ +int +sys_door(struct thread *td, struct door_args *args) +{ + switch (args->subcode) { + case DOOR_CREATE: { + int door_fd; + + /* Check procedure */ + if (args->arg1 == NULL) { + td->td_retval[0] = -1; + return (EINVAL); + } + + door_fd = kern_door_create( + td, args->arg1, (char *)args->arg2, (long)args->arg3); + if (door_fd < 0) { + td->td_retval[0] = -1; + return -(door_fd); + } + + td->td_retval[0] = door_fd; + return (0); + } + case DOOR_CALL: { + struct door *door; + int error = fd_to_door(td, (long)args->arg1, &door); + if (error) { + td->td_retval[0] = -1; + return error; + } + + DOOR_LOCK(door); + if (DOOR_IS_REVOKED(door)) { + DOOR_UNLOCK(door); + return (ECANCELED); + } + + return kern_door_call(td, door, (door_arg_t *)args->arg2); + } + case DOOR_RETURN: + return kern_door_return(td, (char *)args->arg1, + (size_t)args->arg2, (door_desc_t *)args->arg3, + (long)args->arg4, args->arg5, (size_t)args->arg6); + case DOOR_ATTACH: { + return kern_door_attach( + td, (long)args->arg1, (const char *)args->arg2); + } + + case DOOR_DETACH: { + return kern_door_detach(td, (const char *)args->arg1); + } + + case DOOR_UNREFSYS: { + struct door *door; + int error = fd_to_door(td, (long)args->arg1, &door); + if (error) { + td->td_retval[0] = -1; + return error; + } + + return kern_door_unref( + td, door, args->arg2, (size_t)args->arg3); + } + case DOOR_REVOKE: { + struct door *door; + int error = fd_to_door(td, (long)args->arg1, &door); + if (error) { + td->td_retval[0] = -1; + return error; + } + + return kern_door_revoke(td, door); + } + + case DOOR_INFO: { + struct door *door; + int error = fd_to_door(td, (long)args->arg1, &door); + if (error) { + td->td_retval[0] = -1; + return error; + } + + return kern_door_info(td, door, args->arg2); + } + + case DOOR_SETPARAM: { + struct door *door; + int error = fd_to_door(td, (long)args->arg1, &door); + if (error) { + td->td_retval[0] = -1; + return error; + } + + return kern_door_setparam( + td, door, (long)args->arg2, (size_t)args->arg3); + } + case DOOR_GETPARAM: { + struct door *door; + int error = fd_to_door(td, (long)args->arg1, &door); + if (error) { + td->td_retval[0] = -1; + return error; + } + + return kern_door_getparam( + door, (long)args->arg2, (size_t *)args->arg3); + } + case DOOR_BIND: { + struct door *door; + int error = fd_to_door(td, (long)args->arg1, &door); + if (error) { + td->td_retval[0] = -1; + return error; + } + + return kern_door_bind(td, door); + } + case DOOR_UNBIND: { + return kern_door_unbind(td); + } + + default: + return (EINVAL); + } +} diff --git a/sys/kern/kern_exit.c b/sys/kern/kern_exit.c --- a/sys/kern/kern_exit.c +++ b/sys/kern/kern_exit.c @@ -213,6 +213,8 @@ __unreachable(); } +void door_exit(struct proc *p); + /* * Exit: deallocate address space and other resources, change proc state to * zombie, and unlink proc from allproc and parent's lists. Save exit status @@ -242,6 +244,8 @@ panic("Going nowhere without my init!"); } + + /* * Deref SU mp, since the thread does not return to userspace. */ @@ -251,6 +255,9 @@ * MUST abort all other threads before proceeding past here. */ PROC_LOCK(p); + if(!LIST_EMPTY(&p->p_doors)){ + door_exit(p); + } /* * First check if some other thread or external request got * here before us. If so, act appropriately: exit or suspend. @@ -317,6 +324,7 @@ msleep(&p->p_lock, &p->p_mtx, PWAIT, "exithold", 0); PROC_UNLOCK(p); + /* Drain the limit callout while we don't have the proc locked */ callout_drain(&p->p_limco); diff --git a/sys/kern/kern_fork.c b/sys/kern/kern_fork.c --- a/sys/kern/kern_fork.c +++ b/sys/kern/kern_fork.c @@ -626,6 +626,11 @@ LIST_INIT(&p2->p_children); LIST_INIT(&p2->p_orphans); + /* Initialize door-related resources */ + LIST_INIT(&p2->p_doors); + SLIST_INIT(&p2->door_td_pool.td_pool); + cv_init(&p2->door_td_pool.pool_cv, "door_pool_cv"); + callout_init_mtx(&p2->p_itcallout, &p2->p_mtx, 0); TAILQ_INIT(&p2->p_kqtim_stop); diff --git a/sys/kern/kern_thr.c b/sys/kern/kern_thr.c --- a/sys/kern/kern_thr.c +++ b/sys/kern/kern_thr.c @@ -166,6 +166,7 @@ /* Set up our machine context. */ stack.ss_sp = param->stack_base; stack.ss_size = param->stack_size; + /* Set upcall address to user thread entry function. */ cpu_set_upcall(td, param->start_func, param->arg, &stack); /* Setup user TLS address and TLS pointer register. */ @@ -305,11 +306,11 @@ return (0); } + int sys_thr_exit(struct thread *td, struct thr_exit_args *uap) /* long *state */ { - umtx_thread_exit(td); /* Signal userland that it can free the stack. */ @@ -321,6 +322,10 @@ return (kern_thr_exit(td)); } +void door_slam(struct thread *td); + + + int kern_thr_exit(struct thread *td) { @@ -328,6 +333,7 @@ p = td->td_proc; + /* * If all of the threads in a process call this routine to * exit (e.g. all threads call pthread_exit()), exactly one @@ -353,6 +359,12 @@ return (0); } + if(td->td_door){ + PROC_UNLOCK(p); + door_slam(td); + PROC_LOCK(p); + } + if (p->p_sysent->sv_ontdexit != NULL) p->p_sysent->sv_ontdexit(td); @@ -378,6 +390,7 @@ AUDIT_SYSCALL_EXIT(0, td); #endif + PROC_SLOCK(p); thread_stopped(p); thread_exit(); diff --git a/sys/kern/syscalls.c b/sys/kern/syscalls.c --- a/sys/kern/syscalls.c +++ b/sys/kern/syscalls.c @@ -589,4 +589,5 @@ "fspacectl", /* 580 = fspacectl */ "sched_getcpu", /* 581 = sched_getcpu */ "swapoff", /* 582 = swapoff */ + "door", /* 583 = door */ }; diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master --- a/sys/kern/syscalls.master +++ b/sys/kern/syscalls.master @@ -3307,6 +3307,20 @@ ); } +583 AUE_NULL STD { + int door( + int subcode, + void *arg1, + void *arg2, + void *arg3, + void *arg4, + void *arg5, + void *arg6, + ); + } + + + ; Please copy any additions and changes to the following compatability tables: ; sys/compat/freebsd32/syscalls.master ; vim: syntax=off diff --git a/sys/kern/systrace_args.c b/sys/kern/systrace_args.c --- a/sys/kern/systrace_args.c +++ b/sys/kern/systrace_args.c @@ -3417,6 +3417,19 @@ *n_args = 2; break; } + /* door */ + case 583: { + struct door_args *p = params; + iarg[a++] = p->subcode; /* int */ + uarg[a++] = (intptr_t)p->arg1; /* void * */ + uarg[a++] = (intptr_t)p->arg2; /* void * */ + uarg[a++] = (intptr_t)p->arg3; /* void * */ + uarg[a++] = (intptr_t)p->arg4; /* void * */ + uarg[a++] = (intptr_t)p->arg5; /* void * */ + uarg[a++] = (intptr_t)p->arg6; /* void * */ + *n_args = 7; + break; + } default: *n_args = 0; break; @@ -9134,6 +9147,34 @@ break; }; break; + /* door */ + case 583: + switch (ndx) { + case 0: + p = "int"; + break; + case 1: + p = "userland void *"; + break; + case 2: + p = "userland void *"; + break; + case 3: + p = "userland void *"; + break; + case 4: + p = "userland void *"; + break; + case 5: + p = "userland void *"; + break; + case 6: + p = "userland void *"; + break; + default: + break; + }; + break; default: break; }; @@ -11087,6 +11128,11 @@ if (ndx == 0 || ndx == 1) p = "int"; break; + /* door */ + case 583: + if (ndx == 0 || ndx == 1) + p = "int"; + break; default: break; }; diff --git a/sys/kern/vfs_default.c b/sys/kern/vfs_default.c --- a/sys/kern/vfs_default.c +++ b/sys/kern/vfs_default.c @@ -1718,6 +1718,9 @@ case VFIFO: mode |= S_IFIFO; break; + case VDOOR: + mode |= S_IFDOOR; + break; default: error = EBADF; goto out; diff --git a/sys/kern/vfs_subr.c b/sys/kern/vfs_subr.c --- a/sys/kern/vfs_subr.c +++ b/sys/kern/vfs_subr.c @@ -140,11 +140,11 @@ */ enum vtype iftovt_tab[16] = { VNON, VFIFO, VCHR, VNON, VDIR, VNON, VBLK, VNON, - VREG, VNON, VLNK, VNON, VSOCK, VNON, VNON, VNON + VREG, VNON, VLNK, VNON, VSOCK, VDOOR, VNON, VNON }; -int vttoif_tab[10] = { +int vttoif_tab[11] = { 0, S_IFREG, S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, - S_IFSOCK, S_IFIFO, S_IFMT, S_IFMT + S_IFSOCK, S_IFIFO, S_IFMT, S_IFMT, S_IFDOOR }; /* @@ -4065,7 +4065,7 @@ */ static const char * const typename[] = {"VNON", "VREG", "VDIR", "VBLK", "VCHR", "VLNK", "VSOCK", "VFIFO", "VBAD", - "VMARKER"}; + "VMARKER", "VDOOR"}; _Static_assert((VHOLD_ALL_FLAGS & ~VHOLD_NO_SMR) == 0, "new hold count flag not added to vn_printf"); diff --git a/sys/sys/door.h b/sys/sys/door.h new file mode 100644 --- /dev/null +++ b/sys/sys/door.h @@ -0,0 +1,206 @@ +#ifndef _DOOR_ +#define _DOOR_ + +#include + +#define DOOR_UNREF 0x01 /* Deliver an unref notification with door */ +#define DOOR_PRIVATE 0x02 /* Use a private pool of server threads */ +#define DOOR_UNREF_MULTI 0x10 /* Deliver unref notification more than once */ +#define DOOR_REFUSE_DESC 0x40 /* Do not accept descriptors from callers */ +#define DOOR_NO_CANCEL 0x80 /* No server thread cancel on client abort */ +#define DOOR_NO_DEPLETION_CB \ + 0x100 /* No thread create callbacks on depletion \ + */ + +/* Attributes (additional) returned with door_info and door_desc_t data */ +#define DOOR_LOCAL 0x04 /* Descriptor is local to current process */ +#define DOOR_REVOKED 0x08 /* Door has been revoked */ +#define DOOR_IS_UNREF 0x20 /* Door is currently unreferenced */ +#define DOOR_PRIVCREATE 0x200 /* Door has a private thread creation func */ +#define DOOR_DEPLETION_CB 0x400 /* Set only during depletion callbacks */ +#define DOOR_UNREF_ACTIVE 0x800 /* Ongoing UNREF invocation */ + +#define DOOR_UNREF_DATA ((void *)1) +#define DOOR_POOL_DEPLETED \ + (-2) /* Internal return value used by kern_door_return and libdoor */ + +#define DOOR_CREATE_MASK \ + (DOOR_UNREF | DOOR_PRIVATE | DOOR_UNREF_MULTI | DOOR_REFUSE_DESC | \ + DOOR_NO_CANCEL) +#define DOOR_ATTR_MASK \ + (DOOR_CREATE_MASK | DOOR_LOCAL | DOOR_REVOKED | DOOR_IS_UNREF) + +#define DOOR_DESCRIPTOR 0x10000 /* A file descriptor is being passed */ +#define DOOR_RELEASE 0x40000 /* Passed references are also released */ + +/* door parameters */ +#define DOOR_PARAM_DESC_MAX 1 /* max number of request descriptors */ +#define DOOR_PARAM_DATA_MAX 2 /* max bytes of request data */ +#define DOOR_PARAM_DATA_MIN 3 /* min bytes of request data */ + +typedef u_int door_attr_t; +typedef u_int door_id_t; +typedef void *door_ptr_t; + +typedef struct door_desc { + door_attr_t d_attributes; /* Tag for union */ + union { + struct { + int d_descriptor; /* Descriptor */ + door_id_t d_id; /* Unique door id */ + } d_desc; + } d_data; +} door_desc_t; + +typedef struct door_arg { + char *data_ptr; /* Argument/result data */ + size_t data_size; /* Argument/result data size */ + door_desc_t *desc_ptr; /* Argument/result descriptors */ + u_int desc_num; /* Argument/result num discriptors */ + char *rbuf; /* Result area */ + size_t rsize; /* Result size */ +} door_arg_t; + +typedef struct door_info { + pid_t di_target; /* door server pid */ + door_ptr_t di_proc; /* server function */ + door_ptr_t di_data; /* data cookie for invocation */ + door_attr_t di_attributes; /* door attributes */ + door_id_t di_uniquifier; /* unique id among all doors */ +} door_info_t; + +/* Syscall subcodes */ +#define DOOR_CREATE 0 +#define DOOR_REVOKE 1 +#define DOOR_INFO 2 +#define DOOR_CALL 3 +#define DOOR_BIND 6 +#define DOOR_UNBIND 7 +#define DOOR_UNREFSYS 8 +#define DOOR_UCRED 9 +#define DOOR_RETURN 10 +#define DOOR_GETPARAM 11 +#define DOOR_SETPARAM 12 +#define DOOR_ATTACH 13 +#define DOOR_DETACH 14 + +#ifdef _KERNEL +#include +#include +#include +#include + +#define DOOR_WAIT -1 +#define DOOR_EXIT -2 + +void door_slam(struct thread *td); +void door_exit(struct proc *p); + +struct door; + +struct door_upcall_args { + char *data_ptr; /* Argument/result data */ + size_t data_size; /* Argument/result data size */ + door_desc_t *desc_ptr; /* Argument/result descriptors */ + u_int desc_num; /* Argument/result num discriptors */ +}; + +struct door_upcall_info { + int active; + + struct door_upcall_args args; /* Arguments passed to door_return */ + + door_desc_t *translated_desc_ptr; /* Argument/result descriptors */ + u_int translated_desc_num; /* Argument/result num discriptors */ +}; + +struct door_client_info { + struct thread *td_caller; + + door_arg_t uargs; /* Arguments passed via door_call */ + void *uargs_addr; /* Pointer to userspace door_arg_t */ + boolean_t uargs_changed; /* Signals that a result and/or descriptor buffer + have been allocated for the client */ + + boolean_t hold; + struct cv hold_cv; + + int error; + + struct door_upcall_info upcall; +}; + +struct door_server_info { + struct door *cur_door; + stack_t stack; + boolean_t private_pool; + boolean_t queued; + boolean_t hold; + struct cv hold_cv; +}; + +struct door_td_info { + struct mtx lock; + struct door_server_info server; + struct door_client_info client; +}; + +struct vnops; + +struct door_vnode_info { + struct door *v_door; + struct vop_vector *v_prev_vnops; +}; + +struct door { + + struct mtx door_lock; + u_int attr; + /* Currently, a single door can only be bound to a single file */ + struct vnode *door_vnode; + struct vnode *attached_vnode; + struct door_vnode_info *attached_vnode_info; + + size_t desc_max; + size_t data_min; + size_t data_max; + + int refcount; + int unrefcount; + + int active_invocations; + struct cv close_cv; + + struct proc *proc; + struct thread *unref_td; + struct cv unref_cv; + + door_pool_t priv_pool; + + LIST_ENTRY(door) entries; + + void (*procedure)(void); + void *udata; + + door_id_t id; +}; + +#define DOOR_TD_HELD(info) (((info)->hold) == 1) +#define DOOR_TD_HOLD(info) (((info)->hold) = 1) +#define DOOR_TD_RELEASE(info) \ + do { \ + ((info)->hold) = 0; \ + cv_signal(&(info)->hold_cv); \ + } while (0) + +#define DOOR_INFO_LOCK(i) mtx_lock(&(i)->lock) +#define DOOR_INFO_UNLOCK(i) mtx_unlock(&(i)->lock) + +#define DOOR_LOCK(d) mtx_lock(&(d)->door_lock) +#define DOOR_UNLOCK(d) mtx_unlock(&(d)->door_lock) +#define DOOR_LOCK_ASSERT(d, val) mtx_assert(&(d)->door_lock, (val)); +#define DOOR_IS_REVOKED(d) ((d)->attr & DOOR_REVOKED) + + +#endif /* _KERNEL */ +#endif /* _DOOR_ */ diff --git a/sys/sys/door_common.h b/sys/sys/door_common.h new file mode 100644 --- /dev/null +++ b/sys/sys/door_common.h @@ -0,0 +1,14 @@ +/* Used by door.h and proc.h */ +#ifndef _DOOR_COMMON_ +#define _DOOR_COMMON_ + + +#include +#include + +typedef struct { + SLIST_HEAD(, thread) td_pool; + struct cv pool_cv; +} door_pool_t; + +#endif /* _DOOR_COMMON_ */ diff --git a/sys/sys/door_extern.h b/sys/sys/door_extern.h new file mode 100644 --- /dev/null +++ b/sys/sys/door_extern.h @@ -0,0 +1,17 @@ +#ifndef _DOOR_EXTERN_ +#define _DOOR_EXTERN_ + +#ifdef _KERNEL + +#include +#include + +struct thread; +struct door_desc; + +/* NB: udata (the first arg) is set by cpu_set_upcall */ +void door_set_args(struct thread *td, void *cookiep, char *argp, + size_t arg_size, struct door_desc *dp, u_int n_desc); + +#endif /* _KERNEL */ +#endif /* _DOOR_EXTERN_ */ diff --git a/sys/sys/file.h b/sys/sys/file.h --- a/sys/sys/file.h +++ b/sys/sys/file.h @@ -71,6 +71,8 @@ #define DTYPE_PROCDESC 12 /* process descriptor */ #define DTYPE_EVENTFD 13 /* eventfd */ #define DTYPE_LINUXTFD 14 /* emulation timerfd type */ +#define DTYPE_DOOR 15 /* Solaris Door endpoint */ + #ifdef _KERNEL diff --git a/sys/sys/proc.h b/sys/sys/proc.h --- a/sys/sys/proc.h +++ b/sys/sys/proc.h @@ -54,6 +54,7 @@ #include #include #include +#include #include #include /* XXX. */ #include @@ -200,6 +201,7 @@ struct vm_map; struct vm_map_entry; struct epoch_tracker; +struct door_td_info; struct syscall_args { u_int code; @@ -341,6 +343,7 @@ void *td_sigblock_ptr; /* (k) uptr for fast sigblock. */ uint32_t td_sigblock_val; /* (k) fast sigblock value read at td_sigblock_ptr on kern entry */ + #define td_endcopy td_pcb /* @@ -389,6 +392,8 @@ #ifdef EPOCH_TRACE SLIST_HEAD(, epoch_tracker) td_epochs; #endif + struct door_td_info *td_door; + SLIST_ENTRY(thread) td_door_pool; }; struct thread0_storage { @@ -744,6 +749,9 @@ LIST_HEAD(, proc) p_orphans; /* (e) Pointer to list of orphans. */ TAILQ_HEAD(, kq_timer_cb_data) p_kqtim_stop; /* (c) */ + + LIST_HEAD(, door) p_doors; /* List of doors. */ + door_pool_t door_td_pool; /* Door thread pool. */ }; #define p_session p_pgrp->pg_session diff --git a/sys/sys/stat.h b/sys/sys/stat.h --- a/sys/sys/stat.h +++ b/sys/sys/stat.h @@ -275,7 +275,9 @@ #define S_IFREG 0100000 /* regular */ #define S_IFLNK 0120000 /* symbolic link */ #define S_IFSOCK 0140000 /* socket */ +#define S_IFDOOR 0150000 /* door */ #define S_ISVTX 0001000 /* save swapped text even after use */ + #endif #if __BSD_VISIBLE #define S_IFWHT 0160000 /* whiteout */ diff --git a/sys/sys/syscall.h b/sys/sys/syscall.h --- a/sys/sys/syscall.h +++ b/sys/sys/syscall.h @@ -520,4 +520,5 @@ #define SYS_fspacectl 580 #define SYS_sched_getcpu 581 #define SYS_swapoff 582 -#define SYS_MAXSYSCALL 583 +#define SYS_door 583 +#define SYS_MAXSYSCALL 584 diff --git a/sys/sys/syscall.mk b/sys/sys/syscall.mk --- a/sys/sys/syscall.mk +++ b/sys/sys/syscall.mk @@ -425,4 +425,5 @@ aio_readv.o \ fspacectl.o \ sched_getcpu.o \ - swapoff.o + swapoff.o \ + door.o diff --git a/sys/sys/sysproto.h b/sys/sys/sysproto.h --- a/sys/sys/sysproto.h +++ b/sys/sys/sysproto.h @@ -1855,6 +1855,15 @@ char name_l_[PADL_(const char *)]; const char * name; char name_r_[PADR_(const char *)]; char flags_l_[PADL_(u_int)]; u_int flags; char flags_r_[PADR_(u_int)]; }; +struct door_args { + char subcode_l_[PADL_(int)]; int subcode; char subcode_r_[PADR_(int)]; + char arg1_l_[PADL_(void *)]; void * arg1; char arg1_r_[PADR_(void *)]; + char arg2_l_[PADL_(void *)]; void * arg2; char arg2_r_[PADR_(void *)]; + char arg3_l_[PADL_(void *)]; void * arg3; char arg3_r_[PADR_(void *)]; + char arg4_l_[PADL_(void *)]; void * arg4; char arg4_r_[PADR_(void *)]; + char arg5_l_[PADL_(void *)]; void * arg5; char arg5_r_[PADR_(void *)]; + char arg6_l_[PADL_(void *)]; void * arg6; char arg6_r_[PADR_(void *)]; +}; int sys_exit(struct thread *, struct exit_args *); int sys_fork(struct thread *, struct fork_args *); int sys_read(struct thread *, struct read_args *); @@ -2250,6 +2259,7 @@ int sys_fspacectl(struct thread *, struct fspacectl_args *); int sys_sched_getcpu(struct thread *, struct sched_getcpu_args *); int sys_swapoff(struct thread *, struct swapoff_args *); +int sys_door(struct thread *, struct door_args *); #ifdef COMPAT_43 @@ -3219,6 +3229,7 @@ #define SYS_AUE_fspacectl AUE_FSPACECTL #define SYS_AUE_sched_getcpu AUE_NULL #define SYS_AUE_swapoff AUE_SWAPOFF +#define SYS_AUE_door AUE_NULL #undef PAD_ #undef PADL_ diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h --- a/sys/sys/vnode.h +++ b/sys/sys/vnode.h @@ -57,7 +57,7 @@ * Vnode types. VNON means no type. */ enum vtype { VNON, VREG, VDIR, VBLK, VCHR, VLNK, VSOCK, VFIFO, VBAD, - VMARKER }; + VMARKER, VDOOR}; enum vgetstate { VGET_NONE, VGET_HOLDCNT, VGET_USECOUNT }; /* @@ -67,6 +67,7 @@ struct namecache; struct cache_fpl; +struct door_vnode_info; struct vpollinfo { struct mtx vpi_lock; /* lock to protect below */ @@ -127,6 +128,7 @@ struct unpcb *v_unpcb; /* v unix domain net (VSOCK) */ struct cdev *v_rdev; /* v device (VCHR, VBLK) */ struct fifoinfo *v_fifoinfo; /* v fifo (VFIFO) */ + struct door_vnode_info *v_door_vnode_info; /* v ptr to attached door (VDOOR) */ }; /*