Replace eloop with libevent in dhcpcd-curses.
[dhcpcd-ui] / src / dhcpcd-curses / dhcpcd-curses.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/ioctl.h>
29
30 #include <assert.h>
31 #include <curses.h>
32 #include <err.h>
33 #include <errno.h>
34 #include <signal.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39
40 #include "dhcpcd-curses.h"
41 #include "event-object.h"
42
43 #ifdef HAVE_NC_FREE_AND_EXIT
44         void _nc_free_and_exit(void);
45 #endif
46
47 static void try_open_cb(evutil_socket_t, short, void *);
48
49 static void
50 set_status(struct ctx *ctx, const char *status)
51 {
52         int w;
53         size_t slen;
54
55         w = getmaxx(ctx->win_status);
56         w -= (int)(slen = strlen(status));
57         if (ctx->status_len > slen) {
58                 wmove(ctx->win_status, 0, w - (int)(ctx->status_len - slen));
59                 wclrtoeol(ctx->win_status);
60         }
61         mvwprintw(ctx->win_status, 0, w, "%s", status);
62         wrefresh(ctx->win_status);
63         ctx->status_len = slen;
64 }
65
66 static int
67 set_summary(struct ctx *ctx, const char *msg)
68 {
69         int r;
70
71         wclear(ctx->win_summary);
72         if (msg)
73                 r = wprintw(ctx->win_summary, "%s", msg);
74         else
75                 r = 0;
76         wrefresh(ctx->win_summary);
77         return r;
78 }
79
80 __printflike(2, 3) static int
81 debug(struct ctx *ctx, const char *fmt, ...)
82 {
83         va_list args;
84         int r;
85
86         if (ctx->win_debug == NULL)
87                 return 0;
88         waddch(ctx->win_debug, '\n');
89         va_start(args, fmt);
90         r = vwprintw(ctx->win_debug, fmt, args);
91         va_end(args);
92         wrefresh(ctx->win_debug);
93         return r;
94 }
95
96 __printflike(2, 3) static int
97 warning(struct ctx *ctx, const char *fmt, ...)
98 {
99         va_list args;
100         int r;
101
102         if (ctx->win_debug == NULL)
103                 return 0;
104         waddch(ctx->win_debug, '\n');
105         va_start(args, fmt);
106         r = vwprintw(ctx->win_debug, fmt, args);
107         va_end(args);
108         wrefresh(ctx->win_debug);
109         return r;
110 }
111
112 __printflike(2, 3) static int
113 notify(struct ctx *ctx, const char *fmt, ...)
114 {
115         va_list args;
116         int r;
117
118         if (ctx->win_debug == NULL)
119                 return 0;
120         waddch(ctx->win_debug, '\n');
121         va_start(args, fmt);
122         r = vwprintw(ctx->win_debug, fmt, args);
123         va_end(args);
124         wrefresh(ctx->win_debug);
125         return r;
126 }
127
128 static void
129 update_online(struct ctx *ctx, bool show_if)
130 {
131         char *msg, *msgs, *nmsg;
132         size_t msgs_len, mlen;
133         DHCPCD_IF *ifs, *i;
134
135         msgs = NULL;
136         msgs_len = 0;
137         ifs = dhcpcd_interfaces(ctx->con);
138         for (i = ifs; i; i = i->next) {
139                 msg = dhcpcd_if_message(i, NULL);
140                 if (msg) {
141                         if (show_if) {
142                                 if (i->up)
143                                         notify(ctx, "%s", msg);
144                                 else
145                                         warning(ctx, "%s", msg);
146                         }
147                         if (msgs == NULL) {
148                                 msgs = msg;
149                                 msgs_len = strlen(msgs) + 1;
150                         } else {
151                                 mlen = strlen(msg) + 1;
152                                 nmsg = realloc(msgs, msgs_len + mlen);
153                                 if (nmsg) {
154                                         msgs = nmsg;
155                                         msgs[msgs_len - 1] = '\n';
156                                         memcpy(msgs + msgs_len, msg, mlen);
157                                         msgs_len += mlen;
158                                 } else
159                                         warn("realloc");
160                                 free(msg);
161                         }
162                 } else if (show_if) {
163                         if (i->up)
164                                 notify(ctx, "%s: %s", i->ifname, i->reason);
165                         else
166                                 warning(ctx, "%s: %s", i->ifname, i->reason);
167                 }
168         }
169
170         set_summary(ctx, msgs);
171         free(msgs);
172 }
173
174 static void
175 dispatch_cb(evutil_socket_t fd, __unused short what, void *arg)
176 {
177         struct ctx *ctx = arg;
178
179         if (fd == -1 || dhcpcd_get_fd(ctx->con) == -1) {
180                 struct timeval tv = { 0, DHCPCD_RETRYOPEN * MSECS_PER_NSEC };
181                 struct event *ev;
182
183                 if (fd != -1)
184                         warning(ctx, _("dhcpcd connection lost"));
185                 event_object_find_delete(ctx->evobjects, ctx);
186                 ev = evtimer_new(ctx->evbase, try_open_cb, ctx);
187                 if (ev == NULL ||
188                     event_object_add(ctx->evobjects, ev, &tv, ctx) == NULL)
189                         warning(ctx, "dispatch: event: %s", strerror(errno));
190                 return;
191         }
192
193         dhcpcd_dispatch(ctx->con);
194 }
195
196 static void
197 try_open_cb(__unused evutil_socket_t fd, __unused short what, void *arg)
198 {
199         struct ctx *ctx = arg;
200         static int last_error;
201         EVENT_OBJECT *eo;
202         struct event *ev;
203
204         eo = event_object_find(ctx->evobjects, ctx);
205         ctx->fd = dhcpcd_open(ctx->con, true);
206         if (ctx->fd == -1) {
207                 struct timeval tv = { 0, DHCPCD_RETRYOPEN * MSECS_PER_NSEC };
208
209                 if (errno == EACCES || errno == EPERM) {
210                         ctx->fd = dhcpcd_open(ctx->con, false);
211                         if (ctx->fd != -1)
212                                 goto unprived;
213                 }
214                 if (errno != last_error) {
215                         last_error = errno;
216                         set_status(ctx, strerror(errno));
217                 }
218                 event_del(eo->event);
219                 event_add(eo->event, &tv);
220                 return;
221         }
222
223 unprived:
224         event_object_delete(ctx->evobjects, eo);
225
226         /* Start listening to WPA events */
227         dhcpcd_wpa_start(ctx->con);
228
229         ev = event_new(ctx->evbase, ctx->fd, EV_READ | EV_PERSIST,
230             dispatch_cb, ctx);
231         if (ev == NULL ||
232             event_object_add(ctx->evobjects, ev, NULL, ctx) == NULL)
233                 warning(ctx, "event_new: %s", strerror(errno));
234 }
235
236 static void
237 status_cb(DHCPCD_CONNECTION *con,
238     unsigned int status, const char *status_msg, void *arg)
239 {
240         struct ctx *ctx = arg;
241
242         debug(ctx, _("Status changed to %s"), status_msg);
243         set_status(ctx, status_msg);
244
245         if (status == DHC_DOWN) {
246                 ctx->fd = -1;
247                 ctx->online = ctx->carrier = false;
248                 set_summary(ctx, NULL);
249                 dispatch_cb(-1, 0, ctx);
250         } else {
251                 bool refresh;
252
253                 if (ctx->last_status == DHC_UNKNOWN ||
254                     ctx->last_status == DHC_DOWN)
255                 {
256                         debug(ctx, _("Connected to dhcpcd-%s"),
257                             dhcpcd_version(con));
258                         refresh = true;
259                 } else
260                         refresh =
261                             ctx->last_status == DHC_OPENED ? true : false;
262                 update_online(ctx, refresh);
263         }
264
265         ctx->last_status = status;
266 }
267
268 static void
269 if_cb(DHCPCD_IF *i, void *arg)
270 {
271         struct ctx *ctx = arg;
272
273         if (i->state == DHS_RENEW ||
274             i->state == DHS_STOP || i->state == DHS_STOPPED)
275         {
276                 char *msg;
277                 bool new_msg;
278
279                 msg = dhcpcd_if_message(i, &new_msg);
280                 if (msg) {
281                         if (i->up)
282                                 warning(ctx, "%s", msg);
283                         else
284                                 notify(ctx, "%s", msg);
285                         free(msg);
286                 }
287         }
288
289         update_online(ctx, false);
290
291         if (i->wireless) {
292                 /* PROCESS SCANS */
293         }
294 }
295
296 static void
297 wpa_dispatch_cb(__unused evutil_socket_t fd, __unused short what, void *arg)
298 {
299         DHCPCD_WPA *wpa = arg;
300
301         dhcpcd_wpa_dispatch(wpa);
302 }
303
304
305 static void
306 wpa_scan_cb(DHCPCD_WPA *wpa, void *arg)
307 {
308         struct ctx *ctx = arg;
309         EVENT_OBJECT *eo;
310         DHCPCD_IF *i;
311         WI_SCAN *wi;
312         DHCPCD_WI_SCAN *scans, *s1, *s2;
313         int fd, lerrno;
314
315         /* This could be a new WPA so watch it */
316         if ((fd = dhcpcd_wpa_get_fd(wpa)) == -1) {
317                 debug(ctx, "%s (%p)", _("no fd for WPA"), wpa);
318                 return;
319         }
320         if ((eo = event_object_find(ctx->evobjects, wpa)) == NULL) {
321                 struct event *ev;
322
323                 ev = event_new(ctx->evbase, fd, EV_READ | EV_PERSIST,
324                     wpa_dispatch_cb, wpa);
325                 if (ev == NULL ||
326                     event_object_add(ctx->evobjects, ev, NULL, wpa) == NULL)
327                         warning(ctx, "event_new: %s", strerror(errno));
328         }
329
330         i = dhcpcd_wpa_if(wpa);
331         if (i == NULL) {
332                 debug(ctx, "%s (%p)", _("No interface for WPA"), wpa);
333                 return;
334         }
335         debug(ctx, "%s: %s", i->ifname, _("Received scan results"));
336         lerrno = errno;
337         errno = 0;
338         scans = dhcpcd_wi_scans(i);
339         if (scans == NULL && errno)
340                 debug(ctx, "%s: %s", i->ifname, strerror(errno));
341         errno = lerrno;
342         TAILQ_FOREACH(wi, &ctx->wi_scans, next) {
343                 if (wi->interface == i)
344                         break;
345         }
346         if (wi == NULL) {
347                 wi = malloc(sizeof(*wi));
348                 wi->interface = i;
349                 wi->scans = scans;
350                 TAILQ_INSERT_TAIL(&ctx->wi_scans, wi, next);
351         } else {
352                 const char *title;
353                 char *msgs, *nmsg;
354                 size_t msgs_len, mlen;
355
356                 title = NULL;
357                 msgs = NULL;
358                 for (s1 = scans; s1; s1 = s1->next) {
359                         for (s2 = wi->scans; s2; s2 = s2->next)
360                                 if (strcmp(s1->ssid, s2->ssid) == 0)
361                                         break;
362                         if (s2 == NULL) {
363                                 if (msgs == NULL) {
364                                         msgs = strdup(s1->ssid);
365                                         msgs_len = strlen(msgs) + 1;
366                                 } else {
367                                         if (title == NULL)
368                                                 title = _("New Access Points");
369                                         mlen = strlen(s1->ssid) + 1;
370                                         nmsg = realloc(msgs, msgs_len + mlen);
371                                         if (nmsg) {
372                                                 msgs = nmsg;
373                                                 msgs[msgs_len - 1] = '\n';
374                                                 memcpy(msgs + msgs_len,
375                                                     s1->ssid, mlen);
376                                                 msgs_len += mlen;
377                                         } else
378                                                 warn("realloc");
379                                 }
380                         }
381                 }
382                 if (msgs) {
383                         if (title == NULL)
384                                 title = _("New Access Point");
385                         mlen = strlen(title) + 1;
386                         nmsg = realloc(msgs, msgs_len + mlen);
387                         if (nmsg) {
388                                 msgs = nmsg;
389                                 memmove(msgs + mlen, msgs, msgs_len);
390                                 memcpy(msgs, title, mlen);
391                                 msgs[mlen - 1] = '\n';
392                         } else
393                                 warn("realloc");
394                         notify(ctx, "%s", msgs);
395                         free(msgs);
396                 }
397
398                 dhcpcd_wi_scans_free(wi->scans);
399                 wi->scans = scans;
400         }
401 }
402
403 static void
404 wpa_status_cb(DHCPCD_WPA *wpa,
405     unsigned int status, const char *status_msg, void *arg)
406 {
407         struct ctx *ctx = arg;
408         DHCPCD_IF *i;
409         WI_SCAN *w, *wn;
410
411         i = dhcpcd_wpa_if(wpa);
412         debug(ctx, _("%s: WPA status %s"), i->ifname, status_msg);
413         if (status == DHC_DOWN) {
414                 event_object_find_delete(ctx->evobjects, wpa);
415                 TAILQ_FOREACH_SAFE(w, &ctx->wi_scans, next, wn) {
416                         if (w->interface == i) {
417                                 TAILQ_REMOVE(&ctx->wi_scans, w, next);
418                                 dhcpcd_wi_scans_free(w->scans);
419                                 free(w);
420                         }
421                 }
422         }
423 }
424
425 static void
426 bg_scan_cb(__unused evutil_socket_t fd, __unused short what, void *arg)
427 {
428         struct ctx *ctx = arg;
429         WI_SCAN *w;
430         DHCPCD_WPA *wpa;
431
432         TAILQ_FOREACH(w, &ctx->wi_scans, next) {
433                 if (w->interface->wireless& w->interface->up) {
434                         wpa = dhcpcd_wpa_find(ctx->con, w->interface->ifname);
435                         if (wpa &&
436                             (!w->interface->up ||
437                             dhcpcd_wpa_can_background_scan(wpa)))
438                                 dhcpcd_wpa_scan(wpa);
439                 }
440         }
441 }
442
443 static void
444 sigint_cb(__unused evutil_socket_t fd, __unused short what, void *arg)
445 {
446         struct ctx *ctx = arg;
447
448         debug(ctx, _("caught SIGINT, exiting"));
449         event_base_loopbreak(ctx->evbase);
450 }
451
452 static void
453 sigterm_cb(__unused evutil_socket_t fd, __unused short what, void *arg)
454 {
455         struct ctx *ctx = arg;
456
457         debug(ctx, _("caught SIGTERM, exiting"));
458         event_base_loopbreak(ctx->evbase);
459 }
460
461 static void
462 sigwinch_cb(__unused evutil_socket_t fd, __unused short what,
463     __unused void *arg)
464 {
465         struct winsize ws;
466
467         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
468                 resizeterm(ws.ws_row, ws.ws_col);
469 }
470
471 static int
472 setup_signals(struct ctx *ctx)
473 {
474         struct event *ev;
475
476         ev = evsignal_new(ctx->evbase, SIGINT, sigint_cb, ctx);
477         if (ev == NULL || event_add(ev, NULL) == -1)
478                 return -1;
479         ev = evsignal_new(ctx->evbase, SIGTERM, sigterm_cb, ctx);
480         if (ev == NULL || event_add(ev, NULL) == -1)
481                 return -1;
482         ev = evsignal_new(ctx->evbase, SIGWINCH, sigwinch_cb, ctx);
483         if (ev == NULL || event_add(ev, NULL) == -1)
484                 return -1;
485         return 0;
486 }
487
488 static int
489 create_windows(struct ctx *ctx)
490 {
491         int h, w;
492
493         getmaxyx(ctx->stdscr, h, w);
494
495         if ((ctx->win_status = newwin(1, w, 0, 0)) == NULL)
496                 return -1;
497
498         if ((ctx->win_summary_border = newwin(10, w - 2, 2, 1)) == NULL)
499                 return -1;
500         box(ctx->win_summary_border, 0, 0);
501         mvwprintw(ctx->win_summary_border, 0, 5, " %s ",
502             _("Connection Summary"));
503         wrefresh(ctx->win_summary_border);
504         if ((ctx->win_summary = newwin(8, w - 4, 3, 2)) == NULL)
505                 return -1;
506         scrollok(ctx->win_summary, TRUE);
507
508 #if 1
509         if ((ctx->win_debug_border = newwin(8, w - 2, h - 10, 1)) == NULL)
510                 return -1;
511         box(ctx->win_debug_border, 0, 0);
512         mvwprintw(ctx->win_debug_border, 0, 5, " %s ",
513             _("Event Log"));
514         wrefresh(ctx->win_debug_border);
515         if ((ctx->win_debug = newwin(6, w - 4, h - 9, 2)) == NULL)
516                 return -1;
517         scrollok(ctx->win_debug, TRUE);
518 #endif
519         return 0;
520 }
521
522 int
523 main(void)
524 {
525         struct ctx ctx;
526         WI_SCAN *wi;
527         struct timeval tv0 = { 0, 0 };
528         struct timeval tv_short = { 0, DHCPCD_WPA_SCAN_SHORT };
529         struct event *ev;
530
531         memset(&ctx, 0, sizeof(ctx));
532         ctx.fd = -1;
533         if ((ctx.evobjects = event_object_new()) == NULL)
534                 err(EXIT_FAILURE, "event_object_new");
535         TAILQ_INIT(&ctx.wi_scans);
536
537         if ((ctx.evbase = event_base_new()) == NULL)
538                 err(EXIT_FAILURE, "event_base_new");
539
540         if (setup_signals(&ctx) == -1)
541                 err(EXIT_FAILURE, "setup_signals");
542
543         if ((ctx.con = dhcpcd_new()) == NULL)
544                 err(EXIT_FAILURE, "dhcpcd_new");
545
546         if ((ctx.stdscr = initscr()) == NULL)
547                 err(EXIT_FAILURE, "initscr");
548
549         if (create_windows(&ctx) == -1)
550                 err(EXIT_FAILURE, "create_windows");
551
552         curs_set(0);
553         noecho();
554         keypad(ctx.stdscr, TRUE);
555
556         wprintw(ctx.win_status, "%s %s", _("dhcpcd Curses Interface"), VERSION);
557         dhcpcd_set_progname(ctx.con, "dhcpcd-curses");
558         dhcpcd_set_status_callback(ctx.con, status_cb, &ctx);
559         dhcpcd_set_if_callback(ctx.con, if_cb, &ctx);
560         dhcpcd_wpa_set_scan_callback(ctx.con, wpa_scan_cb, &ctx);
561         dhcpcd_wpa_set_status_callback(ctx.con, wpa_status_cb, &ctx);
562
563         if ((ev = event_new(ctx.evbase, 0, 0, try_open_cb, &ctx)) == NULL)
564                 err(EXIT_FAILURE, "event_new");
565         if (event_object_add(ctx.evobjects, ev, &tv0, &ctx) == NULL)
566                 err(EXIT_FAILURE, "event_object_add");
567         if ((ev = event_new(ctx.evbase, EV_PERSIST, 0, bg_scan_cb, &ctx)) == NULL)
568                 err(EXIT_FAILURE, "event_new");
569         if (event_add(ev, &tv_short) == -1)
570                 err(EXIT_FAILURE, "event_add");
571         event_base_dispatch(ctx.evbase);
572
573         /* Un-resgister the callbacks to avoid spam on close */
574         dhcpcd_set_status_callback(ctx.con, NULL, NULL);
575         dhcpcd_set_if_callback(ctx.con, NULL, NULL);
576         dhcpcd_wpa_set_scan_callback(ctx.con, NULL, NULL);
577         dhcpcd_wpa_set_status_callback(ctx.con, NULL, NULL);
578         dhcpcd_close(ctx.con);
579         dhcpcd_free(ctx.con);
580
581         /* Free our saved scans */
582         while ((wi = TAILQ_FIRST(&ctx.wi_scans))) {
583                 TAILQ_REMOVE(&ctx.wi_scans, wi, next);
584                 dhcpcd_wi_scans_free(wi->scans);
585                 free(wi);
586         }
587
588         /* Free everything else */
589         event_base_free(ctx.evbase);
590         event_object_free(ctx.evobjects);
591         endwin();
592
593 #ifdef HAVE_NC_FREE_AND_EXIT
594         /* undefined ncurses function to allow valgrind debugging */
595         _nc_free_and_exit();
596 #endif
597
598         return EXIT_SUCCESS;
599 }