mirror of
https://github.com/rsmarples/dhcpcd.git
synced 2024-11-28 12:33:49 +08:00
367 lines
8.0 KiB
C
367 lines
8.0 KiB
C
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|