aboutsummaryrefslogtreecommitdiffstats
path: root/eloop.c
diff options
context:
space:
mode:
Diffstat (limited to 'eloop.c')
-rw-r--r--eloop.c366
1 files changed, 366 insertions, 0 deletions
diff --git a/eloop.c b/eloop.c
new file mode 100644
index 0000000..a5d08cb
--- /dev/null
+++ b/eloop.c
@@ -0,0 +1,366 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2008 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 <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include "common.h"
+#include "eloop.h"
+
+static struct timeval now;
+
+static struct event {
+ int fd;
+ void (*callback)(void *);
+ void *arg;
+ struct event *next;
+} *events;
+static struct event *free_events;
+
+static struct timeout {
+ struct timeval when;
+ void (*callback)(void *);
+ void *arg;
+ int queue;
+ struct timeout *next;
+} *timeouts;
+static struct timeout *free_timeouts;
+
+static struct pollfd *fds;
+static size_t fds_len;
+
+void
+add_event(int fd, void (*callback)(void *), void *arg)
+{
+ struct event *e, *last = NULL;
+
+ /* We should only have one callback monitoring the fd */
+ for (e = events; e; e = e->next) {
+ if (e->fd == fd) {
+ e->callback = callback;
+ e->arg = arg;
+ return;
+ }
+ last = e;
+ }
+
+ /* Allocate a new event if no free ones already allocated */
+ if (free_events) {
+ e = free_events;
+ free_events = e->next;
+ } else
+ e = xmalloc(sizeof(*e));
+ e->fd = fd;
+ e->callback = callback;
+ e->arg = arg;
+ e->next = NULL;
+ if (last)
+ last->next = e;
+ else
+ events = e;
+}
+
+void
+delete_event(int fd)
+{
+ struct event *e, *last = NULL;
+
+ for (e = events; e; e = e->next) {
+ if (e->fd == fd) {
+ if (last)
+ last->next = e->next;
+ else
+ events = e->next;
+ e->next = free_events;
+ free_events = e;
+ break;
+ }
+ last = e;
+ }
+}
+
+void
+add_q_timeout_tv(int queue,
+ const struct timeval *when, void (*callback)(void *), void *arg)
+{
+ struct timeval w;
+ struct timeout *t, *tt = NULL;
+
+ get_monotonic(&now);
+ timeradd(&now, when, &w);
+ /* Check for time_t overflow. */
+ if (timercmp(&w, &now, <)) {
+ errno = ERANGE;
+ return;
+ }
+
+ /* Remove existing timeout if present */
+ for (t = timeouts; t; t = t->next) {
+ if (t->callback == callback && t->arg == arg) {
+ if (tt)
+ tt->next = t->next;
+ else
+ timeouts = t->next;
+ break;
+ }
+ tt = t;
+ }
+
+ if (!t) {
+ /* No existing, so allocate or grab one from the free pool */
+ if (free_timeouts) {
+ t = free_timeouts;
+ free_timeouts = t->next;
+ } else
+ t = xmalloc(sizeof(*t));
+ }
+
+ t->when.tv_sec = w.tv_sec;
+ t->when.tv_usec = w.tv_usec;
+ t->callback = callback;
+ t->arg = arg;
+ t->queue = queue;
+
+ /* The timeout list should be in chronological order,
+ * soonest first.
+ * This is the easiest algorithm - check the head, then middle
+ * and finally the end. */
+ if (!timeouts || timercmp(&t->when, &timeouts->when, <)) {
+ t->next = timeouts;
+ timeouts = t;
+ return;
+ }
+ for (tt = timeouts; tt->next; tt = tt->next)
+ if (timercmp(&t->when, &tt->next->when, <)) {
+ t->next = tt->next;
+ tt->next = t;
+ return;
+ }
+ tt->next = t;
+ t->next = NULL;
+}
+
+void
+add_q_timeout_sec(int queue, time_t when, void (*callback)(void *), void *arg)
+{
+ struct timeval tv;
+
+ tv.tv_sec = when;
+ tv.tv_usec = 0;
+ add_q_timeout_tv(queue, &tv, callback, arg);
+}
+
+/* This deletes all timeouts for the interface EXCEPT for ones with the
+ * callbacks given. Handy for deleting everything apart from the expire
+ * timeout. */
+static void
+v_delete_q_timeouts(int queue, void *arg, void (*callback)(void *), va_list v)
+{
+ struct timeout *t, *tt, *last = NULL;
+ va_list va;
+ void (*f)(void *);
+
+ for (t = timeouts; t && (tt = t->next, 1); t = tt) {
+ if (t->queue == queue && t->arg == arg &&
+ t->callback != callback)
+ {
+ va_copy(va, v);
+ while ((f = va_arg(va, void (*)(void *))))
+ if (f == t->callback)
+ break;
+ va_end(va);
+ if (!f) {
+ if (last)
+ last->next = t->next;
+ else
+ timeouts = t->next;
+ t->next = free_timeouts;
+ free_timeouts = t;
+ continue;
+ }
+ }
+ last = t;
+ }
+}
+
+void
+delete_q_timeouts(int queue, void *arg, void (*callback)(void *), ...)
+{
+ va_list va;
+
+ va_start(va, callback);
+ v_delete_q_timeouts(queue, arg, callback, va);
+ va_end(va);
+}
+
+void
+delete_q_timeout(int queue, void (*callback)(void *), void *arg)
+{
+ struct timeout *t, *tt, *last = NULL;
+
+ for (t = timeouts; t && (tt = t->next, 1); t = tt) {
+ if (t->queue == queue && t->arg == arg &&
+ (!callback || t->callback == callback))
+ {
+ if (last)
+ last->next = t->next;
+ else
+ timeouts = t->next;
+ t->next = free_timeouts;
+ free_timeouts = t;
+ continue;
+ }
+ last = t;
+ }
+}
+
+#ifdef DEBUG_MEMORY
+/* Define this to free all malloced memory.
+ * Normally we don't do this as the OS will do it for us at exit,
+ * but it's handy for debugging other leaks in valgrind. */
+static void
+cleanup(void)
+{
+ struct event *e;
+ struct timeout *t;
+
+ while (events) {
+ e = events->next;
+ free(events);
+ events = e;
+ }
+ while (free_events) {
+ e = free_events->next;
+ free(free_events);
+ free_events = e;
+ }
+ while (timeouts) {
+ t = timeouts->next;
+ free(timeouts);
+ timeouts = t;
+ }
+ while (free_timeouts) {
+ t = free_timeouts->next;
+ free(free_timeouts);
+ free_timeouts = t;
+ }
+ free(fds);
+}
+#endif
+
+_noreturn void
+start_eloop(void)
+{
+ int msecs, n;
+ nfds_t nfds, i;
+ struct event *e;
+ struct timeout *t;
+ struct timeval tv;
+
+#ifdef DEBUG_MEMORY
+ atexit(cleanup);
+#endif
+
+ for (;;) {
+ /* Run all timeouts first.
+ * When we have one that has not yet occured,
+ * calculate milliseconds until it does for use in poll. */
+ if (timeouts) {
+ if (timercmp(&now, &timeouts->when, >)) {
+ t = timeouts;
+ timeouts = timeouts->next;
+ t->callback(t->arg);
+ t->next = free_timeouts;
+ free_timeouts = t;
+ continue;
+ }
+ timersub(&timeouts->when, &now, &tv);
+ if (tv.tv_sec > INT_MAX / 1000 ||
+ (tv.tv_sec == INT_MAX / 1000 &&
+ (tv.tv_usec + 999) / 1000 > INT_MAX % 1000))
+ msecs = INT_MAX;
+ else
+ msecs = tv.tv_sec * 1000 +
+ (tv.tv_usec + 999) / 1000;
+ } else
+ /* No timeouts, so wait forever. */
+ msecs = -1;
+
+ /* Allocate memory for our pollfds as and when needed.
+ * We don't bother shrinking it. */
+ nfds = 0;
+ for (e = events; e; e = e->next)
+ nfds++;
+ if (msecs == -1 && nfds == 0) {
+ syslog(LOG_ERR, "nothing to do");
+ exit(EXIT_FAILURE);
+ }
+ if (nfds > fds_len) {
+ free(fds);
+ /* Allocate 5 more than we need for future use */
+ fds_len = nfds + 5;
+ fds = xmalloc(sizeof(*fds) * fds_len);
+ }
+ nfds = 0;
+ for (e = events; e; e = e->next) {
+ fds[nfds].fd = e->fd;
+ fds[nfds].events = POLLIN;
+ fds[nfds].revents = 0;
+ nfds++;
+ }
+ n = poll(fds, nfds, msecs);
+ if (n == -1) {
+ if (errno == EAGAIN || errno == EINTR) {
+ get_monotonic(&now);
+ continue;
+ }
+ syslog(LOG_ERR, "poll: %m");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Get the now time and process any triggered events. */
+ get_monotonic(&now);
+ if (n == 0)
+ continue;
+ for (i = 0; i < nfds; i++) {
+ if (!(fds[i].revents & (POLLIN | POLLHUP)))
+ continue;
+ for (e = events; e; e = e->next) {
+ if (e->fd == fds[i].fd) {
+ e->callback(e->arg);
+ break;
+ }
+ }
+ }
+ }
+}