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