#include #include #include #include #include #include #include #include #include #include #include #include "epoll_shim_ctx.h" #ifdef __NetBSD__ #define ppoll pollts #endif // TODO(jan): Remove this once the definition is exposed in in // all supported FreeBSD versions. #ifndef timespecsub #define timespecsub(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ if ((vsp)->tv_nsec < 0) { \ (vsp)->tv_sec--; \ (vsp)->tv_nsec += 1000000000L; \ } \ } while (0) #endif static errno_t epollfd_close(FDContextMapNode *node) { return epollfd_ctx_terminate(&node->ctx.epollfd); } static FDContextVTable const epollfd_vtable = { .read_fun = fd_context_default_read, .write_fun = fd_context_default_write, .close_fun = epollfd_close, }; static FDContextMapNode * epoll_create_impl(errno_t *ec) { FDContextMapNode *node; node = epoll_shim_ctx_create_node(&epoll_shim_ctx, ec); if (!node) { return NULL; } node->flags = 0; if ((*ec = epollfd_ctx_init(&node->ctx.epollfd, /**/ node->fd)) != 0) { goto fail; } node->vtable = &epollfd_vtable; return node; fail: epoll_shim_ctx_remove_node_explicit(&epoll_shim_ctx, node); (void)fd_context_map_node_destroy(node); return NULL; } static int epoll_create_common(void) { FDContextMapNode *node; errno_t ec; node = epoll_create_impl(&ec); if (!node) { errno = ec; return -1; } return node->fd; } int epoll_create(int size) { if (size <= 0) { errno = EINVAL; return -1; } return epoll_create_common(); } int epoll_create1(int flags) { if (flags & ~EPOLL_CLOEXEC) { errno = EINVAL; return -1; } return epoll_create_common(); } static errno_t epoll_ctl_impl(int fd, int op, int fd2, struct epoll_event *ev) { if (!ev && op != EPOLL_CTL_DEL) { return EFAULT; } FDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); if (!node || node->vtable != &epollfd_vtable) { struct stat sb; return (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL; } return epollfd_ctx_ctl(&node->ctx.epollfd, op, fd2, ev); } int epoll_ctl(int fd, int op, int fd2, struct epoll_event *ev) { errno_t ec = epoll_ctl_impl(fd, op, fd2, ev); if (ec != 0) { errno = ec; return -1; } return 0; } static bool is_no_wait_deadline(struct timespec const *deadline) { return (deadline && deadline->tv_sec == 0 && deadline->tv_nsec == 0); } static errno_t epollfd_ctx_wait_or_block(EpollFDCtx *epollfd, struct epoll_event *ev, int cnt, int *actual_cnt, struct timespec const *deadline, sigset_t const *sigs) { errno_t ec; for (;;) { if ((ec = epollfd_ctx_wait(epollfd, /**/ ev, cnt, actual_cnt)) != 0) { return ec; } if (*actual_cnt || is_no_wait_deadline(deadline)) { return 0; } struct timespec timeout; if (deadline) { struct timespec current_time; if (clock_gettime(CLOCK_MONOTONIC, /**/ ¤t_time) < 0) { return errno; } timespecsub(deadline, ¤t_time, &timeout); if (timeout.tv_sec < 0 || is_no_wait_deadline(&timeout)) { return 0; } } (void)pthread_mutex_lock(&epollfd->mutex); nfds_t nfds = (nfds_t)(1 + epollfd->poll_fds_size); size_t size; if (__builtin_mul_overflow(nfds, sizeof(struct pollfd), &size)) { ec = ENOMEM; (void)pthread_mutex_unlock(&epollfd->mutex); return ec; } struct pollfd *pfds = malloc(size); if (!pfds) { ec = errno; (void)pthread_mutex_unlock(&epollfd->mutex); return ec; } epollfd_ctx_fill_pollfds(epollfd, pfds); (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); ++epollfd->nr_polling_threads; (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); (void)pthread_mutex_unlock(&epollfd->mutex); /* * This surfaced a race condition when * registering/unregistering poll-only fds. The tests should * still succeed if this is enabled. */ #if 0 usleep(500000); #endif int n = ppoll(pfds, nfds, deadline ? &timeout : NULL, sigs); if (n < 0) { ec = errno; } free(pfds); (void)pthread_mutex_lock(&epollfd->nr_polling_threads_mutex); --epollfd->nr_polling_threads; if (epollfd->nr_polling_threads == 0) { (void)pthread_cond_signal( &epollfd->nr_polling_threads_cond); } (void)pthread_mutex_unlock(&epollfd->nr_polling_threads_mutex); if (n < 0) { return ec; } } } static errno_t timeout_to_deadline(struct timespec *deadline, int to) { assert(to >= 0); if (to == 0) { *deadline = (struct timespec){0, 0}; } else if (to > 0) { if (clock_gettime(CLOCK_MONOTONIC, deadline) < 0) { return errno; } if (__builtin_add_overflow(deadline->tv_sec, to / 1000 + 1, &deadline->tv_sec)) { return EINVAL; } deadline->tv_sec -= 1; deadline->tv_nsec += (to % 1000) * 1000000L; if (deadline->tv_nsec >= 1000000000) { deadline->tv_nsec -= 1000000000; deadline->tv_sec += 1; } } return 0; } static errno_t epoll_pwait_impl(int fd, struct epoll_event *ev, int cnt, int to, sigset_t const *sigs, int *actual_cnt) { if (cnt < 1 || cnt > (int)(INT_MAX / sizeof(struct epoll_event))) { return EINVAL; } FDContextMapNode *node = epoll_shim_ctx_find_node(&epoll_shim_ctx, fd); if (!node || node->vtable != &epollfd_vtable) { struct stat sb; return (fd < 0 || fstat(fd, &sb) < 0) ? EBADF : EINVAL; } struct timespec deadline; errno_t ec; if (to >= 0 && (ec = timeout_to_deadline(&deadline, to)) != 0) { return ec; } return epollfd_ctx_wait_or_block(&node->ctx.epollfd, ev, cnt, actual_cnt, (to >= 0) ? &deadline : NULL, sigs); } int epoll_pwait(int fd, struct epoll_event *ev, int cnt, int to, sigset_t const *sigs) { int actual_cnt; errno_t ec = epoll_pwait_impl(fd, ev, cnt, to, sigs, &actual_cnt); if (ec != 0) { errno = ec; return -1; } return actual_cnt; } int epoll_wait(int fd, struct epoll_event *ev, int cnt, int to) { return epoll_pwait(fd, ev, cnt, to, NULL); }