aboutsummaryrefslogblamecommitdiff
path: root/sys/compat/cloudabi64/cloudabi64_poll.c
blob: e44d69f2e9730d50bc92bb7613d00835741eb6d0 (plain) (tree)



























                                                                             



                            

                                              

                                          

                                               


































































































































































































                                                                                


                                                                            









                                                            
 



                                                                    
                                       









































                                                                              
                                              








































































                                                                                
                                                                          
 




                                            












                                                            
 
















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

/* 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 = (void *)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, (cloudabi_condvar_t *)sub.condvar.condvar,
			        sub.condvar.condvar_scope,
			        (cloudabi_lock_t *)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, (cloudabi_lock_t *)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, (cloudabi_lock_t *)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, (cloudabi_condvar_t *)sub[0].condvar.condvar,
			    sub[0].condvar.condvar_scope,
			    (cloudabi_lock_t *)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, (cloudabi_lock_t *)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, (cloudabi_lock_t *)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->nin, uap->nout, &copyops,
		    &timeout));
	} else {
		/* Poll without a timeout. */
		return (kern_kevent(td, uap->fd, uap->nin, uap->nout, &copyops,
		    NULL));
	}
}