ui/ncurses: Add link status to system info & system config screens
[petitboot] / ui / ncurses / nc-config.c
1 /*
2  *  Copyright (C) 2013 IBM Corporation
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; version 2 of the License.
7  *
8  *  This program is distributed in the hope that it will be useful,
9  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  *  GNU General Public License for more details.
12  *
13  *  You should have received a copy of the GNU General Public License
14  *  along with this program; if not, write to the Free Software
15  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16  */
17
18 #define _GNU_SOURCE
19
20 #include <errno.h>
21 #include <stdlib.h>
22 #include <string.h>
23
24 #include <pb-config/pb-config.h>
25 #include <talloc/talloc.h>
26 #include <types/types.h>
27 #include <log/log.h>
28
29 #include "config.h"
30 #include "nc-cui.h"
31 #include "nc-config.h"
32 #include "nc-widgets.h"
33
34 #define N_FIELDS        23
35
36 enum net_conf_type {
37         NET_CONF_TYPE_DHCP_ALL,
38         NET_CONF_TYPE_DHCP_ONE,
39         NET_CONF_TYPE_STATIC,
40 };
41
42 struct config_screen {
43         struct nc_scr           scr;
44         struct cui              *cui;
45         struct nc_widgetset     *widgetset;
46         WINDOW                  *pad;
47
48         bool                    exit;
49         void                    (*on_exit)(struct cui *);
50
51         int                     scroll_y;
52
53         int                     label_x;
54         int                     field_x;
55         int                     network_config_y;
56
57         enum net_conf_type      net_conf_type;
58
59         struct {
60                 struct nc_widget_checkbox       *autoboot_f;
61                 struct nc_widget_label          *autoboot_l;
62                 struct nc_widget_textbox        *timeout_f;
63                 struct nc_widget_label          *timeout_l;
64                 struct nc_widget_label          *timeout_help_l;
65
66                 struct nc_widget_label          *network_l;
67                 struct nc_widget_select         *network_f;
68
69                 struct nc_widget_label          *iface_l;
70                 struct nc_widget_select         *iface_f;
71                 struct nc_widget_label          *ip_addr_l;
72                 struct nc_widget_textbox        *ip_addr_f;
73                 struct nc_widget_label          *ip_mask_l;
74                 struct nc_widget_textbox        *ip_mask_f;
75                 struct nc_widget_label          *ip_addr_mask_help_l;
76                 struct nc_widget_label          *gateway_l;
77                 struct nc_widget_textbox        *gateway_f;
78                 struct nc_widget_label          *gateway_help_l;
79                 struct nc_widget_label          *dns_l;
80                 struct nc_widget_textbox        *dns_f;
81                 struct nc_widget_label          *dns_dhcp_help_l;
82                 struct nc_widget_label          *dns_help_l;
83
84                 struct nc_widget_button         *ok_b;
85                 struct nc_widget_button         *cancel_b;
86         } widgets;
87 };
88
89 static struct config_screen *config_screen_from_scr(struct nc_scr *scr)
90 {
91         struct config_screen *config_screen;
92
93         assert(scr->sig == pb_config_screen_sig);
94         config_screen = (struct config_screen *)
95                 ((char *)scr - (size_t)&((struct config_screen *)0)->scr);
96         assert(config_screen->scr.sig == pb_config_screen_sig);
97         return config_screen;
98 }
99
100 static void pad_refresh(struct config_screen *screen)
101 {
102         int y, x, rows, cols;
103
104         getmaxyx(screen->scr.sub_ncw, rows, cols);
105         getbegyx(screen->scr.sub_ncw, y, x);
106
107         prefresh(screen->pad, screen->scroll_y, 0, y, x, rows, cols);
108 }
109
110 static void config_screen_process_key(struct nc_scr *scr, int key)
111 {
112         struct config_screen *screen = config_screen_from_scr(scr);
113         bool handled;
114
115         handled = widgetset_process_key(screen->widgetset, key);
116         if (screen->exit)
117                 screen->on_exit(screen->cui);
118         else if (handled)
119                 pad_refresh(screen);
120 }
121
122 static void config_screen_resize(struct nc_scr *scr)
123 {
124         struct config_screen *screen = config_screen_from_scr(scr);
125         (void)screen;
126 }
127
128 static int config_screen_post(struct nc_scr *scr)
129 {
130         struct config_screen *screen = config_screen_from_scr(scr);
131         widgetset_post(screen->widgetset);
132         nc_scr_frame_draw(scr);
133         pad_refresh(screen);
134         return 0;
135 }
136
137 static int config_screen_unpost(struct nc_scr *scr)
138 {
139         struct config_screen *screen = config_screen_from_scr(scr);
140         widgetset_unpost(screen->widgetset);
141         return 0;
142 }
143
144 struct nc_scr *config_screen_scr(struct config_screen *screen)
145 {
146         return &screen->scr;
147 }
148
149 static int screen_process_form(struct config_screen *screen)
150 {
151         const struct system_info *sysinfo = screen->cui->sysinfo;
152         struct config *config = talloc_zero(screen, struct config);
153         enum net_conf_type net_conf_type;
154         struct interface_config *iface;
155         char *str, *end;
156         int rc;
157
158         config_set_defaults(config);
159
160         config->autoboot_enabled =
161                 widget_checkbox_get_value(screen->widgets.autoboot_f);
162
163
164         str = widget_textbox_get_value(screen->widgets.timeout_f);
165         if (str) {
166                 unsigned long x;
167                 errno = 0;
168                 x = strtoul(str, &end, 10);
169                 if (!errno && end != str)
170                         config->autoboot_timeout_sec = x;
171         }
172
173         net_conf_type = widget_select_get_value(screen->widgets.network_f);
174
175         /* if we don't have any network interfaces, prevent per-interface
176          * configuration */
177         if (sysinfo->n_interfaces == 0)
178                 net_conf_type = NET_CONF_TYPE_DHCP_ALL;
179
180         if (net_conf_type == NET_CONF_TYPE_DHCP_ALL) {
181                 config->network.n_interfaces = 0;
182
183         } else {
184                 int idx;
185
186                 iface = talloc_zero(config, struct interface_config);
187                 config->network.n_interfaces = 1;
188                 config->network.interfaces = talloc_array(config,
189                                 struct interface_config *, 1);
190                 config->network.interfaces[0] = iface;
191
192                 /* copy hwaddr (from the sysinfo interface data) to
193                  * the configuration */
194                 idx = widget_select_get_value(screen->widgets.iface_f);
195                 memcpy(iface->hwaddr, sysinfo->interfaces[idx]->hwaddr,
196                                 sizeof(iface->hwaddr));
197         }
198
199         if (net_conf_type == NET_CONF_TYPE_DHCP_ONE) {
200                 iface->method = CONFIG_METHOD_DHCP;
201         }
202
203         if (net_conf_type == NET_CONF_TYPE_STATIC) {
204                 iface->method = CONFIG_METHOD_STATIC;
205                 iface->static_config.address = talloc_asprintf(iface, "%s/%s",
206                                 widget_textbox_get_value(
207                                         screen->widgets.ip_addr_f),
208                                 widget_textbox_get_value(
209                                         screen->widgets.ip_mask_f));
210                 iface->static_config.gateway = talloc_strdup(iface,
211                                 widget_textbox_get_value(
212                                         screen->widgets.gateway_f));
213         }
214
215         str = widget_textbox_get_value(screen->widgets.dns_f);
216         if (str && strlen(str)) {
217                 char *dns, *tmp;
218                 int i;
219
220                 for (;;) {
221                         dns = strtok_r(str, " \t", &tmp);
222
223                         if (!dns)
224                                 break;
225
226                         i = config->network.n_dns_servers++;
227                         config->network.dns_servers = talloc_realloc(config,
228                                         config->network.dns_servers,
229                                         const char *,
230                                         config->network.n_dns_servers);
231                         config->network.dns_servers[i] =
232                                 talloc_strdup(config, dns);
233
234                         str = NULL;
235                 }
236         }
237
238         rc = cui_send_config(screen->cui, config);
239         talloc_free(config);
240
241         if (rc)
242                 pb_log("cui_send_config failed!\n");
243         else
244                 pb_debug("config sent!\n");
245
246         return 0;
247 }
248
249 static void ok_click(void *arg)
250 {
251         struct config_screen *screen = arg;
252         screen_process_form(screen);
253         screen->exit = true;
254 }
255
256 static void cancel_click(void *arg)
257 {
258         struct config_screen *screen = arg;
259         screen->exit = true;
260 }
261
262 static int layout_pair(struct config_screen *screen, int y,
263                 struct nc_widget_label *label,
264                 struct nc_widget *field)
265 {
266         struct nc_widget *label_w = widget_label_base(label);
267         widget_move(label_w, y, screen->label_x);
268         widget_move(field, y, screen->field_x);
269         return max(widget_height(label_w), widget_height(field));
270 }
271
272 static void config_screen_layout_widgets(struct config_screen *screen,
273                 enum net_conf_type net_conf)
274 {
275         struct nc_widget *wl, *wf, *wh;
276         int y, x, help_x;
277         bool show;
278
279         y = 1;
280         help_x = screen->field_x + 2 +
281                 widget_width(widget_textbox_base(screen->widgets.dns_f));
282
283         y += layout_pair(screen, y, screen->widgets.autoboot_l,
284                         widget_checkbox_base(screen->widgets.autoboot_f));
285
286         wf = widget_textbox_base(screen->widgets.timeout_f);
287         widget_move(widget_label_base(screen->widgets.timeout_l),
288                         y, screen->label_x);
289         widget_move(wf, y, screen->field_x);
290         widget_move(widget_label_base(screen->widgets.timeout_help_l),
291                         y, screen->field_x + widget_width(wf) + 1);
292
293         y += 2;
294
295         y += layout_pair(screen, y, screen->widgets.network_l,
296                         widget_select_base(screen->widgets.network_f));
297
298         y += 1;
299
300         /* conditionally show iface select */
301         wl = widget_label_base(screen->widgets.iface_l);
302         wf = widget_select_base(screen->widgets.iface_f);
303
304         show = net_conf == NET_CONF_TYPE_DHCP_ONE ||
305                 net_conf == NET_CONF_TYPE_STATIC;
306
307         widget_set_visible(wl, show);
308         widget_set_visible(wf, show);
309
310         if (show)
311                 y += layout_pair(screen, y, screen->widgets.iface_l, wf) + 1;
312
313         /* conditionally show static IP params */
314         show = net_conf == NET_CONF_TYPE_STATIC;
315
316         wl = widget_label_base(screen->widgets.ip_addr_l);
317         wf = widget_textbox_base(screen->widgets.ip_addr_f);
318         widget_set_visible(wl, show);
319         widget_set_visible(wf, show);
320         x = screen->field_x + widget_width(wf) + 1;
321
322         if (show)
323                 layout_pair(screen, y, screen->widgets.ip_addr_l, wf);
324
325         wl = widget_label_base(screen->widgets.ip_mask_l);
326         wf = widget_textbox_base(screen->widgets.ip_mask_f);
327         widget_set_visible(wl, show);
328         widget_set_visible(wf, show);
329
330         if (show) {
331                 widget_move(wl, y, x);
332                 widget_move(wf, y, x + 2);
333         }
334
335         /* help for IP/mask */
336         wh = widget_label_base(screen->widgets.ip_addr_mask_help_l);
337         widget_set_visible(wh, show);
338         if (show) {
339                 widget_move(wh, y, help_x);
340                 y++;
341         }
342
343         wl = widget_label_base(screen->widgets.gateway_l);
344         wf = widget_textbox_base(screen->widgets.gateway_f);
345         wh = widget_label_base(screen->widgets.gateway_help_l);
346         widget_set_visible(wl, show);
347         widget_set_visible(wf, show);
348         widget_set_visible(wh, show);
349
350         if (show) {
351                 layout_pair(screen, y, screen->widgets.gateway_l, wf);
352                 widget_move(wh, y, help_x);
353                 y++;
354         }
355
356         wh = widget_label_base(screen->widgets.dns_help_l);
357         layout_pair(screen, y, screen->widgets.dns_l,
358                         widget_textbox_base(screen->widgets.dns_f));
359         widget_move(wh, y, help_x);
360         y++;
361
362         /* we show the DNS/DHCP help if we're configuring DHCP */
363         show = net_conf != NET_CONF_TYPE_STATIC;
364         wl = widget_label_base(screen->widgets.dns_dhcp_help_l);
365         widget_set_visible(wl, show);
366         if (show) {
367                 widget_move(wl, y, screen->field_x);
368                 y += 1;
369         }
370
371         y += 1;
372
373         widget_move(widget_button_base(screen->widgets.ok_b),
374                         y, screen->field_x);
375         widget_move(widget_button_base(screen->widgets.cancel_b),
376                         y, screen->field_x + 10);
377 }
378
379 static void config_screen_network_change(void *arg, int value)
380 {
381         struct config_screen *screen = arg;
382         screen->net_conf_type = value;
383         widgetset_unpost(screen->widgetset);
384         config_screen_layout_widgets(screen, value);
385         widgetset_post(screen->widgetset);
386 }
387
388 static struct interface_config *first_active_interface(
389                 const struct config *config)
390 {
391         unsigned int i;
392
393         for (i = 0; i < config->network.n_interfaces; i++) {
394                 if (config->network.interfaces[i]->ignore)
395                         continue;
396                 return config->network.interfaces[i];
397         }
398         return NULL;
399 }
400
401 static enum net_conf_type find_net_conf_type(const struct config *config)
402 {
403         struct interface_config *ifcfg;
404
405         ifcfg = first_active_interface(config);
406
407         if (!ifcfg)
408                 return NET_CONF_TYPE_DHCP_ALL;
409
410         else if (ifcfg->method == CONFIG_METHOD_DHCP)
411                 return NET_CONF_TYPE_DHCP_ONE;
412
413         else if (ifcfg->method == CONFIG_METHOD_STATIC)
414                 return NET_CONF_TYPE_STATIC;
415
416         assert(0);
417         return NET_CONF_TYPE_DHCP_ALL;
418 }
419
420 static void config_screen_setup_empty(struct config_screen *screen)
421 {
422         widget_new_label(screen->widgetset, 2, screen->field_x,
423                         "Waiting for configuration data...");
424         screen->widgets.cancel_b = widget_new_button(screen->widgetset,
425                         4, screen->field_x, 6, "Cancel", cancel_click, screen);
426 }
427
428
429 static void config_screen_setup_widgets(struct config_screen *screen,
430                 const struct config *config,
431                 const struct system_info *sysinfo)
432 {
433         struct nc_widgetset *set = screen->widgetset;
434         struct interface_config *ifcfg;
435         char *str, *ip, *mask, *gw;
436         enum net_conf_type type;
437         unsigned int i;
438
439         build_assert(sizeof(screen->widgets) / sizeof(struct widget *)
440                         == N_FIELDS);
441
442         type = screen->net_conf_type;
443         ifcfg = first_active_interface(config);
444
445         screen->widgets.autoboot_l = widget_new_label(set, 0, 0, "Autoboot:");
446         screen->widgets.autoboot_f = widget_new_checkbox(set, 0, 0,
447                                         config->autoboot_enabled);
448
449         str = talloc_asprintf(screen, "%d", config->autoboot_timeout_sec);
450         screen->widgets.timeout_l = widget_new_label(set, 0, 0, "Timeout:");
451         screen->widgets.timeout_f = widget_new_textbox(set, 0, 0, 5, str);
452         screen->widgets.timeout_help_l = widget_new_label(set, 0, 0, "seconds");
453
454         screen->widgets.network_l = widget_new_label(set, 0, 0, "Network");
455         screen->widgets.network_f = widget_new_select(set, 0, 0, 50);
456
457         widget_select_add_option(screen->widgets.network_f,
458                                         NET_CONF_TYPE_DHCP_ALL,
459                                         "DHCP on all active interfaces",
460                                         type == NET_CONF_TYPE_DHCP_ALL);
461         widget_select_add_option(screen->widgets.network_f,
462                                         NET_CONF_TYPE_DHCP_ONE,
463                                         "DHCP on a specific interface",
464                                         type == NET_CONF_TYPE_DHCP_ONE);
465         widget_select_add_option(screen->widgets.network_f,
466                                         NET_CONF_TYPE_STATIC,
467                                         "Static IP configuration",
468                                         type == NET_CONF_TYPE_STATIC);
469
470         widget_select_on_change(screen->widgets.network_f,
471                         config_screen_network_change, screen);
472
473         screen->widgets.iface_l = widget_new_label(set, 0, 0, "Device:");
474         screen->widgets.iface_f = widget_new_select(set, 0, 0, 50);
475
476         for (i = 0; i < sysinfo->n_interfaces; i++) {
477                 struct interface_info *info = sysinfo->interfaces[i];
478                 char str[50], mac[20];
479                 bool is_default;
480
481                 is_default = ifcfg && !memcmp(ifcfg->hwaddr, info->hwaddr,
482                                         sizeof(ifcfg->hwaddr));
483
484                 mac_str(info->hwaddr, info->hwaddr_size, mac, sizeof(mac));
485                 snprintf(str, sizeof(str), "%s [%s, %s]", info->name, mac,
486                                 info->link ? "link up" : "link down");
487
488                 widget_select_add_option(screen->widgets.iface_f,
489                                                 i, str, is_default);
490         }
491
492         gw = ip = mask = NULL;
493         if (ifcfg && ifcfg->method == CONFIG_METHOD_STATIC) {
494                 char *sep;
495
496                 str = talloc_strdup(screen, ifcfg->static_config.address);
497                 sep = strchr(str, '/');
498                 ip = str;
499
500                 if (sep) {
501                         *sep = '\0';
502                         mask = sep + 1;
503                 }
504                 gw = ifcfg->static_config.gateway;
505         }
506
507         screen->widgets.ip_addr_l = widget_new_label(set, 0, 0, "IP/mask:");
508         screen->widgets.ip_addr_f = widget_new_textbox(set, 0, 0, 16, ip);
509         screen->widgets.ip_mask_l = widget_new_label(set, 0, 0, "/");
510         screen->widgets.ip_mask_f = widget_new_textbox(set, 0, 0, 3, mask);
511         screen->widgets.ip_addr_mask_help_l =
512                 widget_new_label(set, 0, 0, "(eg. 192.168.0.10 / 24)");
513
514         screen->widgets.gateway_l = widget_new_label(set, 0, 0, "Gateway:");
515         screen->widgets.gateway_f = widget_new_textbox(set, 0, 0, 16, gw);
516         screen->widgets.gateway_help_l =
517                 widget_new_label(set, 0, 0, "(eg. 192.168.0.1)");
518
519         str = talloc_strdup(screen, "");
520         for (i = 0; i < config->network.n_dns_servers; i++) {
521                 str = talloc_asprintf_append(str, "%s%s",
522                                 (i == 0) ? "" : " ",
523                                 config->network.dns_servers[i]);
524         }
525
526         screen->widgets.dns_l = widget_new_label(set, 0, 0, "DNS Server(s):");
527         screen->widgets.dns_f = widget_new_textbox(set, 0, 0, 32, str);
528         screen->widgets.dns_help_l =
529                 widget_new_label(set, 0, 0, "(eg. 192.168.0.2)");
530
531         screen->widgets.dns_dhcp_help_l = widget_new_label(set, 0, 0,
532                         "(if not provided by DHCP server)");
533
534         screen->widgets.ok_b = widget_new_button(set, 0, 0, 6, "OK",
535                         ok_click, screen);
536         screen->widgets.cancel_b = widget_new_button(set, 0, 0, 6, "Cancel",
537                         cancel_click, screen);
538 }
539
540 static void config_screen_widget_focus(struct nc_widget *widget, void *arg)
541 {
542         struct config_screen *screen = arg;
543         int w_y, s_max;
544
545         w_y = widget_y(widget) + widget_focus_y(widget);
546         s_max = getmaxy(screen->scr.sub_ncw) - 1;
547
548         if (w_y < screen->scroll_y)
549                 screen->scroll_y = w_y;
550
551         else if (w_y + screen->scroll_y + 1 > s_max)
552                 screen->scroll_y = 1 + w_y - s_max;
553
554         else
555                 return;
556
557         pad_refresh(screen);
558 }
559
560
561 void config_screen_update(struct config_screen *screen,
562                 const struct config *config,
563                 const struct system_info *sysinfo)
564 {
565         bool repost = false;
566         int height;
567
568         /* The size of the pad we'll need depends on the number of interfaces.
569          *
570          * We use N_FIELDS (which is quite conservative, as some fields share
571          * a line) as a base, then add 3 (as the network select field is
572          * takes 3 lines), and n_interfaces (as the network interface field
573          * has n_interfaces lines).
574          */
575         height = N_FIELDS + 3;
576         if (sysinfo)
577                 height += sysinfo->n_interfaces;
578         if (!screen->pad || getmaxy(screen->pad) < height) {
579                 if (screen->pad)
580                         delwin(screen->pad);
581                 screen->pad = newpad(height, COLS);
582         }
583
584         if (screen->widgetset) {
585                 widgetset_unpost(screen->widgetset);
586                 talloc_free(screen->widgetset);
587                 repost = true;
588         }
589
590         screen->widgetset = widgetset_create(screen, screen->scr.main_ncw,
591                         screen->pad);
592         widgetset_set_widget_focus(screen->widgetset,
593                         config_screen_widget_focus, screen);
594
595         if (!config || !sysinfo) {
596                 config_screen_setup_empty(screen);
597         } else {
598                 screen->net_conf_type = find_net_conf_type(config);
599
600                 config_screen_setup_widgets(screen, config, sysinfo);
601                 config_screen_layout_widgets(screen, screen->net_conf_type);
602         }
603
604         if (repost)
605                 widgetset_post(screen->widgetset);
606
607         pad_refresh(screen);
608 }
609
610 static int config_screen_destroy(void *arg)
611 {
612         struct config_screen *screen = arg;
613         if (screen->pad)
614                 delwin(screen->pad);
615         return 0;
616 }
617
618 struct config_screen *config_screen_init(struct cui *cui,
619                 const struct config *config,
620                 const struct system_info *sysinfo,
621                 void (*on_exit)(struct cui *))
622 {
623         struct config_screen *screen;
624
625         screen = talloc_zero(cui, struct config_screen);
626         talloc_set_destructor(screen, config_screen_destroy);
627         nc_scr_init(&screen->scr, pb_config_screen_sig, 0,
628                         cui, config_screen_process_key,
629                         config_screen_post, config_screen_unpost,
630                         config_screen_resize);
631
632         screen->cui = cui;
633         screen->on_exit = on_exit;
634         screen->label_x = 2;
635         screen->field_x = 17;
636
637         screen->scr.frame.ltitle = talloc_strdup(screen,
638                         "Petitboot System Configuration");
639         screen->scr.frame.rtitle = NULL;
640         screen->scr.frame.help = talloc_strdup(screen,
641                         "tab=next, shift+tab=previous");
642         nc_scr_frame_draw(&screen->scr);
643
644         scrollok(screen->scr.sub_ncw, true);
645
646         config_screen_update(screen, config, sysinfo);
647
648         wrefresh(screen->scr.main_ncw);
649
650         return screen;
651 }