/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2013 Roy Marples * 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. */ /* Needed for ppoll(2) */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "common.h" #include "dhcpcd.h" #include "eloop.h" static struct timeval now; struct event { TAILQ_ENTRY(event) next; int fd; void (*callback)(void *); void *arg; struct pollfd *pollfd; }; static size_t events_len; static TAILQ_HEAD (event_head, event) events = TAILQ_HEAD_INITIALIZER(events); static struct event_head free_events = TAILQ_HEAD_INITIALIZER(free_events); struct timeout { TAILQ_ENTRY(timeout) next; struct timeval when; void (*callback)(void *); void *arg; int queue; }; static TAILQ_HEAD (timeout_head, timeout) timeouts = TAILQ_HEAD_INITIALIZER(timeouts); static struct timeout_head free_timeouts = TAILQ_HEAD_INITIALIZER(free_timeouts); static void (*volatile timeout0)(void *); static void *volatile timeout0_arg; static struct pollfd *fds; static size_t fds_len; static void eloop_event_setup_fds(void) { struct event *e; size_t i; i = 0; TAILQ_FOREACH(e, &events, next) { fds[i].fd = e->fd; fds[i].events = POLLIN; fds[i].revents = 0; e->pollfd = &fds[i]; i++; } } int eloop_event_add(int fd, void (*callback)(void *), void *arg) { struct event *e; /* We should only have one callback monitoring the fd */ TAILQ_FOREACH(e, &events, next) { if (e->fd == fd) { e->callback = callback; e->arg = arg; return 0; } } /* Allocate a new event if no free ones already allocated */ if ((e = TAILQ_FIRST(&free_events))) { TAILQ_REMOVE(&free_events, e, next); } else { e = malloc(sizeof(*e)); if (e == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } } /* Ensure we can actually listen to it */ events_len++; if (events_len > fds_len) { fds_len += 5; free(fds); fds = malloc(sizeof(*fds) * fds_len); if (fds == NULL) { syslog(LOG_ERR, "%s: %m", __func__); free(e); return -1; } } /* Now populate the structure and add it to the list */ e->fd = fd; e->callback = callback; e->arg = arg; /* The order of events should not matter. * However, some PPP servers love to close the link right after * sending their final message. So to ensure dhcpcd processes this * message (which is likely to be that the DHCP addresses are wrong) * we insert new events at the queue head as the link fd will be * the first event added. */ TAILQ_INSERT_HEAD(&events, e, next); eloop_event_setup_fds(); return 0; } void eloop_event_delete(int fd) { struct event *e; TAILQ_FOREACH(e, &events, next) { if (e->fd == fd) { TAILQ_REMOVE(&events, e, next); TAILQ_INSERT_TAIL(&free_events, e, next); events_len--; eloop_event_setup_fds(); break; } } } int eloop_q_timeout_add_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 -1; } /* Remove existing timeout if present */ TAILQ_FOREACH(t, &timeouts, next) { if (t->callback == callback && t->arg == arg) { TAILQ_REMOVE(&timeouts, t, next); break; } } if (t == NULL) { /* No existing, so allocate or grab one from the free pool */ if ((t = TAILQ_FIRST(&free_timeouts))) { TAILQ_REMOVE(&free_timeouts, t, next); } else { t = malloc(sizeof(*t)); if (t == NULL) { syslog(LOG_ERR, "%s: %m", __func__); return -1; } } } 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. */ TAILQ_FOREACH(tt, &timeouts, next) { if (timercmp(&t->when, &tt->when, <)) { TAILQ_INSERT_BEFORE(tt, t, next); return 0; } } TAILQ_INSERT_TAIL(&timeouts, t, next); return 0; } int eloop_q_timeout_add_sec(int queue, time_t when, void (*callback)(void *), void *arg) { struct timeval tv; tv.tv_sec = when; tv.tv_usec = 0; return eloop_q_timeout_add_tv(queue, &tv, callback, arg); } int eloop_timeout_add_now(void (*callback)(void *), void *arg) { if (timeout0 != NULL) { syslog(LOG_WARNING, "%s: timeout0 already set", __func__); return eloop_q_timeout_add_sec(0, 0, callback, arg); } timeout0 = callback; timeout0_arg = arg; return 0; } /* 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 eloop_q_timeouts_delete_v(int queue, void *arg, void (*callback)(void *), va_list v) { struct timeout *t, *tt; va_list va; void (*f)(void *); TAILQ_FOREACH_SAFE(t, &timeouts, next, tt) { if ((queue == 0 || 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 == NULL) { TAILQ_REMOVE(&timeouts, t, next); TAILQ_INSERT_TAIL(&free_timeouts, t, next); } } } } void eloop_q_timeouts_delete(int queue, void *arg, void (*callback)(void *), ...) { va_list va; va_start(va, callback); eloop_q_timeouts_delete_v(queue, arg, callback, va); va_end(va); } void eloop_q_timeout_delete(int queue, void (*callback)(void *), void *arg) { struct timeout *t, *tt; TAILQ_FOREACH_SAFE(t, &timeouts, next, tt) { if (t->queue == queue && t->arg == arg && (!callback || t->callback == callback)) { TAILQ_REMOVE(&timeouts, t, next); TAILQ_INSERT_TAIL(&free_timeouts, t, next); } } } #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 eloop_cleanup(void) { struct event *e; struct timeout *t; while ((e = TAILQ_FIRST(&events))) { TAILQ_REMOVE(&events, e, next); free(e); } while ((e = TAILQ_FIRST(&free_events))) { TAILQ_REMOVE(&free_events, e, next); free(e); } while ((t = TAILQ_FIRST(&timeouts))) { TAILQ_REMOVE(&timeouts, t, next); free(t); } while ((t = TAILQ_FIRST(&free_timeouts))) { TAILQ_REMOVE(&free_timeouts, t, next); free(t); } free(fds); } void eloop_init(void) { atexit(eloop_cleanup); } #endif __dead void eloop_start(const sigset_t *sigmask) { int n; struct event *e; struct timeout *t; struct timeval tv; struct timespec ts, *tsp; void (*t0)(void *); for (;;) { /* Run all timeouts first */ if (timeout0) { t0 = timeout0; timeout0 = NULL; t0(timeout0_arg); continue; } if ((t = TAILQ_FIRST(&timeouts))) { get_monotonic(&now); if (timercmp(&now, &t->when, >)) { TAILQ_REMOVE(&timeouts, t, next); t->callback(t->arg); TAILQ_INSERT_TAIL(&free_timeouts, t, next); continue; } timersub(&t->when, &now, &tv); TIMEVAL_TO_TIMESPEC(&tv, &ts); tsp = &ts; } else /* No timeouts, so wait forever */ tsp = NULL; if (tsp == NULL && events_len == 0) { syslog(LOG_ERR, "nothing to do"); exit(EXIT_FAILURE); } n = pollts(fds, events_len, tsp, sigmask); if (n == -1) { if (errno == EAGAIN || errno == EINTR) continue; syslog(LOG_ERR, "poll: %m"); exit(EXIT_FAILURE); } /* Process any triggered events. */ if (n > 0) { TAILQ_FOREACH(e, &events, next) { if (e->pollfd->revents & (POLLIN | POLLHUP)) { e->callback(e->arg); /* We need to break here as the * callback could destroy the next * fd to process. */ break; } } } } }