1/*
2 * dhcpcd - DHCP client daemon
3 * Copyright (c) 2006-2010 Roy Marples <roy@marples.name>
4 * All rights reserved
5
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/time.h>
29
30#include <errno.h>
31#include <limits.h>
32#include <poll.h>
33#include <stdarg.h>
34#include <stdlib.h>
35#include <syslog.h>
36
37#include "common.h"
38#include "eloop.h"
39
40static struct timeval now;
41
42static struct event {
43	int fd;
44	void (*callback)(void *);
45	void *arg;
46	struct event *next;
47} *events;
48static struct event *free_events;
49
50static struct timeout {
51	struct timeval when;
52	void (*callback)(void *);
53	void *arg;
54	int queue;
55	struct timeout *next;
56} *timeouts;
57static struct timeout *free_timeouts;
58
59static struct pollfd *fds;
60static size_t fds_len;
61
62void
63add_event(int fd, void (*callback)(void *), void *arg)
64{
65	struct event *e, *last = NULL;
66
67	/* We should only have one callback monitoring the fd */
68	for (e = events; e; e = e->next) {
69		if (e->fd == fd) {
70			e->callback = callback;
71			e->arg = arg;
72			return;
73		}
74		last = e;
75	}
76
77	/* Allocate a new event if no free ones already allocated */
78	if (free_events) {
79		e = free_events;
80		free_events = e->next;
81	} else
82		e = xmalloc(sizeof(*e));
83	e->fd = fd;
84	e->callback = callback;
85	e->arg = arg;
86	e->next = NULL;
87	if (last)
88		last->next = e;
89	else
90		events = e;
91}
92
93void
94delete_event(int fd)
95{
96	struct event *e, *last = NULL;
97
98	for (e = events; e; e = e->next) {
99		if (e->fd == fd) {
100			if (last)
101				last->next = e->next;
102			else
103				events = e->next;
104			e->next = free_events;
105			free_events = e;
106			break;
107		}
108		last = e;
109	}
110}
111
112void
113add_q_timeout_tv(int queue,
114    const struct timeval *when, void (*callback)(void *), void *arg)
115{
116	struct timeval w;
117	struct timeout *t, *tt = NULL;
118
119	get_monotonic(&now);
120	timeradd(&now, when, &w);
121	/* Check for time_t overflow. */
122	if (timercmp(&w, &now, <)) {
123		errno = ERANGE;
124		return;
125	}
126
127	/* Remove existing timeout if present */
128	for (t = timeouts; t; t = t->next) {
129		if (t->callback == callback && t->arg == arg) {
130			if (tt)
131				tt->next = t->next;
132			else
133				timeouts = t->next;
134			break;
135		}
136		tt = t;
137	}
138
139	if (!t) {
140		/* No existing, so allocate or grab one from the free pool */
141		if (free_timeouts) {
142			t = free_timeouts;
143			free_timeouts = t->next;
144		} else
145			t = xmalloc(sizeof(*t));
146	}
147
148	t->when.tv_sec = w.tv_sec;
149	t->when.tv_usec = w.tv_usec;
150	t->callback = callback;
151	t->arg = arg;
152	t->queue = queue;
153
154	/* The timeout list should be in chronological order,
155	 * soonest first.
156	 * This is the easiest algorithm - check the head, then middle
157	 * and finally the end. */
158	if (!timeouts || timercmp(&t->when, &timeouts->when, <)) {
159		t->next = timeouts;
160		timeouts = t;
161		return;
162	}
163	for (tt = timeouts; tt->next; tt = tt->next)
164		if (timercmp(&t->when, &tt->next->when, <)) {
165			t->next = tt->next;
166			tt->next = t;
167			return;
168		}
169	tt->next = t;
170	t->next = NULL;
171}
172
173void
174add_q_timeout_sec(int queue, time_t when, void (*callback)(void *), void *arg)
175{
176	struct timeval tv;
177
178	tv.tv_sec = when;
179	tv.tv_usec = 0;
180	add_q_timeout_tv(queue, &tv, callback, arg);
181}
182
183/* This deletes all timeouts for the interface EXCEPT for ones with the
184 * callbacks given. Handy for deleting everything apart from the expire
185 * timeout. */
186static void
187v_delete_q_timeouts(int queue, void *arg, void (*callback)(void *), va_list v)
188{
189	struct timeout *t, *tt, *last = NULL;
190	va_list va;
191	void (*f)(void *);
192
193	for (t = timeouts; t && (tt = t->next, 1); t = tt) {
194		if (t->queue == queue && t->arg == arg &&
195		    t->callback != callback)
196		{
197			va_copy(va, v);
198			while ((f = va_arg(va, void (*)(void *))))
199				if (f == t->callback)
200					break;
201			va_end(va);
202			if (!f) {
203				if (last)
204					last->next = t->next;
205				else
206					timeouts = t->next;
207				t->next = free_timeouts;
208				free_timeouts = t;
209				continue;
210			}
211		}
212		last = t;
213	}
214}
215
216void
217delete_q_timeouts(int queue, void *arg, void (*callback)(void *), ...)
218{
219	va_list va;
220
221	va_start(va, callback);
222	v_delete_q_timeouts(queue, arg, callback, va);
223	va_end(va);
224}
225
226void
227delete_q_timeout(int queue, void (*callback)(void *), void *arg)
228{
229	struct timeout *t, *tt, *last = NULL;
230
231	for (t = timeouts; t && (tt = t->next, 1); t = tt) {
232		if (t->queue == queue && t->arg == arg &&
233		    (!callback || t->callback == callback))
234		{
235			if (last)
236				last->next = t->next;
237			else
238				timeouts = t->next;
239			t->next = free_timeouts;
240			free_timeouts = t;
241			continue;
242		}
243		last = t;
244	}
245}
246
247#ifdef DEBUG_MEMORY
248/* Define this to free all malloced memory.
249 * Normally we don't do this as the OS will do it for us at exit,
250 * but it's handy for debugging other leaks in valgrind. */
251static void
252cleanup(void)
253{
254	struct event *e;
255	struct timeout *t;
256
257	while (events) {
258		e = events->next;
259		free(events);
260		events = e;
261	}
262	while (free_events) {
263		e = free_events->next;
264		free(free_events);
265		free_events = e;
266	}
267	while (timeouts) {
268		t = timeouts->next;
269		free(timeouts);
270		timeouts = t;
271	}
272	while (free_timeouts) {
273		t = free_timeouts->next;
274		free(free_timeouts);
275		free_timeouts = t;
276	}
277	free(fds);
278}
279#endif
280
281_noreturn void
282start_eloop(void)
283{
284	int msecs, n;
285	nfds_t nfds, i;
286	struct event *e;
287	struct timeout *t;
288	struct timeval tv;
289
290#ifdef DEBUG_MEMORY
291	atexit(cleanup);
292#endif
293
294	for (;;) {
295		/* Run all timeouts first.
296		 * When we have one that has not yet occured,
297		 * calculate milliseconds until it does for use in poll. */
298		if (timeouts) {
299			if (timercmp(&now, &timeouts->when, >)) {
300				t = timeouts;
301				timeouts = timeouts->next;
302				t->callback(t->arg);
303				t->next = free_timeouts;
304				free_timeouts = t;
305				continue;
306			}
307			timersub(&timeouts->when, &now, &tv);
308			if (tv.tv_sec > INT_MAX / 1000 ||
309			    (tv.tv_sec == INT_MAX / 1000 &&
310				(tv.tv_usec + 999) / 1000 > INT_MAX % 1000))
311				msecs = INT_MAX;
312			else
313				msecs = tv.tv_sec * 1000 +
314				    (tv.tv_usec + 999) / 1000;
315		} else
316			/* No timeouts, so wait forever. */
317			msecs = -1;
318
319		/* Allocate memory for our pollfds as and when needed.
320		 * We don't bother shrinking it. */
321		nfds = 0;
322		for (e = events; e; e = e->next)
323			nfds++;
324		if (msecs == -1 && nfds == 0) {
325			syslog(LOG_ERR, "nothing to do");
326			exit(EXIT_FAILURE);
327		}
328		if (nfds > fds_len) {
329			free(fds);
330			/* Allocate 5 more than we need for future use */
331			fds_len = nfds + 5;
332			fds = xmalloc(sizeof(*fds) * fds_len);
333		}
334		nfds = 0;
335		for (e = events; e; e = e->next) {
336			fds[nfds].fd = e->fd;
337			fds[nfds].events = POLLIN;
338			fds[nfds].revents = 0;
339			nfds++;
340		}
341		n = poll(fds, nfds, msecs);
342		if (n == -1) {
343			if (errno == EAGAIN || errno == EINTR) {
344				get_monotonic(&now);
345				continue;
346			}
347			syslog(LOG_ERR, "poll: %m");
348			exit(EXIT_FAILURE);
349		}
350
351		/* Get the now time and process any triggered events. */
352		get_monotonic(&now);
353		if (n == 0)
354			continue;
355		for (i = 0; i < nfds; i++) {
356			if (!(fds[i].revents & (POLLIN | POLLHUP)))
357				continue;
358			for (e = events; e; e = e->next) {
359				if (e->fd == fds[i].fd) {
360					e->callback(e->arg);
361					break;
362				}
363			}
364		}
365	}
366}
367