blob: 0eb99ed19d5da940950eb58cffeda0fda2abe7a4 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <fcntl.h>
#include <pthread.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/io.h>
#include <lib/fdio/spawn.h>
#include <lib/fdio/unsafe.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
// This is a very inefficient way to emulate select() by creating a port, adding all of the fds
// of interest as async waits, and blocking until we get a port packet back.
// The callers of this (like serverloop) generally have a static set of fds they care about, so
// it'd be much more efficient for them to register async waits on a port object that persists
// across blocking calls.
int fuchsia_select(int nfds, void* readfds, void* writefds, struct timeval *timeout) {
fd_set* readfds_fd_set = (fd_set*) readfds;
fd_set* writefds_fd_set = (fd_set*) writefds;
int ret = 0;
zx_handle_t port = ZX_HANDLE_INVALID;
fdio_t* ios[FD_SETSIZE] = { 0 };
// Create a fresh port for this wait.
zx_status_t st = zx_port_create(0, &port);
if (st != ZX_OK) {
fprintf(stderr, "Can't allocate new port.\n");
errno = EINVAL;
ret = -1;
goto cleanup;
}
// Register port waits for file descriptors in the read and write sets.
for (int fd = 0; fd < nfds; ++fd) {
uint32_t events = 0;
if (readfds_fd_set && FD_ISSET(fd, readfds_fd_set)) {
events |= POLLIN;
}
if (writefds_fd_set && FD_ISSET(fd, writefds_fd_set)) {
events |= POLLOUT;
}
if (!events)
continue;
fdio_t* io;
// This acquires a reference to the fdio which is released in the cleanup path below.
if ((io = fdio_unsafe_fd_to_io(fd)) == NULL) {
errno = EBADF;
ret = -1;
goto cleanup;
}
ios[fd] = io;
zx_handle_t h;
zx_signals_t sigs;
// Translate the poll-style events to fdio-specific signal bits to wait on.
fdio_unsafe_wait_begin(io, events, &h, &sigs);
if (h == ZX_HANDLE_INVALID) {
errno = EBADF;
ret = -1;
goto cleanup;
}
uint64_t key = fd;
st = zx_object_wait_async(h, port, key, sigs, 0);
if (st != ZX_OK) {
fprintf(stderr, "Can't wait on object %d.\n", st);
errno = EINVAL;
ret = -1;
goto cleanup;
}
}
zx_time_t deadline = (timeout == NULL) ? ZX_TIME_INFINITE :
zx_deadline_after(ZX_SEC(timeout->tv_sec) + ZX_USEC(timeout->tv_usec));
for (;;) {
zx_port_packet_t packet;
st = zx_port_wait(port, deadline, &packet);
// We expect zx_port_wait to return either ZX_ERR_TIMED_OUT if nothing happened, or ZX_OK
// if at least one thing happened.
if (st == ZX_OK) {
if (packet.type != ZX_PKT_TYPE_SIGNAL_ONE) {
fprintf(stderr, "Unexpected port packet type %u\n", packet.type);
errno = EINVAL;
ret = -1;
goto cleanup;
}
// We've heard about an fd in the set we care about. Update the read/write
// sets to reflect this information, then remove them from the set we are
// listening to.
int fd = (int)packet.key;
uint32_t events = 0;
fdio_t* io = ios[fd];
if (!io) {
fprintf(stderr, "Can't find fd for packet key %d.\n", fd);
errno = EINVAL;
ret = -1;
goto cleanup;
}
// fdio_unsafe_wait_end translates the signals back to poll-style flags.
fdio_unsafe_wait_end(io, packet.signal.observed, &events);
if (readfds_fd_set && FD_ISSET(fd, readfds_fd_set)) {
if (events & POLLIN)
ret++;
else
FD_CLR(fd, readfds_fd_set);
}
if (writefds_fd_set && FD_ISSET(fd, writefds_fd_set)) {
if (events & POLLOUT)
ret++;
else
FD_CLR(fd, writefds_fd_set);
}
// The read and write sets for this fd are now updated, and our wait has expired, so
// remove this fd from the set of things we care about.
ios[fd] = NULL;
fdio_unsafe_release(io);
} else if (st == ZX_ERR_TIMED_OUT) {
break;
} else {
fprintf(stderr, "Port wait return unexpected error %d.\n", st);
errno = EINVAL;
ret = -1;
goto cleanup;
}
// After pulling the first packet out, poll without blocking by doing another wait with a
// deadline in the past. This will populate any other members of the read/write set that
// are ready to go now.
deadline = 0;
}
// If there are any entries left in ios at this point, we have not received a port packet
// indicating that those fds are readable or writable and so we should clear those from the
// read/write sets.
for (int fd = 0; fd < nfds; ++fd) {
if (ios[fd]) {
if (readfds_fd_set && FD_ISSET(fd, readfds_fd_set)) {
FD_CLR(fd, readfds_fd_set);
}
if (writefds_fd_set && FD_ISSET(fd, writefds_fd_set)) {
FD_CLR(fd, writefds_fd_set);
}
}
}
cleanup:
// Release reference to any fdio objects we acquired with fdio_unsafe_fd_to_io().
for (int fd = 0; fd < nfds; ++fd) {
if (ios[fd]) {
fdio_unsafe_release(ios[fd]);
}
}
if (port != ZX_HANDLE_INVALID) {
zx_handle_close(port);
}
return ret;
}