freebsd-nq/sys/compat/cloudabi64/cloudabi64_poll.c
2017-01-28 16:31:23 +00:00

409 lines
12 KiB
C

/*-
* Copyright (c) 2015 Nuxi, https://nuxi.nl/
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/syscallsubr.h>
#include <contrib/cloudabi/cloudabi64_types.h>
#include <compat/cloudabi/cloudabi_util.h>
#include <compat/cloudabi64/cloudabi64_proto.h>
#include <compat/cloudabi64/cloudabi64_util.h>
/* Converts a FreeBSD signal number to a CloudABI signal number. */
static cloudabi_signal_t
convert_signal(int sig)
{
static const cloudabi_signal_t signals[] = {
[SIGABRT] = CLOUDABI_SIGABRT,
[SIGALRM] = CLOUDABI_SIGALRM,
[SIGBUS] = CLOUDABI_SIGBUS,
[SIGCHLD] = CLOUDABI_SIGCHLD,
[SIGCONT] = CLOUDABI_SIGCONT,
[SIGFPE] = CLOUDABI_SIGFPE,
[SIGHUP] = CLOUDABI_SIGHUP,
[SIGILL] = CLOUDABI_SIGILL,
[SIGINT] = CLOUDABI_SIGINT,
[SIGKILL] = CLOUDABI_SIGKILL,
[SIGPIPE] = CLOUDABI_SIGPIPE,
[SIGQUIT] = CLOUDABI_SIGQUIT,
[SIGSEGV] = CLOUDABI_SIGSEGV,
[SIGSTOP] = CLOUDABI_SIGSTOP,
[SIGSYS] = CLOUDABI_SIGSYS,
[SIGTERM] = CLOUDABI_SIGTERM,
[SIGTRAP] = CLOUDABI_SIGTRAP,
[SIGTSTP] = CLOUDABI_SIGTSTP,
[SIGTTIN] = CLOUDABI_SIGTTIN,
[SIGTTOU] = CLOUDABI_SIGTTOU,
[SIGURG] = CLOUDABI_SIGURG,
[SIGUSR1] = CLOUDABI_SIGUSR1,
[SIGUSR2] = CLOUDABI_SIGUSR2,
[SIGVTALRM] = CLOUDABI_SIGVTALRM,
[SIGXCPU] = CLOUDABI_SIGXCPU,
[SIGXFSZ] = CLOUDABI_SIGXFSZ,
};
/* Convert unknown signals to SIGABRT. */
if (sig < 0 || sig >= nitems(signals) || signals[sig] == 0)
return (SIGABRT);
return (signals[sig]);
}
struct cloudabi64_kevent_args {
const cloudabi64_subscription_t *in;
cloudabi64_event_t *out;
bool once;
};
/* Converts CloudABI's subscription objects to FreeBSD's struct kevent. */
static int
cloudabi64_kevent_copyin(void *arg, struct kevent *kevp, int count)
{
cloudabi64_subscription_t sub;
struct cloudabi64_kevent_args *args;
cloudabi_timestamp_t ts;
int error;
args = arg;
while (count-- > 0) {
/* TODO(ed): Copy in multiple entries at once. */
error = copyin(args->in++, &sub, sizeof(sub));
if (error != 0)
return (error);
memset(kevp, 0, sizeof(*kevp));
kevp->udata = TO_PTR(sub.userdata);
switch (sub.type) {
case CLOUDABI_EVENTTYPE_CLOCK:
kevp->filter = EVFILT_TIMER;
kevp->ident = sub.clock.identifier;
kevp->fflags = NOTE_NSECONDS;
if ((sub.clock.flags &
CLOUDABI_SUBSCRIPTION_CLOCK_ABSTIME) != 0 &&
sub.clock.timeout > 0) {
/* Convert absolute timestamp to a relative. */
error = cloudabi_clock_time_get(curthread,
sub.clock.clock_id, &ts);
if (error != 0)
return (error);
ts = ts > sub.clock.timeout ? 0 :
sub.clock.timeout - ts;
} else {
/* Relative timestamp. */
ts = sub.clock.timeout;
}
kevp->data = ts > INTPTR_MAX ? INTPTR_MAX : ts;
break;
case CLOUDABI_EVENTTYPE_FD_READ:
kevp->filter = EVFILT_READ;
kevp->ident = sub.fd_readwrite.fd;
if ((sub.fd_readwrite.flags &
CLOUDABI_SUBSCRIPTION_FD_READWRITE_POLL) != 0)
kevp->fflags = NOTE_FILE_POLL;
break;
case CLOUDABI_EVENTTYPE_FD_WRITE:
kevp->filter = EVFILT_WRITE;
kevp->ident = sub.fd_readwrite.fd;
break;
case CLOUDABI_EVENTTYPE_PROC_TERMINATE:
kevp->filter = EVFILT_PROCDESC;
kevp->ident = sub.proc_terminate.fd;
kevp->fflags = NOTE_EXIT;
break;
}
if (args->once) {
/* Ignore flags. Simply use oneshot mode. */
kevp->flags = EV_ADD | EV_ONESHOT;
} else {
/* Translate flags. */
if ((sub.flags & CLOUDABI_SUBSCRIPTION_ADD) != 0)
kevp->flags |= EV_ADD;
if ((sub.flags & CLOUDABI_SUBSCRIPTION_CLEAR) != 0)
kevp->flags |= EV_CLEAR;
if ((sub.flags & CLOUDABI_SUBSCRIPTION_DELETE) != 0)
kevp->flags |= EV_DELETE;
if ((sub.flags & CLOUDABI_SUBSCRIPTION_DISABLE) != 0)
kevp->flags |= EV_DISABLE;
if ((sub.flags & CLOUDABI_SUBSCRIPTION_ENABLE) != 0)
kevp->flags |= EV_ENABLE;
if ((sub.flags & CLOUDABI_SUBSCRIPTION_ONESHOT) != 0)
kevp->flags |= EV_ONESHOT;
}
++kevp;
}
return (0);
}
/* Converts FreeBSD's struct kevent to CloudABI's event objects. */
static int
cloudabi64_kevent_copyout(void *arg, struct kevent *kevp, int count)
{
cloudabi64_event_t ev;
struct cloudabi64_kevent_args *args;
int error;
args = arg;
while (count-- > 0) {
/* Convert fields that should always be present. */
memset(&ev, 0, sizeof(ev));
ev.userdata = (uintptr_t)kevp->udata;
switch (kevp->filter) {
case EVFILT_TIMER:
ev.type = CLOUDABI_EVENTTYPE_CLOCK;
ev.clock.identifier = kevp->ident;
break;
case EVFILT_READ:
ev.type = CLOUDABI_EVENTTYPE_FD_READ;
ev.fd_readwrite.fd = kevp->ident;
break;
case EVFILT_WRITE:
ev.type = CLOUDABI_EVENTTYPE_FD_WRITE;
ev.fd_readwrite.fd = kevp->ident;
break;
case EVFILT_PROCDESC:
ev.type = CLOUDABI_EVENTTYPE_PROC_TERMINATE;
ev.proc_terminate.fd = kevp->ident;
break;
}
if ((kevp->flags & EV_ERROR) == 0) {
/* Success. */
switch (kevp->filter) {
case EVFILT_READ:
case EVFILT_WRITE:
ev.fd_readwrite.nbytes = kevp->data;
if ((kevp->flags & EV_EOF) != 0) {
ev.fd_readwrite.flags |=
CLOUDABI_EVENT_FD_READWRITE_HANGUP;
}
break;
case EVFILT_PROCDESC:
if (WIFSIGNALED(kevp->data)) {
/* Process got signalled. */
ev.proc_terminate.signal =
convert_signal(WTERMSIG(kevp->data));
ev.proc_terminate.exitcode = 0;
} else {
/* Process exited. */
ev.proc_terminate.signal = 0;
ev.proc_terminate.exitcode =
WEXITSTATUS(kevp->data);
}
break;
}
} else {
/* Error. */
ev.error = cloudabi_convert_errno(kevp->data);
}
++kevp;
/* TODO(ed): Copy out multiple entries at once. */
error = copyout(&ev, args->out++, sizeof(ev));
if (error != 0)
return (error);
}
return (0);
}
int
cloudabi64_sys_poll(struct thread *td, struct cloudabi64_sys_poll_args *uap)
{
struct cloudabi64_kevent_args args = {
.in = uap->in,
.out = uap->out,
.once = true,
};
struct kevent_copyops copyops = {
.k_copyin = cloudabi64_kevent_copyin,
.k_copyout = cloudabi64_kevent_copyout,
.arg = &args,
};
/*
* Bandaid to support CloudABI futex constructs that are not
* implemented through FreeBSD's kqueue().
*/
if (uap->nsubscriptions == 1) {
cloudabi64_subscription_t sub;
cloudabi64_event_t ev = {};
int error;
error = copyin(uap->in, &sub, sizeof(sub));
if (error != 0)
return (error);
ev.userdata = sub.userdata;
ev.type = sub.type;
if (sub.type == CLOUDABI_EVENTTYPE_CONDVAR) {
/* Wait on a condition variable. */
ev.condvar.condvar = sub.condvar.condvar;
ev.error = cloudabi_convert_errno(
cloudabi_futex_condvar_wait(
td, TO_PTR(sub.condvar.condvar),
sub.condvar.condvar_scope,
TO_PTR(sub.condvar.lock),
sub.condvar.lock_scope,
CLOUDABI_CLOCK_MONOTONIC, UINT64_MAX, 0));
td->td_retval[0] = 1;
return (copyout(&ev, uap->out, sizeof(ev)));
} else if (sub.type == CLOUDABI_EVENTTYPE_LOCK_RDLOCK) {
/* Acquire a read lock. */
ev.lock.lock = sub.lock.lock;
ev.error = cloudabi_convert_errno(
cloudabi_futex_lock_rdlock(
td, TO_PTR(sub.lock.lock),
sub.lock.lock_scope, CLOUDABI_CLOCK_MONOTONIC,
UINT64_MAX, 0));
td->td_retval[0] = 1;
return (copyout(&ev, uap->out, sizeof(ev)));
} else if (sub.type == CLOUDABI_EVENTTYPE_LOCK_WRLOCK) {
/* Acquire a write lock. */
ev.lock.lock = sub.lock.lock;
ev.error = cloudabi_convert_errno(
cloudabi_futex_lock_wrlock(
td, TO_PTR(sub.lock.lock),
sub.lock.lock_scope, CLOUDABI_CLOCK_MONOTONIC,
UINT64_MAX, 0));
td->td_retval[0] = 1;
return (copyout(&ev, uap->out, sizeof(ev)));
}
} else if (uap->nsubscriptions == 2) {
cloudabi64_subscription_t sub[2];
cloudabi64_event_t ev[2] = {};
int error;
error = copyin(uap->in, &sub, sizeof(sub));
if (error != 0)
return (error);
ev[0].userdata = sub[0].userdata;
ev[0].type = sub[0].type;
ev[1].userdata = sub[1].userdata;
ev[1].type = sub[1].type;
if (sub[0].type == CLOUDABI_EVENTTYPE_CONDVAR &&
sub[1].type == CLOUDABI_EVENTTYPE_CLOCK &&
sub[1].clock.flags == CLOUDABI_SUBSCRIPTION_CLOCK_ABSTIME) {
/* Wait for a condition variable with timeout. */
ev[0].condvar.condvar = sub[0].condvar.condvar;
ev[1].clock.identifier = sub[1].clock.identifier;
error = cloudabi_futex_condvar_wait(
td, TO_PTR(sub[0].condvar.condvar),
sub[0].condvar.condvar_scope,
TO_PTR(sub[0].condvar.lock),
sub[0].condvar.lock_scope, sub[1].clock.clock_id,
sub[1].clock.timeout, sub[1].clock.precision);
if (error == ETIMEDOUT) {
td->td_retval[0] = 1;
return (copyout(&ev[1], uap->out,
sizeof(ev[1])));
}
ev[0].error = cloudabi_convert_errno(error);
td->td_retval[0] = 1;
return (copyout(&ev[0], uap->out, sizeof(ev[0])));
} else if (sub[0].type == CLOUDABI_EVENTTYPE_LOCK_RDLOCK &&
sub[1].type == CLOUDABI_EVENTTYPE_CLOCK &&
sub[1].clock.flags == CLOUDABI_SUBSCRIPTION_CLOCK_ABSTIME) {
/* Acquire a read lock with a timeout. */
ev[0].lock.lock = sub[0].lock.lock;
ev[1].clock.identifier = sub[1].clock.identifier;
error = cloudabi_futex_lock_rdlock(
td, TO_PTR(sub[0].lock.lock),
sub[0].lock.lock_scope, sub[1].clock.clock_id,
sub[1].clock.timeout, sub[1].clock.precision);
if (error == ETIMEDOUT) {
td->td_retval[0] = 1;
return (copyout(&ev[1], uap->out,
sizeof(ev[1])));
}
ev[0].error = cloudabi_convert_errno(error);
td->td_retval[0] = 1;
return (copyout(&ev[0], uap->out, sizeof(ev[0])));
} else if (sub[0].type == CLOUDABI_EVENTTYPE_LOCK_WRLOCK &&
sub[1].type == CLOUDABI_EVENTTYPE_CLOCK &&
sub[1].clock.flags == CLOUDABI_SUBSCRIPTION_CLOCK_ABSTIME) {
/* Acquire a write lock with a timeout. */
ev[0].lock.lock = sub[0].lock.lock;
ev[1].clock.identifier = sub[1].clock.identifier;
error = cloudabi_futex_lock_wrlock(
td, TO_PTR(sub[0].lock.lock),
sub[0].lock.lock_scope, sub[1].clock.clock_id,
sub[1].clock.timeout, sub[1].clock.precision);
if (error == ETIMEDOUT) {
td->td_retval[0] = 1;
return (copyout(&ev[1], uap->out,
sizeof(ev[1])));
}
ev[0].error = cloudabi_convert_errno(error);
td->td_retval[0] = 1;
return (copyout(&ev[0], uap->out, sizeof(ev[0])));
}
}
return (kern_kevent_anonymous(td, uap->nsubscriptions, &copyops));
}
int
cloudabi64_sys_poll_fd(struct thread *td,
struct cloudabi64_sys_poll_fd_args *uap)
{
struct cloudabi64_kevent_args args = {
.in = uap->in,
.out = uap->out,
.once = false,
};
struct kevent_copyops copyops = {
.k_copyin = cloudabi64_kevent_copyin,
.k_copyout = cloudabi64_kevent_copyout,
.arg = &args,
};
cloudabi64_subscription_t subtimo;
struct timespec timeout;
int error;
if (uap->timeout != NULL) {
/* Poll with a timeout. */
error = copyin(uap->timeout, &subtimo, sizeof(subtimo));
if (error != 0)
return (error);
if (subtimo.type != CLOUDABI_EVENTTYPE_CLOCK ||
subtimo.clock.flags != 0)
return (EINVAL);
timeout.tv_sec = subtimo.clock.timeout / 1000000000;
timeout.tv_nsec = subtimo.clock.timeout % 1000000000;
return (kern_kevent(td, uap->fd, uap->in_len, uap->out_len,
&copyops, &timeout));
} else {
/* Poll without a timeout. */
return (kern_kevent(td, uap->fd, uap->in_len, uap->out_len,
&copyops, NULL));
}
}