diff --git a/CMakeLists.txt b/CMakeLists.txt index cac8b3b..796707b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ set(uvwasi_sources src/clocks.c src/fd_table.c src/path_resolver.c + src/poll_oneoff.c src/uv_mapping.c src/uvwasi.c src/wasi_rights.c diff --git a/src/poll_oneoff.c b/src/poll_oneoff.c new file mode 100644 index 0000000..80d9db9 --- /dev/null +++ b/src/poll_oneoff.c @@ -0,0 +1,267 @@ +#include "uv.h" +#include "poll_oneoff.h" +#include "uv_mapping.h" +#include "uvwasi_alloc.h" + + +static void poll_cb(uv_poll_t* handle, int status, int events) { + struct uvwasi_poll_oneoff_state_t* state; + struct uvwasi__poll_fdevent_t* event; + + uv_poll_stop(handle); + event = uv_handle_get_data((uv_handle_t*) handle); + event->revents = events; + + if (status != 0) + event->error = UVWASI_EIO; + + state = uv_loop_get_data(handle->loop); + state->result++; +} + + +static void timeout_cb(uv_timer_t* handle) { + struct uvwasi_poll_oneoff_state_t* state; + size_t i; + + state = uv_loop_get_data(handle->loop); + + for (i = 0; i < state->handle_cnt; i++) + uv_poll_stop(&state->poll_handles[i]); +} + + +uvwasi_errno_t uvwasi__poll_oneoff_state_init( + uvwasi_t* uvwasi, + struct uvwasi_poll_oneoff_state_t* state, + size_t max_fds + ) { + uvwasi_errno_t err; + int r; + + if (uvwasi == NULL || state == NULL) + return UVWASI_EINVAL; + + state->uvwasi = NULL; + state->timeout = 0; + state->has_timer = 0; + state->fdevents = NULL; + state->poll_handles = NULL; + state->max_fds = 0; + state->fdevent_cnt = 0; + state->handle_cnt = 0; + state->result = 0; + + r = uv_loop_init(&state->loop); + if (r != 0) + return uvwasi__translate_uv_error(r); + + if (max_fds > 0) { + state->fdevents = uvwasi__calloc(uvwasi, + max_fds, + sizeof(*state->fdevents)); + if (state->fdevents == NULL) { + err = UVWASI_ENOMEM; + goto error_exit; + } + + state->poll_handles = uvwasi__calloc(uvwasi, + max_fds, + sizeof(*state->poll_handles)); + if (state->poll_handles == NULL) { + err = UVWASI_ENOMEM; + goto error_exit; + } + } + + uv_loop_set_data(&state->loop, (void*) state); + state->uvwasi = uvwasi; + state->max_fds = max_fds; + + return UVWASI_ESUCCESS; + +error_exit: + uv_loop_close(&state->loop); + uvwasi__free(state->uvwasi, state->fdevents); + uvwasi__free(state->uvwasi, state->poll_handles); + return err; +} + + +uvwasi_errno_t uvwasi__poll_oneoff_state_cleanup( + struct uvwasi_poll_oneoff_state_t* state + ) { + struct uvwasi__poll_fdevent_t* event; + size_t i; + int r; + + if (state == NULL) + return UVWASI_EINVAL; + + if (state->has_timer != 0) { + state->timeout = 0; + state->has_timer = 0; + uv_close((uv_handle_t*) &state->timer, NULL); + } + + for (i = 0; i < state->fdevent_cnt; i++) { + event = &state->fdevents[i]; + + if (event->is_duplicate_fd == 0 && event->wrap != NULL) + uv_mutex_unlock(&event->wrap->mutex); + } + + for (i = 0; i < state->handle_cnt; i++) + uv_close((uv_handle_t*) &state->poll_handles[i], NULL); + + state->max_fds = 0; + state->fdevent_cnt = 0; + state->handle_cnt = 0; + + uvwasi__free(state->uvwasi, state->fdevents); + uvwasi__free(state->uvwasi, state->poll_handles); + state->fdevents = NULL; + state->poll_handles = NULL; + state->uvwasi = NULL; + + r = uv_loop_close(&state->loop); + if (r != 0) + return uvwasi__translate_uv_error(r); + + return UVWASI_ESUCCESS; +} + + +uvwasi_errno_t uvwasi__poll_oneoff_state_set_timer( + struct uvwasi_poll_oneoff_state_t* state, + uvwasi_timestamp_t timeout + ) { + int r; + + if (state == NULL) + return UVWASI_EINVAL; + + r = uv_timer_init(&state->loop, &state->timer); + if (r != 0) + return uvwasi__translate_uv_error(r); + + /* Convert WASI timeout from nanoseconds to milliseconds for libuv. */ + state->timeout = timeout / 1000000; + state->has_timer = 1; + return UVWASI_ESUCCESS; +} + + +uvwasi_errno_t uvwasi__poll_oneoff_state_add_fdevent( + struct uvwasi_poll_oneoff_state_t* state, + uvwasi_subscription_t* subscription + ) { + struct uvwasi__poll_fdevent_t* event; + struct uvwasi__poll_fdevent_t* dup; + uv_poll_t* poll_handle; + uvwasi_eventtype_t type; + uvwasi_rights_t rights; + uvwasi_fd_t fd; + uvwasi_errno_t err; + size_t i; + int r; + + if (state == NULL) + return UVWASI_EINVAL; + + event = &state->fdevents[state->fdevent_cnt]; + fd = subscription->u.fd_readwrite.fd; + type = subscription->type; + + if (type == UVWASI_EVENTTYPE_FD_READ) { + event->events = UV_DISCONNECT | UV_READABLE; + rights = UVWASI_RIGHT_POLL_FD_READWRITE | UVWASI_RIGHT_FD_READ; + } else if (type == UVWASI_EVENTTYPE_FD_WRITE) { + event->events = UV_DISCONNECT | UV_WRITABLE; + rights = UVWASI_RIGHT_POLL_FD_READWRITE | UVWASI_RIGHT_FD_WRITE; + } else { + return UVWASI_EINVAL; + } + + /* Check if the same file descriptor is already being polled. If so, use the + wrap and poll handle from the first descriptor. The reasons are that libuv + does not support polling the same fd more than once at the same time, and + uvwasi has the fd's mutex locked. */ + event->is_duplicate_fd = 0; + for (i = 0; i < state->fdevent_cnt; i++) { + dup = &state->fdevents[i]; + if (dup->wrap->id == fd) { + event->is_duplicate_fd = 1; + event->wrap = dup->wrap; + event->poll_handle = dup->poll_handle; + err = event->error; + goto poll_config_done; + } + } + + /* Get the file descriptor. If UVWASI_EBADF is returned, continue on, but + don't do any polling with the handle. */ + err = uvwasi_fd_table_get(&state->uvwasi->fds, fd, &event->wrap, rights, 0); + if (err == UVWASI_EBADF) + event->wrap = NULL; + else if (err != UVWASI_ESUCCESS) + return err; + + if (err == UVWASI_ESUCCESS) { + /* The fd is valid, so setup the poll handle. */ + poll_handle = &state->poll_handles[state->handle_cnt]; + r = uv_poll_init(&state->loop, poll_handle, event->wrap->fd); + + if (r != 0) { + /* If uv_poll_init() fails (for example on Windows because only sockets + are supported), set the error for this event to UVWASI_EBADF, but don't + do any polling with the handle. */ + uv_mutex_unlock(&event->wrap->mutex); + return uvwasi__translate_uv_error(r); + } else { + r = uv_poll_start(poll_handle, + event->events, + poll_cb); + if (r != 0) { + uv_mutex_unlock(&event->wrap->mutex); + uv_close((uv_handle_t*) poll_handle, NULL); + return uvwasi__translate_uv_error(r); + } + + uv_handle_set_data((uv_handle_t*) poll_handle, + (void*) &state->fdevents[state->fdevent_cnt]); + event->poll_handle = poll_handle; + state->handle_cnt++; + } + } + +poll_config_done: + event->type = type; + event->userdata = subscription->userdata; + event->error = err; + event->revents = 0; + state->fdevent_cnt++; + return UVWASI_ESUCCESS; +} + + +uvwasi_errno_t uvwasi__poll_oneoff_run( + struct uvwasi_poll_oneoff_state_t* state + ) { + int r; + + if (state->has_timer == 1) { + r = uv_timer_start(&state->timer, timeout_cb, state->timeout, 0); + if (r != 0) + return uvwasi__translate_uv_error(r); + + if (state->fdevent_cnt > 0) + uv_unref((uv_handle_t*) &state->timer); + } + + r = uv_run(&state->loop, UV_RUN_DEFAULT); + if (r != 0) + return uvwasi__translate_uv_error(r); + + return UVWASI_ESUCCESS; +} diff --git a/src/poll_oneoff.h b/src/poll_oneoff.h new file mode 100644 index 0000000..cf3700a --- /dev/null +++ b/src/poll_oneoff.h @@ -0,0 +1,59 @@ +#ifndef __UVWASI_POLL_ONEOFF_H__ +#define __UVWASI_POLL_ONEOFF_H__ + +#include "wasi_types.h" + +struct uvwasi_s; + +struct uvwasi__poll_fdevent_t { + struct uvwasi_fd_wrap_t* wrap; + uvwasi_userdata_t userdata; + uvwasi_eventtype_t type; + uvwasi_errno_t error; + uv_poll_t* poll_handle; + int is_duplicate_fd; + int events; + int revents; +}; + +struct uvwasi_poll_oneoff_state_t { + struct uvwasi_s* uvwasi; + struct uvwasi__poll_fdevent_t* fdevents; + uv_poll_t* poll_handles; + uv_timer_t timer; + uint64_t timeout; + uv_loop_t loop; + size_t max_fds; + int has_timer; + size_t fdevent_cnt; + size_t handle_cnt; + int result; +}; + + +uvwasi_errno_t uvwasi__poll_oneoff_state_init( + struct uvwasi_s* uvwasi, + struct uvwasi_poll_oneoff_state_t* state, + size_t max_fds + ); + +uvwasi_errno_t uvwasi__poll_oneoff_state_cleanup( + struct uvwasi_poll_oneoff_state_t* state + ); + +uvwasi_errno_t uvwasi__poll_oneoff_state_set_timer( + struct uvwasi_poll_oneoff_state_t* state, + uvwasi_timestamp_t timeout + ); + +uvwasi_errno_t uvwasi__poll_oneoff_state_add_fdevent( + struct uvwasi_poll_oneoff_state_t* state, + uvwasi_subscription_t* subscription + ); + +uvwasi_errno_t uvwasi__poll_oneoff_run( + struct uvwasi_poll_oneoff_state_t* state + ); + + +#endif /* __UVWASI_POLL_ONEOFF_H__ */ diff --git a/src/uvwasi.c b/src/uvwasi.c index f7ac98f..e9e8e8d 100644 --- a/src/uvwasi.c +++ b/src/uvwasi.c @@ -20,6 +20,7 @@ #include "fd_table.h" #include "clocks.h" #include "path_resolver.h" +#include "poll_oneoff.h" #include "wasi_rights.h" #include "debug.h" @@ -2193,6 +2194,18 @@ uvwasi_errno_t uvwasi_poll_oneoff(uvwasi_t* uvwasi, uvwasi_event_t* out, size_t nsubscriptions, size_t* nevents) { + struct uvwasi_poll_oneoff_state_t state; + struct uvwasi__poll_fdevent_t* fdevent; + uvwasi_userdata_t timer_userdata; + uvwasi_timestamp_t min_timeout; + uvwasi_timestamp_t cur_timeout; + uvwasi_timestamp_t now; + uvwasi_subscription_t sub; + uvwasi_event_t* event; + uvwasi_errno_t err; + int has_timeout; + size_t i; + DEBUG("uvwasi_poll_oneoff(uvwasi=%p, in=%p, out=%p, nsubscriptions=%zu, " "nevents=%p)\n", uvwasi, @@ -2201,8 +2214,100 @@ uvwasi_errno_t uvwasi_poll_oneoff(uvwasi_t* uvwasi, nsubscriptions, nevents); - /* TODO(cjihrig): Implement this. */ - return UVWASI_ENOTSUP; + if (uvwasi == NULL || in == NULL || out == NULL || + nsubscriptions == 0 || nevents == NULL) { + return UVWASI_EINVAL; + } + + *nevents = 0; + err = uvwasi__poll_oneoff_state_init(uvwasi, &state, nsubscriptions); + if (err != UVWASI_ESUCCESS) + return err; + + has_timeout = 0; + min_timeout = 0; + + for (i = 0; i < nsubscriptions; i++) { + sub = in[i]; + + switch (sub.type) { + case UVWASI_EVENTTYPE_CLOCK: + if (sub.u.clock.flags == UVWASI_SUBSCRIPTION_CLOCK_ABSTIME) { + /* Convert absolute time to relative delay. */ + err = uvwasi__clock_gettime_realtime(&now); + if (err != UVWASI_ESUCCESS) + goto exit; + + cur_timeout = sub.u.clock.timeout - now; + } else { + cur_timeout = sub.u.clock.timeout; + } + + if (has_timeout == 0 || cur_timeout < min_timeout) { + min_timeout = cur_timeout; + timer_userdata = sub.userdata; + has_timeout = 1; + } + + break; + case UVWASI_EVENTTYPE_FD_READ: + case UVWASI_EVENTTYPE_FD_WRITE: + err = uvwasi__poll_oneoff_state_add_fdevent(&state, &sub); + if (err != UVWASI_ESUCCESS) + goto exit; + + break; + default: + err = UVWASI_EINVAL; + goto exit; + } + } + + if (has_timeout == 1) { + err = uvwasi__poll_oneoff_state_set_timer(&state, min_timeout); + if (err != UVWASI_ESUCCESS) + goto exit; + } + + /* Handle poll() errors, then timeouts, then happy path. */ + err = uvwasi__poll_oneoff_run(&state); + if (err != UVWASI_ESUCCESS) { + goto exit; + } else if (state.result == 0) { + event = &out[0]; + event->userdata = timer_userdata; + event->error = UVWASI_ESUCCESS; + event->type = UVWASI_EVENTTYPE_CLOCK; + *nevents = 1; + } else { + for (i = 0; i < state.fdevent_cnt; i++) { + fdevent = &state.fdevents[i]; + event = &out[*nevents]; + + event->userdata = fdevent->userdata; + event->error = fdevent->error; + event->type = fdevent->type; + event->u.fd_readwrite.nbytes = 0; + event->u.fd_readwrite.flags = 0; + + if (fdevent->error != UVWASI_ESUCCESS) + ; + else if ((fdevent->revents & UV_DISCONNECT) != 0) + event->u.fd_readwrite.flags = UVWASI_EVENT_FD_READWRITE_HANGUP; + else if ((fdevent->revents & (UV_READABLE | UV_WRITABLE)) != 0) + ; /* TODO(cjihrig): Set nbytes if type is UVWASI_EVENTTYPE_FD_READ. */ + else + continue; + + *nevents = *nevents + 1; + } + } + + err = UVWASI_ESUCCESS; + +exit: + uvwasi__poll_oneoff_state_cleanup(&state); + return err; } diff --git a/test/test-einval-input-validation.c b/test/test-einval-input-validation.c index e246ae3..3bb0b75 100644 --- a/test/test-einval-input-validation.c +++ b/test/test-einval-input-validation.c @@ -20,6 +20,8 @@ int main(void) { uvwasi_prestat_t test_prestat; uvwasi_dircookie_t test_dircookie = 0; uvwasi_filesize_t test_filesize; + uvwasi_subscription_t test_sub; + uvwasi_event_t test_event; uvwasi_fd_t test_fd; test_void = (void*) &test_fdstat; @@ -145,7 +147,11 @@ int main(void) { CHECK(uvwasi_path_unlink_file(NULL, 3, test_str, 10)); CHECK(uvwasi_path_unlink_file(&uvw, 3, NULL, 10)); - /* TODO(cjihrig): Add uvwasi_poll_oneoff() tests. */ + CHECK(uvwasi_poll_oneoff(NULL, &test_sub, &test_event, 5, &test_size)); + CHECK(uvwasi_poll_oneoff(&uvw, NULL, &test_event, 5, &test_size)); + CHECK(uvwasi_poll_oneoff(&uvw, &test_sub, NULL, 5, &test_size)); + CHECK(uvwasi_poll_oneoff(&uvw, &test_sub, &test_event, 0, &test_size)); + CHECK(uvwasi_poll_oneoff(&uvw, &test_sub, &test_event, 5, NULL)); CHECK(uvwasi_proc_raise(NULL, UVWASI_SIGUSR2)); diff --git a/test/test-enotsup-apis.c b/test/test-enotsup-apis.c index 95bac35..fdf0c0d 100644 --- a/test/test-enotsup-apis.c +++ b/test/test-enotsup-apis.c @@ -3,7 +3,6 @@ int main(void) { /* TODO(cjihrig): This test is intended to be temporary. */ - assert(UVWASI_ENOTSUP == uvwasi_poll_oneoff(NULL, NULL, NULL, 0, NULL)); assert(UVWASI_ENOTSUP == uvwasi_sock_recv(NULL, 0, NULL, 0, 0, NULL, NULL)); assert(UVWASI_ENOTSUP == uvwasi_sock_send(NULL, 0, NULL, 0, 0, NULL)); assert(UVWASI_ENOTSUP == uvwasi_sock_shutdown(NULL, 0, 0));