Import new eloop.
[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 static const int sigs[] = {
46         SIGHUP,
47         SIGINT,
48         SIGPIPE,
49         SIGTERM,
50         SIGWINCH
51 };
52
53 #ifndef __arraycount
54 #define __arraycount(__x)       (sizeof(__x) / sizeof(__x[0]))
55 #endif
56
57 static void try_open(void *);
58
59 static void
60 set_status(struct ctx *ctx, const char *status)
61 {
62         int w;
63         size_t slen;
64
65         w = getmaxx(ctx->win_status);
66         w -= (int)(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 (i->type == DHT_LINK) {
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         dhcpcd_dispatch(ctx->con);
199 }
200
201 static void
202 try_open(void *arg)
203 {
204         struct ctx *ctx = arg;
205         static int last_error;
206         int fd;
207
208         fd = dhcpcd_open(ctx->con, true);
209         if (fd == -1) {
210                 if (errno == EACCES || errno == EPERM) {
211                         fd = dhcpcd_open(ctx->con, false);
212                         if (fd != -1)
213                                 goto unprived;
214                 }
215                 if (errno != last_error) {
216                         last_error = errno;
217                         set_status(ctx, strerror(errno));
218                 }
219                 eloop_timeout_add_msec(ctx->eloop, DHCPCD_RETRYOPEN,
220                     try_open, ctx);
221                 return;
222         }
223
224 unprived:
225         last_error = 0;
226
227         /* Start listening to WPA events */
228         dhcpcd_wpa_start(ctx->con);
229
230         eloop_event_add(ctx->eloop, fd, dispatch, ctx, NULL, NULL);
231 }
232
233 static void
234 status_cb(DHCPCD_CONNECTION *con,
235     unsigned int status, const char *status_msg, void *arg)
236 {
237         struct ctx *ctx = arg;
238
239         debug(ctx, _("Status changed to %s"), status_msg);
240         set_status(ctx, status_msg);
241
242         if (status == DHC_DOWN) {
243                 int fd;
244
245                 fd = dhcpcd_get_fd(ctx->con);
246                 eloop_event_delete(ctx->eloop, fd, 0);
247                 ctx->online = ctx->carrier = false;
248                 eloop_timeout_delete(ctx->eloop, NULL, ctx);
249                 set_summary(ctx, NULL);
250                 eloop_timeout_add_msec(ctx->eloop, DHCPCD_RETRYOPEN,
251                     try_open, ctx);
252         } else {
253                 bool refresh;
254
255                 if (ctx->last_status == DHC_UNKNOWN ||
256                     ctx->last_status == DHC_DOWN)
257                 {
258                         debug(ctx, _("Connected to dhcpcd-%s"),
259                             dhcpcd_version(con));
260                         refresh = true;
261                 } else
262                         refresh =
263                             ctx->last_status == DHC_OPENED ? true : false;
264                 update_online(ctx, refresh);
265         }
266
267         ctx->last_status = status;
268 }
269
270 static void
271 if_cb(DHCPCD_IF *i, void *arg)
272 {
273         struct ctx *ctx = arg;
274
275         if (i->state == DHS_RENEW ||
276             i->state == DHS_STOP || i->state == DHS_STOPPED)
277         {
278                 char *msg;
279                 bool new_msg;
280
281                 msg = dhcpcd_if_message(i, &new_msg);
282                 if (msg) {
283                         if (i->up)
284                                 warning(ctx, "%s", msg);
285                         else
286                                 notify(ctx, "%s", msg);
287                         free(msg);
288                 }
289         }
290
291         update_online(ctx, false);
292
293         if (i->wireless) {
294                 /* PROCESS SCANS */
295         }
296 }
297
298 static void
299 wpa_dispatch(void *arg)
300 {
301         DHCPCD_WPA *wpa = arg;
302
303         dhcpcd_wpa_dispatch(wpa);
304 }
305
306 static void
307 wpa_scan_cb(DHCPCD_WPA *wpa, void *arg)
308 {
309         struct ctx *ctx = arg;
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         eloop_event_add(ctx->eloop, fd, wpa_dispatch, wpa, NULL, NULL);
321
322         i = dhcpcd_wpa_if(wpa);
323         if (i == NULL) {
324                 debug(ctx, "%s (%p)", _("No interface for WPA"), wpa);
325                 return;
326         }
327         debug(ctx, "%s: %s", i->ifname, _("Received scan results"));
328         lerrno = errno;
329         errno = 0;
330         scans = dhcpcd_wi_scans(i);
331         if (scans == NULL && errno)
332                 debug(ctx, "%s: %s", i->ifname, strerror(errno));
333         errno = lerrno;
334         TAILQ_FOREACH(wi, &ctx->wi_scans, next) {
335                 if (wi->interface == i)
336                         break;
337         }
338         if (wi == NULL) {
339                 wi = malloc(sizeof(*wi));
340                 wi->interface = i;
341                 wi->scans = scans;
342                 TAILQ_INSERT_TAIL(&ctx->wi_scans, wi, next);
343         } else {
344                 const char *title;
345                 char *msgs, *nmsg;
346                 size_t msgs_len, mlen;
347
348                 title = NULL;
349                 msgs = NULL;
350                 for (s1 = scans; s1; s1 = s1->next) {
351                         for (s2 = wi->scans; s2; s2 = s2->next)
352                                 if (strcmp(s1->ssid, s2->ssid) == 0)
353                                         break;
354                         if (s2 == NULL) {
355                                 if (msgs == NULL) {
356                                         msgs = strdup(s1->ssid);
357                                         msgs_len = strlen(msgs) + 1;
358                                 } else {
359                                         if (title == NULL)
360                                                 title = _("New Access Points");
361                                         mlen = strlen(s1->ssid) + 1;
362                                         nmsg = realloc(msgs, msgs_len + mlen);
363                                         if (nmsg) {
364                                                 msgs = nmsg;
365                                                 msgs[msgs_len - 1] = '\n';
366                                                 memcpy(msgs + msgs_len,
367                                                     s1->ssid, mlen);
368                                                 msgs_len += mlen;
369                                         } else
370                                                 warn("realloc");
371                                 }
372                         }
373                 }
374                 if (msgs) {
375                         if (title == NULL)
376                                 title = _("New Access Point");
377                         mlen = strlen(title) + 1;
378                         nmsg = realloc(msgs, msgs_len + mlen);
379                         if (nmsg) {
380                                 msgs = nmsg;
381                                 memmove(msgs + mlen, msgs, msgs_len);
382                                 memcpy(msgs, title, mlen);
383                                 msgs[mlen - 1] = '\n';
384                         } else
385                                 warn("realloc");
386                         notify(ctx, "%s", msgs);
387                         free(msgs);
388                 }
389
390                 dhcpcd_wi_scans_free(wi->scans);
391                 wi->scans = scans;
392         }
393 }
394
395 static void
396 wpa_status_cb(DHCPCD_WPA *wpa,
397     unsigned int status, const char *status_msg, void *arg)
398 {
399         struct ctx *ctx = arg;
400         DHCPCD_IF *i;
401         WI_SCAN *w, *wn;
402
403         i = dhcpcd_wpa_if(wpa);
404         debug(ctx, _("%s: WPA status %s"), i->ifname, status_msg);
405         if (status == DHC_DOWN) {
406                 int fd;
407
408                 fd = dhcpcd_wpa_get_fd(wpa);
409                 eloop_event_delete(ctx->eloop, fd, 0);
410                 dhcpcd_wpa_close(wpa);
411                 TAILQ_FOREACH_SAFE(w, &ctx->wi_scans, next, wn) {
412                         if (w->interface == i) {
413                                 TAILQ_REMOVE(&ctx->wi_scans, w, next);
414                                 dhcpcd_wi_scans_free(w->scans);
415                                 free(w);
416                         }
417                 }
418         }
419 }
420
421 static void
422 bg_scan(void *arg)
423 {
424         struct ctx *ctx = arg;
425         WI_SCAN *w;
426         DHCPCD_WPA *wpa;
427
428         TAILQ_FOREACH(w, &ctx->wi_scans, next) {
429                 if (w->interface->wireless& w->interface->up) {
430                         wpa = dhcpcd_wpa_find(ctx->con, w->interface->ifname);
431                         if (wpa &&
432                             (!w->interface->up ||
433                             dhcpcd_wpa_can_background_scan(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
442 static void
443 signal_cb(int sig, void *arg)
444 {
445         struct ctx *ctx = arg;
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                 /* ignore and don't report */
466                 break;
467         }
468 }
469
470 static int
471 create_windows(struct ctx *ctx)
472 {
473         int h, w;
474
475         getmaxyx(ctx->stdscr, h, w);
476
477         if ((ctx->win_status = newwin(1, w, 0, 0)) == NULL)
478                 return -1;
479
480         if ((ctx->win_summary_border = newwin(10, w - 2, 2, 1)) == NULL)
481                 return -1;
482         box(ctx->win_summary_border, 0, 0);
483         mvwprintw(ctx->win_summary_border, 0, 5, " %s ",
484             _("Connection Summary"));
485         wrefresh(ctx->win_summary_border);
486         if ((ctx->win_summary = newwin(8, w - 4, 3, 2)) == NULL)
487                 return -1;
488         scrollok(ctx->win_summary, TRUE);
489
490 #if 1
491         if ((ctx->win_debug_border = newwin(8, w - 2, h - 10, 1)) == NULL)
492                 return -1;
493         box(ctx->win_debug_border, 0, 0);
494         mvwprintw(ctx->win_debug_border, 0, 5, " %s ",
495             _("Event Log"));
496         wrefresh(ctx->win_debug_border);
497         if ((ctx->win_debug = newwin(6, w - 4, h - 9, 2)) == NULL)
498                 return -1;
499         scrollok(ctx->win_debug, TRUE);
500 #endif
501         return 0;
502 }
503
504 int
505 main(void)
506 {
507         struct ctx ctx;
508         WI_SCAN *wi;
509         sigset_t sigmask;
510
511         memset(&ctx, 0, sizeof(ctx));
512         TAILQ_INIT(&ctx.wi_scans);
513
514         if ((ctx.eloop = eloop_new()) == NULL)
515                 err(EXIT_FAILURE, "eloop_new");
516         if (eloop_signal_set_cb(ctx.eloop, sigs, __arraycount(sigs),
517             signal_cb, &ctx) == -1)
518                 err(EXIT_FAILURE, "eloop_signal_set_cb");
519         if (eloop_signal_mask(ctx.eloop, &sigmask) == -1)
520                 err(EXIT_FAILURE, "eloop_signal_mask");
521
522         if ((ctx.con = dhcpcd_new()) == NULL)
523                 err(EXIT_FAILURE, "dhcpcd_new");
524
525         if ((ctx.stdscr = initscr()) == NULL)
526                 err(EXIT_FAILURE, "initscr");
527
528         if (create_windows(&ctx) == -1)
529                 err(EXIT_FAILURE, "create_windows");
530
531         curs_set(0);
532         noecho();
533         keypad(ctx.stdscr, TRUE);
534
535         wprintw(ctx.win_status, "%s %s", _("dhcpcd Curses Interface"), VERSION);
536         dhcpcd_set_progname(ctx.con, "dhcpcd-curses");
537         dhcpcd_set_status_callback(ctx.con, status_cb, &ctx);
538         dhcpcd_set_if_callback(ctx.con, if_cb, &ctx);
539         dhcpcd_wpa_set_scan_callback(ctx.con, wpa_scan_cb, &ctx);
540         dhcpcd_wpa_set_status_callback(ctx.con, wpa_status_cb, &ctx);
541
542         eloop_timeout_add_sec(ctx.eloop, 0, try_open, &ctx);
543         eloop_timeout_add_msec(ctx.eloop, DHCPCD_WPA_SCAN_SHORT,
544             bg_scan, &ctx);
545         eloop_start(ctx.eloop, &sigmask);
546
547         /* Un-resgister the callbacks to avoid spam on close */
548         dhcpcd_set_status_callback(ctx.con, NULL, NULL);
549         dhcpcd_set_if_callback(ctx.con, NULL, NULL);
550         dhcpcd_wpa_set_scan_callback(ctx.con, NULL, NULL);
551         dhcpcd_wpa_set_status_callback(ctx.con, NULL, NULL);
552         dhcpcd_close(ctx.con);
553         dhcpcd_free(ctx.con);
554
555         /* Free our saved scans */
556         while ((wi = TAILQ_FIRST(&ctx.wi_scans))) {
557                 TAILQ_REMOVE(&ctx.wi_scans, wi, next);
558                 dhcpcd_wi_scans_free(wi->scans);
559                 free(wi);
560         }
561
562         /* Free everything else */
563         eloop_free(ctx.eloop);
564         endwin();
565
566 #ifdef HAVE_NC_FREE_AND_EXIT
567         /* undefined ncurses function to allow valgrind debugging */
568         _nc_free_and_exit();
569 #endif
570
571         return EXIT_SUCCESS;
572 }