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