/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2010 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. */ #include #include #include #include #include #include #include #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; } } } } }