Use constants rather than string comparison for a saner API.
[dhcpcd-ui] / src / dhcpcd-curses / eloop.c
1 /*
2  * dhcpcd - DHCP client daemon
3  * Copyright (c) 2006-2015 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 <signal.h>
34 #include <stdlib.h>
35 #include <syslog.h>
36
37 #include <stdio.h>
38
39 #define IN_ELOOP
40
41 #include "config.h"
42 #include "common.h"
43 #include "eloop.h"
44
45 /* Handy function to get the time.
46  * We only care about time advancements, not the actual time itself
47  * Which is why we use CLOCK_MONOTONIC, but it is not available on all
48  * platforms.
49  */
50 #define NO_MONOTONIC "host does not support a monotonic clock - timing can skew"
51 static int
52 get_monotonic(struct timespec *ts)
53 {
54
55 #if defined(_POSIX_MONOTONIC_CLOCK) && defined(CLOCK_MONOTONIC)
56         return clock_gettime(CLOCK_MONOTONIC, ts);
57                 return 0;
58 #elif defined(__APPLE__)
59         /* We can use mach kernel functions here.
60          * This is crap though - why can't they implement clock_gettime?*/
61         static struct mach_timebase_info info = { 0, 0 };
62         static double factor = 0.0;
63         uint64_t nano;
64         long rem;
65
66         if (!posix_clock_set) {
67                 if (mach_timebase_info(&info) == KERN_SUCCESS) {
68                         factor = (double)info.numer / (double)info.denom;
69                         clock_monotonic = posix_clock_set = 1;
70                 }
71         }
72         if (clock_monotonic) {
73                 nano = mach_absolute_time();
74                 if ((info.denom != 1 || info.numer != 1) && factor != 0.0)
75                         nano *= factor;
76                 ts->tv_sec = nano / NSEC_PER_SEC;
77                 ts->tv_nsec = nano % NSEC_PER_SEC;
78                 if (ts->tv_nsec < 0) {
79                         ts->tv_sec--;
80                         ts->tv_nsec += NSEC_PER_SEC;
81                 }
82                 return 0;
83         }
84 #endif
85
86 #if 0
87         /* Something above failed, so fall back to gettimeofday */
88         if (!posix_clock_set) {
89                 syslog(LOG_WARNING, NO_MONOTONIC);
90                 posix_clock_set = 1;
91         }
92 #endif
93         {
94                 struct timeval tv;
95                 if (gettimeofday(&tv, NULL) == 0) {
96                         TIMEVAL_TO_TIMESPEC(&tv, ts);
97                         return 0;
98                 }
99         }
100
101         return -1;
102 }
103
104 static void
105 eloop_event_setup_fds(ELOOP_CTX *ctx)
106 {
107         struct eloop_event *e;
108         size_t i;
109
110         i = 0;
111         TAILQ_FOREACH(e, &ctx->events, next) {
112                 ctx->fds[i].fd = e->fd;
113                 ctx->fds[i].events = 0;
114                 if (e->read_cb)
115                         ctx->fds[i].events |= POLLIN;
116                 if (e->write_cb)
117                         ctx->fds[i].events |= POLLOUT;
118                 ctx->fds[i].revents = 0;
119                 e->pollfd = &ctx->fds[i];
120                 i++;
121         }
122 }
123
124 int
125 eloop_event_add(ELOOP_CTX *ctx, int fd,
126     void (*read_cb)(void *), void *read_cb_arg,
127     void (*write_cb)(void *), void *write_cb_arg)
128 {
129         struct eloop_event *e;
130         struct pollfd *nfds;
131
132         /* We should only have one callback monitoring the fd */
133         TAILQ_FOREACH(e, &ctx->events, next) {
134                 if (e->fd == fd) {
135                         if (read_cb) {
136                                 e->read_cb = read_cb;
137                                 e->read_cb_arg = read_cb_arg;
138                         }
139                         if (write_cb) {
140                                 e->write_cb = write_cb;
141                                 e->write_cb_arg = write_cb_arg;
142                         }
143                         eloop_event_setup_fds(ctx);
144                         return 0;
145                 }
146         }
147
148         /* Allocate a new event if no free ones already allocated */
149         if ((e = TAILQ_FIRST(&ctx->free_events))) {
150                 TAILQ_REMOVE(&ctx->free_events, e, next);
151         } else {
152                 e = malloc(sizeof(*e));
153                 if (e == NULL) {
154                         syslog(LOG_ERR, "%s: %m", __func__);
155                         return -1;
156                 }
157         }
158
159         /* Ensure we can actually listen to it */
160         ctx->events_len++;
161         if (ctx->events_len > ctx->fds_len) {
162                 ctx->fds_len += 5;
163                 nfds = malloc(sizeof(*ctx->fds) * (ctx->fds_len + 5));
164                 if (nfds == NULL) {
165                         syslog(LOG_ERR, "%s: %m", __func__);
166                         ctx->events_len--;
167                         TAILQ_INSERT_TAIL(&ctx->free_events, e, next);
168                         return -1;
169                 }
170                 ctx->fds_len += 5;
171                 free(ctx->fds);
172                 ctx->fds = nfds;
173         }
174
175         /* Now populate the structure and add it to the list */
176         e->fd = fd;
177         e->read_cb = read_cb;
178         e->read_cb_arg = read_cb_arg;
179         e->write_cb = write_cb;
180         e->write_cb_arg = write_cb_arg;
181         /* The order of events should not matter.
182          * However, some PPP servers love to close the link right after
183          * sending their final message. So to ensure dhcpcd processes this
184          * message (which is likely to be that the DHCP addresses are wrong)
185          * we insert new events at the queue head as the link fd will be
186          * the first event added. */
187         TAILQ_INSERT_HEAD(&ctx->events, e, next);
188         eloop_event_setup_fds(ctx);
189         return 0;
190 }
191
192 void
193 eloop_event_delete(ELOOP_CTX *ctx, int fd, void (*callback)(void *), void *arg,
194     int write_only)
195 {
196         struct eloop_event *e;
197
198         TAILQ_FOREACH(e, &ctx->events, next) {
199                 if (e->fd == fd ||
200                     e->read_cb == callback || (arg && e->read_cb_arg == arg))
201                 {
202                         if (write_only) {
203                                 e->write_cb = NULL;
204                                 e->write_cb_arg = NULL;
205                         } else {
206                                 TAILQ_REMOVE(&ctx->events, e, next);
207                                 TAILQ_INSERT_TAIL(&ctx->free_events, e, next);
208                                 ctx->events_len--;
209                         }
210                         eloop_event_setup_fds(ctx);
211                         break;
212                 }
213         }
214 }
215
216 int
217 eloop_q_timeout_add_tv(ELOOP_CTX *ctx, int queue,
218     const struct timespec *when, void (*callback)(void *), void *arg)
219 {
220         struct timespec now;
221         struct timespec w;
222         struct eloop_timeout *t, *tt = NULL;
223
224         get_monotonic(&now);
225         timespecadd(&now, when, &w);
226         /* Check for time_t overflow. */
227         if (timespeccmp(&w, &now, <)) {
228                 errno = ERANGE;
229                 return -1;
230         }
231
232         /* Remove existing timeout if present */
233         TAILQ_FOREACH(t, &ctx->timeouts, next) {
234                 if (t->callback == callback && t->arg == arg) {
235                         TAILQ_REMOVE(&ctx->timeouts, t, next);
236                         break;
237                 }
238         }
239
240         if (t == NULL) {
241                 /* No existing, so allocate or grab one from the free pool */
242                 if ((t = TAILQ_FIRST(&ctx->free_timeouts))) {
243                         TAILQ_REMOVE(&ctx->free_timeouts, t, next);
244                 } else {
245                         t = malloc(sizeof(*t));
246                         if (t == NULL) {
247                                 syslog(LOG_ERR, "%s: %m", __func__);
248                                 return -1;
249                         }
250                 }
251         }
252
253         t->when = *when;
254         t->callback = callback;
255         t->arg = arg;
256         t->queue = queue;
257
258         /* The timeout list should be in chronological order,
259          * soonest first. */
260         TAILQ_FOREACH(tt, &ctx->timeouts, next) {
261                 if (timespeccmp(&t->when, &tt->when, <)) {
262                         TAILQ_INSERT_BEFORE(tt, t, next);
263                         return 0;
264                 }
265         }
266         TAILQ_INSERT_TAIL(&ctx->timeouts, t, next);
267         return 0;
268 }
269
270 int
271 eloop_q_timeout_add_sec(ELOOP_CTX *ctx, int queue, time_t when,
272     void (*callback)(void *), void *arg)
273 {
274         struct timespec tv;
275
276         tv.tv_sec = when;
277         tv.tv_nsec = 0;
278         return eloop_q_timeout_add_tv(ctx, queue, &tv, callback, arg);
279 }
280
281 int
282 eloop_q_timeout_add_msec(ELOOP_CTX *ctx, int queue, suseconds_t when,
283     void (*callback)(void *), void *arg)
284 {
285         struct timespec tv;
286
287         tv.tv_sec = 0;
288         tv.tv_nsec = when * MSEC_PER_NSEC;
289         timespecnorm(&tv);
290         return eloop_q_timeout_add_tv(ctx, queue, &tv, callback, arg);
291 }
292
293 int
294 eloop_timeout_add_now(ELOOP_CTX *ctx,
295     void (*callback)(void *), void *arg)
296 {
297
298         if (ctx->timeout0 != NULL) {
299                 syslog(LOG_WARNING, "%s: timeout0 already set", __func__);
300                 return eloop_q_timeout_add_sec(ctx, 0, 0, callback, arg);
301         }
302
303         ctx->timeout0 = callback;
304         ctx->timeout0_arg = arg;
305         return 0;
306 }
307
308 void
309 eloop_q_timeout_delete(ELOOP_CTX *ctx, int queue,
310     void (*callback)(void *), void *arg)
311 {
312         struct eloop_timeout *t, *tt;
313
314         TAILQ_FOREACH_SAFE(t, &ctx->timeouts, next, tt) {
315                 if ((queue == 0 || t->queue == queue) &&
316                     t->arg == arg &&
317                     (!callback || t->callback == callback))
318                 {
319                         TAILQ_REMOVE(&ctx->timeouts, t, next);
320                         TAILQ_INSERT_TAIL(&ctx->free_timeouts, t, next);
321                 }
322         }
323 }
324
325 void
326 eloop_exit(ELOOP_CTX *ctx, int code)
327 {
328
329         ctx->exitcode = code;
330         ctx->exitnow = 1;
331 }
332
333 ELOOP_CTX *
334 eloop_init(void)
335 {
336         ELOOP_CTX *ctx;
337
338         ctx = calloc(1, sizeof(*ctx));
339         if (ctx) {
340                 TAILQ_INIT(&ctx->events);
341                 TAILQ_INIT(&ctx->free_events);
342                 TAILQ_INIT(&ctx->timeouts);
343                 TAILQ_INIT(&ctx->free_timeouts);
344                 ctx->exitcode = EXIT_FAILURE;
345         }
346         return ctx;
347 }
348
349
350 void eloop_free(ELOOP_CTX *ctx)
351 {
352         struct eloop_event *e;
353         struct eloop_timeout *t;
354
355         if (ctx == NULL)
356                 return;
357
358         while ((e = TAILQ_FIRST(&ctx->events))) {
359                 TAILQ_REMOVE(&ctx->events, e, next);
360                 free(e);
361         }
362         while ((e = TAILQ_FIRST(&ctx->free_events))) {
363                 TAILQ_REMOVE(&ctx->free_events, e, next);
364                 free(e);
365         }
366         while ((t = TAILQ_FIRST(&ctx->timeouts))) {
367                 TAILQ_REMOVE(&ctx->timeouts, t, next);
368                 free(t);
369         }
370         while ((t = TAILQ_FIRST(&ctx->free_timeouts))) {
371                 TAILQ_REMOVE(&ctx->free_timeouts, t, next);
372                 free(t);
373         }
374         free(ctx->fds);
375         free(ctx);
376 }
377
378 int
379 eloop_start(ELOOP_CTX *ctx)
380 {
381         int n;
382         struct eloop_event *e;
383         struct eloop_timeout *t;
384         struct timespec now, ts, tv, *tsp;
385         void (*t0)(void *);
386         int timeout;
387
388         for (;;) {
389                 if (ctx->exitnow)
390                         break;
391
392                 /* Run all timeouts first */
393                 if (ctx->timeout0) {
394                         t0 = ctx->timeout0;
395                         ctx->timeout0 = NULL;
396                         t0(ctx->timeout0_arg);
397                         continue;
398                 }
399                 if ((t = TAILQ_FIRST(&ctx->timeouts))) {
400                         get_monotonic(&now);
401                         if (timespeccmp(&now, &t->when, >)) {
402                                 TAILQ_REMOVE(&ctx->timeouts, t, next);
403                                 t->callback(t->arg);
404                                 TAILQ_INSERT_TAIL(&ctx->free_timeouts, t, next);
405                                 continue;
406                         }
407                         timespecsub(&t->when, &now, &tv);
408                         tsp = &ts;
409                 } else
410                         /* No timeouts, so wait forever */
411                         tsp = NULL;
412
413                 if (tsp == NULL && ctx->events_len == 0) {
414                         syslog(LOG_ERR, "nothing to do");
415                         break;
416                 }
417
418                 if (tsp == NULL)
419                         timeout = -1;
420                 else if (tsp->tv_sec > INT_MAX / 1000 ||
421                     (tsp->tv_sec == INT_MAX / 1000 &&
422                     (tsp->tv_nsec + 999999) / 1000000 > INT_MAX % 1000000))
423                         timeout = INT_MAX;
424                 else
425                         timeout = (int)(tsp->tv_sec * 1000 +
426                             (tsp->tv_nsec + 999999) / 1000000);
427                 n = poll(ctx->fds, ctx->events_len, timeout);
428                 if (n == -1) {
429                         if (errno == EINTR)
430                                 continue;
431                         syslog(LOG_ERR, "poll: %m");
432                         break;
433                 }
434
435                 /* Process any triggered events. */
436                 if (n > 0) {
437                         TAILQ_FOREACH(e, &ctx->events, next) {
438                                 if (e->pollfd->revents & POLLOUT &&
439                                         e->write_cb)
440                                 {
441                                         e->write_cb(e->write_cb_arg);
442                                         /* We need to break here as the
443                                          * callback could destroy the next
444                                          * fd to process. */
445                                         break;
446                                 }
447                                 if (e->pollfd->revents) {
448                                         e->read_cb(e->read_cb_arg);
449                                         /* We need to break here as the
450                                          * callback could destroy the next
451                                          * fd to process. */
452                                         break;
453                                 }
454                         }
455                 }
456         }
457
458         return ctx->exitcode;
459 }