aboutsummaryrefslogtreecommitdiff
path: root/src/eloop.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/eloop.c')
-rw-r--r--src/eloop.c787
1 files changed, 787 insertions, 0 deletions
diff --git a/src/eloop.c b/src/eloop.c
new file mode 100644
index 000000000000..a6ab43fbaef7
--- /dev/null
+++ b/src/eloop.c
@@ -0,0 +1,787 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * eloop - portable event based main loop.
+ * Copyright (c) 2006-2021 Roy Marples <roy@marples.name>
+ * All rights reserved.
+
+ * 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/time.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* config.h should define HAVE_PPOLL, etc. */
+#if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_H)
+#include "config.h"
+#endif
+
+#if defined(HAVE_PPOLL)
+#elif defined(HAVE_POLLTS)
+#define ppoll pollts
+#elif !defined(HAVE_PSELECT)
+#pragma message("Compiling eloop with pselect(2) support.")
+#define HAVE_PSELECT
+#define ppoll eloop_ppoll
+#endif
+
+#include "eloop.h"
+
+#ifndef UNUSED
+#define UNUSED(a) (void)((a))
+#endif
+#ifndef __unused
+#ifdef __GNUC__
+#define __unused __attribute__((__unused__))
+#else
+#define __unused
+#endif
+#endif
+
+#ifdef HAVE_PSELECT
+#include <sys/select.h>
+#endif
+
+/* Our structures require TAILQ macros, which really every libc should
+ * ship as they are useful beyond belief.
+ * Sadly some libc's don't have sys/queue.h and some that do don't have
+ * the TAILQ_FOREACH macro. For those that don't, the application using
+ * this implementation will need to ship a working queue.h somewhere.
+ * If we don't have sys/queue.h found in config.h, then
+ * allow QUEUE_H to override loading queue.h in the current directory. */
+#ifndef TAILQ_FOREACH
+#ifdef HAVE_SYS_QUEUE_H
+#include <sys/queue.h>
+#elif defined(QUEUE_H)
+#define __QUEUE_HEADER(x) #x
+#define _QUEUE_HEADER(x) __QUEUE_HEADER(x)
+#include _QUEUE_HEADER(QUEUE_H)
+#else
+#include "queue.h"
+#endif
+#endif
+
+#ifdef ELOOP_DEBUG
+#include <stdio.h>
+#endif
+
+/*
+ * Allow a backlog of signals.
+ * If you use many eloops in the same process, they should all
+ * use the same signal handler or have the signal handler unset.
+ * Otherwise the signal might not behave as expected.
+ */
+#define ELOOP_NSIGNALS 5
+
+/*
+ * time_t is a signed integer of an unspecified size.
+ * To adjust for time_t wrapping, we need to work the maximum signed
+ * value and use that as a maximum.
+ */
+#ifndef TIME_MAX
+#define TIME_MAX ((1ULL << (sizeof(time_t) * NBBY - 1)) - 1)
+#endif
+/* The unsigned maximum is then simple - multiply by two and add one. */
+#ifndef UTIME_MAX
+#define UTIME_MAX (TIME_MAX * 2) + 1
+#endif
+
+struct eloop_event {
+ TAILQ_ENTRY(eloop_event) next;
+ int fd;
+ void (*read_cb)(void *);
+ void *read_cb_arg;
+ void (*write_cb)(void *);
+ void *write_cb_arg;
+ struct pollfd *pollfd;
+};
+
+struct eloop_timeout {
+ TAILQ_ENTRY(eloop_timeout) next;
+ unsigned int seconds;
+ unsigned int nseconds;
+ void (*callback)(void *);
+ void *arg;
+ int queue;
+};
+
+struct eloop {
+ TAILQ_HEAD (event_head, eloop_event) events;
+ size_t nevents;
+ struct event_head free_events;
+ bool events_need_setup;
+
+ struct timespec now;
+ TAILQ_HEAD (timeout_head, eloop_timeout) timeouts;
+ struct timeout_head free_timeouts;
+
+ const int *signals;
+ size_t signals_len;
+ void (*signal_cb)(int, void *);
+ void *signal_cb_ctx;
+
+ struct pollfd *fds;
+ size_t nfds;
+
+ int exitnow;
+ int exitcode;
+};
+
+#ifdef HAVE_REALLOCARRAY
+#define eloop_realloca reallocarray
+#else
+/* Handy routing to check for potential overflow.
+ * reallocarray(3) and reallocarr(3) are not portable. */
+#define SQRT_SIZE_MAX (((size_t)1) << (sizeof(size_t) * CHAR_BIT / 2))
+static void *
+eloop_realloca(void *ptr, size_t n, size_t size)
+{
+
+ if ((n | size) >= SQRT_SIZE_MAX && n > SIZE_MAX / size) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ return realloc(ptr, n * size);
+}
+#endif
+
+#ifdef HAVE_PSELECT
+/* Wrapper around pselect, to imitate the ppoll call. */
+static int
+eloop_ppoll(struct pollfd * fds, nfds_t nfds,
+ const struct timespec *ts, const sigset_t *sigmask)
+{
+ fd_set read_fds, write_fds;
+ nfds_t n;
+ int maxfd, r;
+
+ FD_ZERO(&read_fds);
+ FD_ZERO(&write_fds);
+ maxfd = 0;
+ for (n = 0; n < nfds; n++) {
+ if (fds[n].events & POLLIN) {
+ FD_SET(fds[n].fd, &read_fds);
+ if (fds[n].fd > maxfd)
+ maxfd = fds[n].fd;
+ }
+ if (fds[n].events & POLLOUT) {
+ FD_SET(fds[n].fd, &write_fds);
+ if (fds[n].fd > maxfd)
+ maxfd = fds[n].fd;
+ }
+ }
+
+ r = pselect(maxfd + 1, &read_fds, &write_fds, NULL, ts, sigmask);
+ if (r > 0) {
+ for (n = 0; n < nfds; n++) {
+ fds[n].revents =
+ FD_ISSET(fds[n].fd, &read_fds) ? POLLIN : 0;
+ if (FD_ISSET(fds[n].fd, &write_fds))
+ fds[n].revents |= POLLOUT;
+ }
+ }
+
+ return r;
+}
+#endif
+
+unsigned long long
+eloop_timespec_diff(const struct timespec *tsp, const struct timespec *usp,
+ unsigned int *nsp)
+{
+ unsigned long long tsecs, usecs, secs;
+ long nsecs;
+
+ if (tsp->tv_sec < 0) /* time wreapped */
+ tsecs = UTIME_MAX - (unsigned long long)(-tsp->tv_sec);
+ else
+ tsecs = (unsigned long long)tsp->tv_sec;
+ if (usp->tv_sec < 0) /* time wrapped */
+ usecs = UTIME_MAX - (unsigned long long)(-usp->tv_sec);
+ else
+ usecs = (unsigned long long)usp->tv_sec;
+
+ if (usecs > tsecs) /* time wrapped */
+ secs = (UTIME_MAX - usecs) + tsecs;
+ else
+ secs = tsecs - usecs;
+
+ nsecs = tsp->tv_nsec - usp->tv_nsec;
+ if (nsecs < 0) {
+ if (secs == 0)
+ nsecs = 0;
+ else {
+ secs--;
+ nsecs += NSEC_PER_SEC;
+ }
+ }
+ if (nsp != NULL)
+ *nsp = (unsigned int)nsecs;
+ return secs;
+}
+
+static void
+eloop_reduce_timers(struct eloop *eloop)
+{
+ struct timespec now;
+ unsigned long long secs;
+ unsigned int nsecs;
+ struct eloop_timeout *t;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ secs = eloop_timespec_diff(&now, &eloop->now, &nsecs);
+
+ TAILQ_FOREACH(t, &eloop->timeouts, next) {
+ if (secs > t->seconds) {
+ t->seconds = 0;
+ t->nseconds = 0;
+ } else {
+ t->seconds -= (unsigned int)secs;
+ if (nsecs > t->nseconds) {
+ if (t->seconds == 0)
+ t->nseconds = 0;
+ else {
+ t->seconds--;
+ t->nseconds = NSEC_PER_SEC
+ - (nsecs - t->nseconds);
+ }
+ } else
+ t->nseconds -= nsecs;
+ }
+ }
+
+ eloop->now = now;
+}
+
+static void
+eloop_event_setup_fds(struct eloop *eloop)
+{
+ struct eloop_event *e, *ne;
+ struct pollfd *pfd;
+
+ pfd = eloop->fds;
+ TAILQ_FOREACH_SAFE(e, &eloop->events, next, ne) {
+ if (e->fd == -1) {
+ TAILQ_REMOVE(&eloop->events, e, next);
+ TAILQ_INSERT_TAIL(&eloop->free_events, e, next);
+ continue;
+ }
+#ifdef ELOOP_DEBUG
+ fprintf(stderr, "%s(%d) fd=%d, rcb=%p, wcb=%p\n",
+ __func__, getpid(), e->fd, e->read_cb, e->write_cb);
+#endif
+ e->pollfd = pfd;
+ pfd->fd = e->fd;
+ pfd->events = 0;
+ if (e->read_cb != NULL)
+ pfd->events |= POLLIN;
+ if (e->write_cb != NULL)
+ pfd->events |= POLLOUT;
+ pfd->revents = 0;
+ pfd++;
+ }
+ eloop->events_need_setup = false;
+}
+
+size_t
+eloop_event_count(const struct eloop *eloop)
+{
+
+ return eloop->nevents;
+}
+
+int
+eloop_event_add_rw(struct eloop *eloop, int fd,
+ void (*read_cb)(void *), void *read_cb_arg,
+ void (*write_cb)(void *), void *write_cb_arg)
+{
+ struct eloop_event *e;
+ struct pollfd *pfd;
+
+ assert(eloop != NULL);
+ assert(read_cb != NULL || write_cb != NULL);
+ if (fd == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ if (e->fd == fd)
+ break;
+ }
+
+ if (e == NULL) {
+ if (eloop->nevents + 1 > eloop->nfds) {
+ pfd = eloop_realloca(eloop->fds, eloop->nevents + 1,
+ sizeof(*pfd));
+ if (pfd == NULL)
+ return -1;
+ eloop->fds = pfd;
+ eloop->nfds++;
+ }
+
+ e = TAILQ_FIRST(&eloop->free_events);
+ if (e != NULL)
+ TAILQ_REMOVE(&eloop->free_events, e, next);
+ else {
+ e = malloc(sizeof(*e));
+ if (e == NULL)
+ return -1;
+ }
+ TAILQ_INSERT_HEAD(&eloop->events, e, next);
+ eloop->nevents++;
+ e->fd = fd;
+ e->read_cb = read_cb;
+ e->read_cb_arg = read_cb_arg;
+ e->write_cb = write_cb;
+ e->write_cb_arg = write_cb_arg;
+ goto setup;
+ }
+
+ if (read_cb) {
+ e->read_cb = read_cb;
+ e->read_cb_arg = read_cb_arg;
+ }
+ if (write_cb) {
+ e->write_cb = write_cb;
+ e->write_cb_arg = write_cb_arg;
+ }
+
+setup:
+ e->pollfd = NULL;
+ eloop->events_need_setup = true;
+ return 0;
+}
+
+int
+eloop_event_add(struct eloop *eloop, int fd,
+ void (*read_cb)(void *), void *read_cb_arg)
+{
+
+ return eloop_event_add_rw(eloop, fd, read_cb, read_cb_arg, NULL, NULL);
+}
+
+int
+eloop_event_add_w(struct eloop *eloop, int fd,
+ void (*write_cb)(void *), void *write_cb_arg)
+{
+
+ return eloop_event_add_rw(eloop, fd, NULL,NULL, write_cb, write_cb_arg);
+}
+
+int
+eloop_event_delete_write(struct eloop *eloop, int fd, int write_only)
+{
+ struct eloop_event *e;
+
+ assert(eloop != NULL);
+ if (fd == -1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ if (e->fd == fd)
+ break;
+ }
+ if (e == NULL) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (write_only) {
+ if (e->read_cb == NULL)
+ goto remove;
+ e->write_cb = NULL;
+ e->write_cb_arg = NULL;
+ if (e->pollfd != NULL) {
+ e->pollfd->events &= ~POLLOUT;
+ e->pollfd->revents &= ~POLLOUT;
+ }
+ return 1;
+ }
+
+remove:
+ e->fd = -1;
+ eloop->nevents--;
+ eloop->events_need_setup = true;
+ return 1;
+}
+
+/*
+ * This implementation should cope with UINT_MAX seconds on a system
+ * where time_t is INT32_MAX. It should also cope with the monotonic timer
+ * wrapping, although this is highly unlikely.
+ * unsigned int should match or be greater than any on wire specified timeout.
+ */
+static int
+eloop_q_timeout_add(struct eloop *eloop, int queue,
+ unsigned int seconds, unsigned int nseconds,
+ void (*callback)(void *), void *arg)
+{
+ struct eloop_timeout *t, *tt = NULL;
+
+ assert(eloop != NULL);
+ assert(callback != NULL);
+ assert(nseconds <= NSEC_PER_SEC);
+
+ /* Remove existing timeout if present. */
+ TAILQ_FOREACH(t, &eloop->timeouts, next) {
+ if (t->callback == callback && t->arg == arg) {
+ TAILQ_REMOVE(&eloop->timeouts, t, next);
+ break;
+ }
+ }
+
+ if (t == NULL) {
+ /* No existing, so allocate or grab one from the free pool. */
+ if ((t = TAILQ_FIRST(&eloop->free_timeouts))) {
+ TAILQ_REMOVE(&eloop->free_timeouts, t, next);
+ } else {
+ if ((t = malloc(sizeof(*t))) == NULL)
+ return -1;
+ }
+ }
+
+ eloop_reduce_timers(eloop);
+
+ t->seconds = seconds;
+ t->nseconds = nseconds;
+ t->callback = callback;
+ t->arg = arg;
+ t->queue = queue;
+
+ /* The timeout list should be in chronological order,
+ * soonest first. */
+ TAILQ_FOREACH(tt, &eloop->timeouts, next) {
+ if (t->seconds < tt->seconds ||
+ (t->seconds == tt->seconds && t->nseconds < tt->nseconds))
+ {
+ TAILQ_INSERT_BEFORE(tt, t, next);
+ return 0;
+ }
+ }
+ TAILQ_INSERT_TAIL(&eloop->timeouts, t, next);
+ return 0;
+}
+
+int
+eloop_q_timeout_add_tv(struct eloop *eloop, int queue,
+ const struct timespec *when, void (*callback)(void *), void *arg)
+{
+
+ if (when->tv_sec < 0 || (unsigned long)when->tv_sec > UINT_MAX) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (when->tv_nsec < 0 || when->tv_nsec > NSEC_PER_SEC) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return eloop_q_timeout_add(eloop, queue,
+ (unsigned int)when->tv_sec, (unsigned int)when->tv_sec,
+ callback, arg);
+}
+
+int
+eloop_q_timeout_add_sec(struct eloop *eloop, int queue, unsigned int seconds,
+ void (*callback)(void *), void *arg)
+{
+
+ return eloop_q_timeout_add(eloop, queue, seconds, 0, callback, arg);
+}
+
+int
+eloop_q_timeout_add_msec(struct eloop *eloop, int queue, unsigned long when,
+ void (*callback)(void *), void *arg)
+{
+ unsigned long seconds, nseconds;
+
+ seconds = when / MSEC_PER_SEC;
+ if (seconds > UINT_MAX) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ nseconds = (when % MSEC_PER_SEC) * NSEC_PER_MSEC;
+ return eloop_q_timeout_add(eloop, queue,
+ (unsigned int)seconds, (unsigned int)nseconds, callback, arg);
+}
+
+int
+eloop_q_timeout_delete(struct eloop *eloop, int queue,
+ void (*callback)(void *), void *arg)
+{
+ struct eloop_timeout *t, *tt;
+ int n;
+
+ assert(eloop != NULL);
+
+ n = 0;
+ TAILQ_FOREACH_SAFE(t, &eloop->timeouts, next, tt) {
+ if ((queue == 0 || t->queue == queue) &&
+ t->arg == arg &&
+ (!callback || t->callback == callback))
+ {
+ TAILQ_REMOVE(&eloop->timeouts, t, next);
+ TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next);
+ n++;
+ }
+ }
+ return n;
+}
+
+void
+eloop_exit(struct eloop *eloop, int code)
+{
+
+ assert(eloop != NULL);
+
+ eloop->exitcode = code;
+ eloop->exitnow = 1;
+}
+
+void
+eloop_enter(struct eloop *eloop)
+{
+
+ eloop->exitnow = 0;
+}
+
+void
+eloop_signal_set_cb(struct eloop *eloop,
+ const int *signals, size_t signals_len,
+ void (*signal_cb)(int, void *), void *signal_cb_ctx)
+{
+
+ assert(eloop != NULL);
+
+ eloop->signals = signals;
+ eloop->signals_len = signals_len;
+ eloop->signal_cb = signal_cb;
+ eloop->signal_cb_ctx = signal_cb_ctx;
+}
+
+static volatile int _eloop_sig[ELOOP_NSIGNALS];
+static volatile size_t _eloop_nsig;
+
+static void
+eloop_signal3(int sig, __unused siginfo_t *siginfo, __unused void *arg)
+{
+
+ if (_eloop_nsig == __arraycount(_eloop_sig)) {
+#ifdef ELOOP_DEBUG
+ fprintf(stderr, "%s: signal storm, discarding signal %d\n",
+ __func__, sig);
+#endif
+ return;
+ }
+
+ _eloop_sig[_eloop_nsig++] = sig;
+}
+
+int
+eloop_signal_mask(struct eloop *eloop, sigset_t *oldset)
+{
+ sigset_t newset;
+ size_t i;
+ struct sigaction sa = {
+ .sa_sigaction = eloop_signal3,
+ .sa_flags = SA_SIGINFO,
+ };
+
+ assert(eloop != NULL);
+
+ sigemptyset(&newset);
+ for (i = 0; i < eloop->signals_len; i++)
+ sigaddset(&newset, eloop->signals[i]);
+ if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1)
+ return -1;
+
+ sigemptyset(&sa.sa_mask);
+
+ for (i = 0; i < eloop->signals_len; i++) {
+ if (sigaction(eloop->signals[i], &sa, NULL) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+struct eloop *
+eloop_new(void)
+{
+ struct eloop *eloop;
+
+ eloop = calloc(1, sizeof(*eloop));
+ if (eloop == NULL)
+ return NULL;
+
+ /* Check we have a working monotonic clock. */
+ if (clock_gettime(CLOCK_MONOTONIC, &eloop->now) == -1) {
+ free(eloop);
+ return NULL;
+ }
+
+ TAILQ_INIT(&eloop->events);
+ TAILQ_INIT(&eloop->free_events);
+ TAILQ_INIT(&eloop->timeouts);
+ TAILQ_INIT(&eloop->free_timeouts);
+ eloop->exitcode = EXIT_FAILURE;
+
+ return eloop;
+}
+
+void
+eloop_clear(struct eloop *eloop)
+{
+ struct eloop_event *e;
+ struct eloop_timeout *t;
+
+ if (eloop == NULL)
+ return;
+
+ eloop->nevents = 0;
+ eloop->signals = NULL;
+ eloop->signals_len = 0;
+
+ while ((e = TAILQ_FIRST(&eloop->events))) {
+ TAILQ_REMOVE(&eloop->events, e, next);
+ free(e);
+ }
+ while ((e = TAILQ_FIRST(&eloop->free_events))) {
+ TAILQ_REMOVE(&eloop->free_events, e, next);
+ free(e);
+ }
+ while ((t = TAILQ_FIRST(&eloop->timeouts))) {
+ TAILQ_REMOVE(&eloop->timeouts, t, next);
+ free(t);
+ }
+ while ((t = TAILQ_FIRST(&eloop->free_timeouts))) {
+ TAILQ_REMOVE(&eloop->free_timeouts, t, next);
+ free(t);
+ }
+
+ free(eloop->fds);
+ eloop->fds = NULL;
+ eloop->nfds = 0;
+}
+
+void
+eloop_free(struct eloop *eloop)
+{
+
+ eloop_clear(eloop);
+ free(eloop);
+}
+
+int
+eloop_start(struct eloop *eloop, sigset_t *signals)
+{
+ int n;
+ struct eloop_event *e;
+ struct eloop_timeout *t;
+ struct timespec ts, *tsp;
+
+ assert(eloop != NULL);
+
+ for (;;) {
+ if (eloop->exitnow)
+ break;
+
+ if (_eloop_nsig != 0) {
+ n = _eloop_sig[--_eloop_nsig];
+ if (eloop->signal_cb != NULL)
+ eloop->signal_cb(n, eloop->signal_cb_ctx);
+ continue;
+ }
+
+ t = TAILQ_FIRST(&eloop->timeouts);
+ if (t == NULL && eloop->nevents == 0)
+ break;
+
+ if (t != NULL)
+ eloop_reduce_timers(eloop);
+
+ if (t != NULL && t->seconds == 0 && t->nseconds == 0) {
+ TAILQ_REMOVE(&eloop->timeouts, t, next);
+ t->callback(t->arg);
+ TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next);
+ continue;
+ }
+
+ if (t != NULL) {
+ if (t->seconds > INT_MAX) {
+ ts.tv_sec = (time_t)INT_MAX;
+ ts.tv_nsec = 0;
+ } else {
+ ts.tv_sec = (time_t)t->seconds;
+ ts.tv_nsec = (long)t->nseconds;
+ }
+ tsp = &ts;
+ } else
+ tsp = NULL;
+
+ if (eloop->events_need_setup)
+ eloop_event_setup_fds(eloop);
+
+ n = ppoll(eloop->fds, (nfds_t)eloop->nevents, tsp, signals);
+ if (n == -1) {
+ if (errno == EINTR)
+ continue;
+ return -errno;
+ }
+ if (n == 0)
+ continue;
+
+ TAILQ_FOREACH(e, &eloop->events, next) {
+ /* Skip freshly added events */
+ if (e->pollfd == NULL)
+ continue;
+ if (e->pollfd->revents)
+ n--;
+ if (e->fd != -1 && e->pollfd->revents & POLLOUT) {
+ if (e->write_cb != NULL)
+ e->write_cb(e->write_cb_arg);
+ }
+ if (e->fd != -1 &&
+ e->pollfd != NULL && e->pollfd->revents)
+ {
+ if (e->read_cb != NULL)
+ e->read_cb(e->read_cb_arg);
+ }
+ if (n == 0)
+ break;
+ }
+ }
+
+ return eloop->exitcode;
+}