#include "lint.h"
#include "thr_uberdata.h"
#include "libc.h"
#include <alloca.h>
#include <unistd.h>
#include <thread.h>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <door.h>
#include <signal.h>
#include <ucred.h>
#include <strings.h>
#include <ucontext.h>
#include <sys/ucred.h>
#include <atomic.h>
static door_server_func_t door_create_server;
static mutex_t door_state_lock = DEFAULTMUTEX;
door_server_func_t *door_server_func = door_create_server;
pid_t door_create_pid = 0;
static pid_t door_create_first_pid = 0;
static pid_t door_create_unref_pid = 0;
extern int __door_create(void (*)(void *, char *, size_t, door_desc_t *,
uint_t), void *, uint_t);
extern int __door_return(caddr_t, size_t, door_return_desc_t *, caddr_t,
size_t);
extern int __door_ucred(ucred_t *);
extern int __door_unref(void);
extern int __door_unbind(void);
static pthread_key_t privdoor_key = PTHREAD_ONCE_KEY_NP;
struct privdoor_data {
int pd_dfd;
door_id_t pd_uniqid;
volatile uint32_t pd_refcnt;
door_xcreate_server_func_t *pd_crf;
void *pd_crcookie;
door_xcreate_thrsetup_func_t *pd_setupf;
};
static int door_xcreate_n(door_info_t *, struct privdoor_data *, int);
static void
privdoor_data_hold(struct privdoor_data *pdd)
{
atomic_inc_32(&pdd->pd_refcnt);
}
static void
privdoor_data_rele(struct privdoor_data *pdd)
{
if (atomic_dec_32_nv(&pdd->pd_refcnt) == 0)
free(pdd);
}
void
privdoor_destructor(void *data)
{
privdoor_data_rele((struct privdoor_data *)data);
}
static void *
door_unref_func(void *arg)
{
pid_t mypid = (pid_t)(uintptr_t)arg;
sigset_t fillset;
(void) sigfillset(&fillset);
(void) thr_sigsetmask(SIG_SETMASK, &fillset, NULL);
while (getpid() == mypid && __door_unref() && errno == EINTR)
continue;
return (NULL);
}
static int
door_create_cmn(door_server_procedure_t *f, void *cookie, uint_t flags,
door_xcreate_server_func_t *crf, door_xcreate_thrsetup_func_t *setupf,
void *crcookie, int nthread)
{
int d;
int is_private = (flags & DOOR_PRIVATE);
int is_unref = (flags & (DOOR_UNREF | DOOR_UNREF_MULTI));
int do_create_first = 0;
int do_create_unref = 0;
ulwp_t *self = curthread;
pid_t mypid;
if (self->ul_vfork) {
errno = ENOTSUP;
return (-1);
}
if (crf)
flags |= DOOR_PRIVCREATE;
enter_critical(self);
if ((d = __door_create(f, cookie, flags)) < 0) {
exit_critical(self);
return (-1);
}
mypid = getpid();
if (mypid != door_create_pid ||
(!is_private && mypid != door_create_first_pid) ||
(is_unref && mypid != door_create_unref_pid)) {
lmutex_lock(&door_state_lock);
door_create_pid = mypid;
if (!is_private && mypid != door_create_first_pid) {
do_create_first = 1;
door_create_first_pid = mypid;
}
if (is_unref && mypid != door_create_unref_pid) {
do_create_unref = 1;
door_create_unref_pid = mypid;
}
lmutex_unlock(&door_state_lock);
}
exit_critical(self);
if (do_create_unref) {
(void) thr_create(NULL, 0, door_unref_func,
(void *)(uintptr_t)mypid, THR_DAEMON, NULL);
}
if (is_private) {
door_info_t di;
if (__door_info(d, &di) < 0)
return (-1);
(void) pthread_key_create_once_np(&privdoor_key,
privdoor_destructor);
if (crf == NULL) {
(*door_server_func)(&di);
} else {
struct privdoor_data *pdd = malloc(sizeof (*pdd));
if (pdd == NULL) {
(void) door_revoke(d);
errno = ENOMEM;
return (-1);
}
pdd->pd_dfd = d;
pdd->pd_uniqid = di.di_uniquifier;
pdd->pd_refcnt = 1;
pdd->pd_crf = crf;
pdd->pd_crcookie = crcookie;
pdd->pd_setupf = setupf;
if (!door_xcreate_n(&di, pdd, nthread)) {
int errnocp = errno;
(void) door_revoke(d);
privdoor_data_rele(pdd);
errno = errnocp;
return (-1);
} else {
privdoor_data_rele(pdd);
}
}
} else if (do_create_first) {
(*door_server_func)(NULL);
}
return (d);
}
int
door_create(door_server_procedure_t *f, void *cookie, uint_t flags)
{
if (flags & (DOOR_NO_DEPLETION_CB | DOOR_PRIVCREATE)) {
errno = EINVAL;
return (-1);
}
return (door_create_cmn(f, cookie, flags, NULL, NULL, NULL, 1));
}
int
door_xcreate(door_server_procedure_t *f, void *cookie, uint_t flags,
door_xcreate_server_func_t *crf, door_xcreate_thrsetup_func_t *setupf,
void *crcookie, int nthread)
{
if (flags & DOOR_PRIVCREATE || nthread < 1 || crf == NULL) {
errno = EINVAL;
return (-1);
}
return (door_create_cmn(f, cookie, flags | DOOR_PRIVATE,
crf, setupf, crcookie, nthread));
}
int
door_ucred(ucred_t **uc)
{
ucred_t *ucp = *uc;
if (ucp == NULL) {
ucp = _ucred_alloc();
if (ucp == NULL)
return (-1);
}
if (__door_ucred(ucp) != 0) {
if (*uc == NULL)
ucred_free(ucp);
return (-1);
}
*uc = ucp;
return (0);
}
int
door_cred(door_cred_t *dc)
{
ucred_t *ucp = alloca(ucred_size());
int ret;
if ((ret = __door_ucred(ucp)) == 0) {
dc->dc_euid = ucred_geteuid(ucp);
dc->dc_ruid = ucred_getruid(ucp);
dc->dc_egid = ucred_getegid(ucp);
dc->dc_rgid = ucred_getrgid(ucp);
dc->dc_pid = ucred_getpid(ucp);
}
return (ret);
}
int
door_unbind(void)
{
struct privdoor_data *pdd;
int rv = __door_unbind();
if (rv == 0 && (pdd = pthread_getspecific(privdoor_key)) != NULL) {
(void) pthread_setspecific(privdoor_key, NULL);
privdoor_data_rele(pdd);
}
return (rv);
}
int
door_return(char *data_ptr, size_t data_size,
door_desc_t *desc_ptr, uint_t num_desc)
{
caddr_t sp;
size_t ssize;
size_t reserve;
ulwp_t *self = curthread;
{
stack_t s;
if (thr_stksegment(&s) != 0) {
errno = EINVAL;
return (-1);
}
sp = s.ss_sp;
ssize = s.ss_size;
}
if (!self->ul_door_noreserve) {
#define STACK_FRACTION 32
#define MINSTACK_FRACTION 8
if (ssize < (MINSTACK * (STACK_FRACTION/MINSTACK_FRACTION)))
reserve = MINSTACK / MINSTACK_FRACTION;
else if (ssize < DEFAULTSTACK)
reserve = ssize / STACK_FRACTION;
else
reserve = DEFAULTSTACK / STACK_FRACTION;
#undef STACK_FRACTION
#undef MINSTACK_FRACTION
if (ssize > reserve)
ssize -= reserve;
else
ssize = 0;
}
#if defined(__sparc)
reserve = SA(MINFRAME);
#elif defined(__x86)
reserve = SA(512);
#else
#error need to define stack base reserve
#endif
#ifdef _STACK_GROWS_DOWNWARD
sp -= reserve;
#else
#error stack does not grow downwards, routine needs update
#endif
if (ssize > reserve)
ssize -= reserve;
else
ssize = 0;
#define MIN_DOOR_STACK 1024
if (ssize < MIN_DOOR_STACK)
ssize = 0;
#undef MIN_DOOR_STACK
if (num_desc != 0) {
door_return_desc_t d;
d.desc_ptr = desc_ptr;
d.desc_num = num_desc;
return (__door_return(data_ptr, data_size, &d, sp, ssize));
}
return (__door_return(data_ptr, data_size, NULL, sp, ssize));
}
enum door_xsync_state {
DOOR_XSYNC_CREATEWAIT = 0x1c8c8c80,
DOOR_XSYNC_ABORT,
DOOR_XSYNC_ABORTED,
DOOR_XSYNC_MAXCONCUR,
DOOR_XSYNC_CREATEFAIL,
DOOR_XSYNC_SETSPEC_FAIL,
DOOR_XSYNC_BINDFAIL,
DOOR_XSYNC_BOUND,
DOOR_XSYNC_ENTER_SERVICE
};
uint64_t door_xcreate_n_stats[DOOR_XSYNC_ENTER_SERVICE -
DOOR_XSYNC_CREATEWAIT + 1];
struct door_xsync_shared {
pthread_mutex_t lock;
pthread_cond_t cv_m2s;
pthread_cond_t cv_s2m;
struct privdoor_data *pdd;
volatile uint32_t waiting;
};
struct door_xsync {
volatile enum door_xsync_state state;
struct door_xsync_shared *sharedp;
};
void *
door_xcreate_startf(void *arg)
{
struct door_xsync *xsp = (struct door_xsync *)arg;
struct door_xsync_shared *xssp = xsp->sharedp;
struct privdoor_data *pdd = xssp->pdd;
enum door_xsync_state next_state;
privdoor_data_hold(pdd);
if (pthread_setspecific(privdoor_key, (const void *)pdd) != 0) {
next_state = DOOR_XSYNC_SETSPEC_FAIL;
privdoor_data_rele(pdd);
goto handshake;
}
if (pdd->pd_setupf != NULL) {
(pdd->pd_setupf)(pdd->pd_crcookie);
} else {
(void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
(void) pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
}
if (door_bind(pdd->pd_dfd) == 0)
next_state = DOOR_XSYNC_BOUND;
else
next_state = DOOR_XSYNC_BINDFAIL;
handshake:
(void) pthread_mutex_lock(&xssp->lock);
ASSERT(xsp->state == DOOR_XSYNC_CREATEWAIT ||
xsp->state == DOOR_XSYNC_ABORT);
if (xsp->state == DOOR_XSYNC_ABORT)
next_state = DOOR_XSYNC_ABORTED;
xsp->state = next_state;
if (--xssp->waiting == 0)
(void) pthread_cond_signal(&xssp->cv_s2m);
if (next_state != DOOR_XSYNC_BOUND) {
(void) pthread_mutex_unlock(&xssp->lock);
return (NULL);
}
while (xsp->state == DOOR_XSYNC_BOUND)
(void) pthread_cond_wait(&xssp->cv_m2s, &xssp->lock);
next_state = xsp->state;
ASSERT(next_state == DOOR_XSYNC_ENTER_SERVICE ||
next_state == DOOR_XSYNC_ABORT);
if (--xssp->waiting == 0)
(void) pthread_cond_signal(&xssp->cv_s2m);
(void) pthread_mutex_unlock(&xssp->lock);
if (next_state == DOOR_XSYNC_ABORT)
return (NULL);
(void) door_return(NULL, 0, NULL, 0);
return (NULL);
}
static int
door_xcreate_n(door_info_t *dip, struct privdoor_data *pdd, int n)
{
struct door_xsync_shared *xssp;
struct door_xsync *xsp;
int i, failidx = -1;
int isdepcb = 0;
int failerrno;
int bound = 0;
#ifdef _STACK_GROWS_DOWNWARD
int stkdir = -1;
#else
int stkdir = 1;
#endif
int rv = 0;
if (pdd == NULL) {
isdepcb = 1;
if ((pdd = pthread_getspecific(privdoor_key)) == NULL)
thr_panic("door_xcreate_n - no privdoor_data "
"on existing server thread");
}
{
size_t sz = sizeof (*xssp) + n * sizeof (*xsp) + 32;
char dummy;
if (!stack_inbounds(&dummy + stkdir * sz)) {
errno = E2BIG;
return (0);
}
}
if ((xssp = alloca(sizeof (*xssp))) == NULL ||
(xsp = alloca(n * sizeof (*xsp))) == NULL) {
errno = E2BIG;
return (0);
}
(void) pthread_mutex_init(&xssp->lock, NULL);
(void) pthread_cond_init(&xssp->cv_m2s, NULL);
(void) pthread_cond_init(&xssp->cv_s2m, NULL);
xssp->pdd = pdd;
xssp->waiting = 0;
(void) pthread_mutex_lock(&xssp->lock);
for (i = 0; failidx == -1 && i < n; i++) {
xsp[i].sharedp = xssp;
membar_producer();
switch ((pdd->pd_crf)(dip, door_xcreate_startf,
(void *)&xsp[i], pdd->pd_crcookie)) {
case 1:
xsp[i].state = DOOR_XSYNC_CREATEWAIT;
xssp->waiting++;
break;
case 0:
xsp[i].state = DOOR_XSYNC_MAXCONCUR;
if (!isdepcb) {
failidx = i;
failerrno = EINVAL;
}
break;
case -1:
xsp[i].state = DOOR_XSYNC_CREATEFAIL;
failidx = i;
failerrno = EPIPE;
break;
default:
thr_panic("door server create function illegal return");
}
}
if (!isdepcb && failidx != -1) {
for (i = 0; i < failidx; i++)
if (xsp[i].state == DOOR_XSYNC_CREATEWAIT)
xsp[i].state = DOOR_XSYNC_ABORT;
}
while (xssp->waiting)
(void) pthread_cond_wait(&xssp->cv_s2m, &xssp->lock);
if (!isdepcb && failidx != -1) {
rv = 0;
goto out;
}
for (i = 0; i < n; i++) {
int statidx = xsp[i].state - DOOR_XSYNC_CREATEWAIT;
door_xcreate_n_stats[statidx]++;
if (xsp[i].state == DOOR_XSYNC_BOUND)
bound++;
}
if (bound == n) {
rv = 1;
} else {
failerrno = EBADF;
rv = 0;
}
for (i = 0; i < n; i++) {
if (xsp[i].state == DOOR_XSYNC_BOUND) {
xsp[i].state = (rv == 1 || isdepcb) ?
DOOR_XSYNC_ENTER_SERVICE : DOOR_XSYNC_ABORT;
xssp->waiting++;
}
}
(void) pthread_cond_broadcast(&xssp->cv_m2s);
while (xssp->waiting)
(void) pthread_cond_wait(&xssp->cv_s2m, &xssp->lock);
out:
(void) pthread_mutex_unlock(&xssp->lock);
(void) pthread_mutex_destroy(&xssp->lock);
(void) pthread_cond_destroy(&xssp->cv_m2s);
(void) pthread_cond_destroy(&xssp->cv_s2m);
if (rv == 0)
errno = failerrno;
return (rv);
}
void
door_depletion_cb(door_info_t *dip)
{
if (dip == NULL) {
(*door_server_func)(NULL);
return;
}
if (dip->di_attributes & DOOR_NO_DEPLETION_CB) {
return;
} else if (!(dip->di_attributes & DOOR_PRIVCREATE)) {
dip->di_attributes |= DOOR_DEPLETION_CB;
(*door_server_func)(dip);
return;
} else {
dip->di_attributes |= DOOR_DEPLETION_CB;
(void) door_xcreate_n(dip, NULL, 1);
}
}
door_server_func_t *
door_server_create(door_server_func_t *create_func)
{
door_server_func_t *prev;
lmutex_lock(&door_state_lock);
prev = door_server_func;
door_server_func = create_func;
lmutex_unlock(&door_state_lock);
return (prev);
}
static void *
door_create_func(void *arg)
{
(void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
(void) door_return(NULL, 0, NULL, 0);
return (arg);
}
static void
door_create_server(door_info_t *dip __unused)
{
(void) thr_create(NULL, 0, door_create_func, NULL, THR_DETACHED, NULL);
yield();
}