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