| // 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, ZX_WAIT_ASYNC_ONCE); |
| 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; |
| } |