]> git.ozlabs.org Git - petitboot/blob - ui/ncurses/nc-cui.c
ui/ncurses: Simplify starting shell
[petitboot] / ui / ncurses / nc-cui.c
1 /*
2  *  Copyright (C) 2009 Sony Computer Entertainment Inc.
3  *  Copyright 2009 Sony Corp.
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; version 2 of the License.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 #if defined(HAVE_CONFIG_H)
20 #include "config.h"
21 #endif
22
23 #include <assert.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <stdlib.h>
27 #include <locale.h>
28 #include <string.h>
29 #include <sys/ioctl.h>
30 #include <sys/reboot.h>
31
32 #include "log/log.h"
33 #include "pb-protocol/pb-protocol.h"
34 #include "talloc/talloc.h"
35 #include "waiter/waiter.h"
36 #include "process/process.h"
37 #include "i18n/i18n.h"
38 #include "ui/common/discover-client.h"
39 #include "ui/common/ui-system.h"
40 #include "nc-cui.h"
41 #include "nc-boot-editor.h"
42 #include "nc-config.h"
43 #include "nc-add-url.h"
44 #include "nc-sysinfo.h"
45 #include "nc-lang.h"
46 #include "nc-helpscreen.h"
47 #include "nc-statuslog.h"
48 #include "nc-subset.h"
49 #include "nc-plugin.h"
50 #include "console-codes.h"
51
52 extern const struct help_text main_menu_help_text;
53 extern const struct help_text plugin_menu_help_text;
54
55 static bool cui_detached = false;
56
57 static struct pmenu *main_menu_init(struct cui *cui);
58 static struct pmenu *plugin_menu_init(struct cui *cui);
59
60 static void cui_cancel_autoboot_on_exit(struct cui *cui);
61
62 static struct {
63         int key;
64         struct autoboot_option opt;
65 } autoboot_override_keys[] = {
66         { KEY_F(10), {
67                         .boot_type = BOOT_DEVICE_TYPE,
68                         .type = DEVICE_TYPE_DISK,
69                 },
70         },
71         { KEY_F(11), {
72                         .boot_type = BOOT_DEVICE_TYPE,
73                         .type = DEVICE_TYPE_USB,
74                 },
75         },
76         { KEY_F(12), {
77                         .boot_type = BOOT_DEVICE_TYPE,
78                         .type = DEVICE_TYPE_NETWORK,
79                 },
80         },
81 };
82
83 static bool lockdown_active(void)
84 {
85 #if defined(SIGNED_BOOT) && defined(HARD_LOCKDOWN)
86         return true;
87 #else
88         bool lockdown = false;
89         if (access(LOCKDOWN_FILE, F_OK) != -1)
90                 lockdown = true;
91         return lockdown;
92 #endif
93 }
94
95 static void cui_set_curses_options(bool curses_mode)
96 {
97         if (curses_mode) {
98                 cbreak();                       /* Disable line buffering. */
99                 noecho();                       /* Disable getch() echo. */
100                 nonl();                         /* Disable new-line translation. */
101                 intrflush(stdscr, FALSE);       /* Disable interrupt flush. */
102                 curs_set(0);                    /* Make cursor invisible */
103                 nodelay(stdscr, TRUE);          /* Enable non-blocking getch() */
104         } else {
105                 nocbreak();                     /* Enable line buffering. */
106                 echo();                         /* Enable getch() echo. */
107                 nl();                           /* Enable new-line translation. */
108                 intrflush(stdscr, TRUE);        /* Enable interrupt flush. */
109                 curs_set(1);                    /* Make cursor visible */
110                 nodelay(stdscr, FALSE);         /* Disable non-blocking getch() */
111         }
112 }
113
114 static void cui_start(void)
115 {
116         initscr();                      /* Initialize ncurses. */
117         keypad(stdscr, TRUE);           /* Enable num keypad keys. */
118         cui_set_curses_options(true);
119
120         /* We may be operating with an incorrect $TERM type; in this case
121          * the keymappings will be slightly broken. We want at least
122          * backspace to work though, so we'll define both DEL and ^H to
123          * map to backspace */
124         define_key("\x7f", KEY_BACKSPACE);
125         define_key("\x08", KEY_BACKSPACE);
126
127         /* we need backtab too, for form navigation. vt220 doesn't include
128          * this (kcbt), but we don't want to require a full linux/xterm termcap
129          */
130         define_key("\x1b[Z", KEY_BTAB);
131
132         /* We'll define a few other keys too since they're commonly
133          * used for navigation but the escape character will cause
134          * Petitboot to exit if they're left undefined */
135         define_key("\x1b\x5b\x35\x7e", KEY_PPAGE);
136         define_key("\x1b\x5b\x36\x7e", KEY_NPAGE);
137         define_key("\x1b\x5b\x31\x7e", KEY_HOME);
138         define_key("\x1b\x5b\x34\x7e", KEY_END);
139         define_key("\x1b\x4f\x48", KEY_HOME);
140         define_key("\x1b\x4f\x46", KEY_END);
141         define_key("OH", KEY_HOME);
142         define_key("OF", KEY_END);
143
144         /* Arrow keys in normal cursor mode */
145         define_key("\x1b\x5b\x41", KEY_UP);
146         define_key("\x1b\x5b\x42", KEY_DOWN);
147         define_key("\x1b\x5b\x43", KEY_RIGHT);
148         define_key("\x1b\x5b\x44", KEY_LEFT);
149         /* Arrow keys in "application" cursor mode */
150         define_key("\x1b\x4f\x41", KEY_UP);
151         define_key("\x1b\x4f\x42", KEY_DOWN);
152         define_key("\x1b\x4f\x43", KEY_RIGHT);
153         define_key("\x1b\x4f\x44", KEY_LEFT);
154
155         define_key("\x1b\x5b\x33\x7e", KEY_DC);
156
157         while (getch() != ERR)          /* flush stdin */
158                 (void)0;
159 }
160
161 static void cui_atexit(void)
162 {
163         if (cui_detached)
164                 return;
165
166         clear();
167         refresh();
168         endwin();
169
170         bool lockdown = lockdown_active();
171
172         while (lockdown) {
173                 sync();
174                 reboot(RB_AUTOBOOT);
175         }
176 }
177
178 /**
179  * cui_abort - Signal the main cui program loop to exit.
180  *
181  * Sets cui.abort, which causes the cui_run() routine to return.
182  */
183
184 void cui_abort(struct cui *cui)
185 {
186         pb_log_fn("exiting\n");
187         cui->abort = 1;
188 }
189
190 /**
191  * cui_resize - Signal the main cui program loop to resize
192  *
193  * Called at SIGWINCH.
194  */
195
196 void cui_resize(struct cui *cui)
197 {
198         pb_debug("%s: resizing\n", __func__);
199         cui->resize = 1;
200 }
201
202 /**
203  * cui_on_exit - A generic main menu exit callback.
204  */
205
206 void cui_on_exit(struct pmenu *menu)
207 {
208         struct cui *cui = cui_from_pmenu(menu);
209
210         cui_cancel_autoboot_on_exit(cui);
211
212         const char *argv[] = {
213                 pb_system_apps.sh,
214                 NULL
215         };
216
217         cui_run_cmd(cui, argv);
218
219         nc_scr_status_printf(cui->current, _("Returned from shell"));
220 }
221
222 /**
223  * cui_abort_on_exit - Force an exit of the main loop on menu exit.
224  *                     This is mainly for lockdown situations where
225  *                     the exit then triggers an expected reboot.
226  */
227 void cui_abort_on_exit(struct pmenu *menu)
228 {
229         struct cui *cui = cui_from_pmenu(menu);
230         cui->abort = 1;
231 }
232
233 /**
234  * cui_run_cmd - A generic cb to run the supplied command.
235  */
236
237 int cui_run_cmd(struct cui *cui, const char **cmd_argv)
238 {
239         struct process *process;
240         int result;
241
242         process = process_create(cui);
243         if (!process)
244                 return -1;
245
246         process->path = cmd_argv[0];
247         process->argv = cmd_argv;
248         process->raw_stdout = true;
249
250         nc_scr_status_printf(cui->current, _("Running %s..."), cmd_argv[0]);
251
252         def_prog_mode();
253         clear();
254         refresh();
255         endwin();
256
257         result = process_run_sync(process);
258
259         reset_prog_mode();
260         refresh();
261
262         redrawwin(cui->current->main_ncw);
263         nc_scr_post(cui->current);
264
265         if (result) {
266                 pb_log_fn("failed: '%s'\n", cmd_argv[0]);
267                 nc_scr_status_printf(cui->current, _("Failed: %s"),
268                                 cmd_argv[0]);
269         }
270
271         process_release(process);
272
273         return result;
274 }
275
276 int cui_run_cmd_from_item(struct pmenu_item *item)
277 {
278         struct cui *cui = cui_from_item(item);
279         const char **cmd_argv = item->data;
280
281         return cui_run_cmd(cui, cmd_argv);
282 }
283
284 /**
285  * cui_boot - A generic cb to run kexec.
286  */
287
288 static int cui_boot(struct pmenu_item *item)
289 {
290         int result;
291         struct cui *cui = cui_from_item(item);
292         struct cui_opt_data *cod = cod_from_item(item);
293
294         assert(cui->current == &cui->main->scr);
295
296         pb_debug("%s: %s\n", __func__, cod->name);
297
298         nc_scr_status_printf(cui->current, _("Booting %s..."), cod->name);
299
300         result = discover_client_boot(cui->client, NULL, cod->opt, cod->bd);
301
302         if (result) {
303                 nc_scr_status_printf(cui->current,
304                                 _("Failed: boot %s"), cod->bd->image);
305         }
306
307         return 0;
308 }
309
310 static int menu_sysinfo_execute(struct pmenu_item *item)
311 {
312         cui_show_sysinfo(cui_from_item(item));
313         return 0;
314 }
315
316 static int menu_config_execute(struct pmenu_item *item)
317 {
318         cui_show_config(cui_from_item(item));
319         return 0;
320 }
321
322 static int menu_lang_execute(struct pmenu_item *item)
323 {
324         cui_show_lang(cui_from_item(item));
325         return 0;
326 }
327
328 static int menu_statuslog_execute(struct pmenu_item *item)
329 {
330         cui_show_statuslog(cui_from_item(item));
331         return 0;
332 }
333
334 static int menu_reinit_execute(struct pmenu_item *item)
335 {
336         if (cui_from_item(item)->client)
337                 cui_send_reinit(cui_from_item(item));
338         return 0;
339 }
340
341 static int menu_add_url_execute(struct pmenu_item *item)
342 {
343         if (cui_from_item(item)->client)
344                 cui_show_add_url(cui_from_item(item));
345         return 0;
346 }
347
348 static int menu_plugin_execute(struct pmenu_item *item)
349 {
350         if (cui_from_item(item)->client)
351                 cui_show_plugin_menu(cui_from_item(item));
352         return 0;
353 }
354
355 static void cui_boot_editor_on_exit(struct cui *cui,
356                 struct pmenu_item *item,
357                 struct pb_boot_data *bd)
358 {
359         struct pmenu *menu = cui->main;
360         struct cui_opt_data *cod;
361         int idx, top, rows, cols;
362         static int user_idx = 0;
363
364         /* Was the edit cancelled? */
365         if (!bd) {
366                 cui_set_current(cui, &cui->main->scr);
367                 talloc_free(cui->boot_editor);
368                 cui->boot_editor = NULL;
369                 return;
370         }
371
372         /* Is this was a new item, we'll need to update the menu */
373         if (!item) {
374                 int insert_pt;
375
376                 cod = talloc_zero(NULL, struct cui_opt_data);
377                 cod->name = talloc_asprintf(cod, _("User item %u"), ++user_idx);
378
379                 item = pmenu_item_create(menu, cod->name);
380                 if (!item) {
381                         talloc_free(cod);
382                         goto out;
383                 }
384
385                 item->on_edit = cui_item_edit;
386                 item->on_execute = cui_boot;
387                 item->data = cod;
388
389                 talloc_steal(item, cod);
390
391                 /* Detach the items array. */
392                 set_menu_items(menu->ncm, NULL);
393
394                 /* Insert new item at insert_pt. */
395                 insert_pt = pmenu_grow(menu, 1);
396                 pmenu_item_insert(menu, item, insert_pt);
397
398                 /* Re-attach the items array. */
399                 set_menu_items(menu->ncm, menu->items);
400
401                 /* If our index is above the current top row, align
402                  * us to the new top. Otherwise, align us to the new
403                  * bottom */
404                 menu_format(cui->main->ncm, &rows, &cols);
405                 top = top_row(cui->main->ncm);
406                 idx = item_index(item->nci);
407
408                 if (top >= idx)
409                         top = idx;
410                 else
411                         top = idx < rows ? 0 : idx - rows + 1;
412
413                 set_top_row(cui->main->ncm, top);
414                 set_current_item(item->pmenu->ncm, item->nci);
415
416                 nc_scr_post(&menu->scr);
417         } else {
418                 cod = item->data;
419         }
420
421         cod->bd = talloc_steal(cod, bd);
422
423 out:
424         cui_set_current(cui, &cui->main->scr);
425         talloc_free(cui->boot_editor);
426         cui->boot_editor = NULL;
427 }
428
429 void cui_item_edit(struct pmenu_item *item)
430 {
431         struct cui *cui = cui_from_item(item);
432         cui->boot_editor = boot_editor_init(cui, item, cui->sysinfo,
433                                         cui_boot_editor_on_exit);
434         cui_set_current(cui, boot_editor_scr(cui->boot_editor));
435 }
436
437 void cui_item_new(struct pmenu *menu)
438 {
439         struct cui *cui = cui_from_pmenu(menu);
440         cui->boot_editor = boot_editor_init(cui, NULL, cui->sysinfo,
441                                         cui_boot_editor_on_exit);
442         cui_set_current(cui, boot_editor_scr(cui->boot_editor));
443 }
444
445 static void cui_sysinfo_exit(struct cui *cui)
446 {
447         cui_set_current(cui, &cui->main->scr);
448         talloc_free(cui->sysinfo_screen);
449         cui->sysinfo_screen = NULL;
450 }
451
452 void cui_show_sysinfo(struct cui *cui)
453 {
454         cui->sysinfo_screen = sysinfo_screen_init(cui, cui->sysinfo,
455                         cui_sysinfo_exit);
456         cui_set_current(cui, sysinfo_screen_scr(cui->sysinfo_screen));
457 }
458
459 static void cui_config_exit(struct cui *cui)
460 {
461         cui_set_current(cui, &cui->main->scr);
462         talloc_free(cui->config_screen);
463         cui->config_screen = NULL;
464 }
465
466 void cui_show_config(struct cui *cui)
467 {
468         cui->config_screen = config_screen_init(cui, cui->config,
469                         cui->sysinfo, cui_config_exit);
470         cui_set_current(cui, config_screen_scr(cui->config_screen));
471 }
472
473 static void cui_lang_exit(struct cui *cui)
474 {
475         cui_set_current(cui, &cui->main->scr);
476         talloc_free(cui->lang_screen);
477         cui->lang_screen = NULL;
478 }
479
480 void cui_show_lang(struct cui *cui)
481 {
482         cui->lang_screen = lang_screen_init(cui, cui->config, cui_lang_exit);
483         cui_set_current(cui, lang_screen_scr(cui->lang_screen));
484 }
485
486 static void cui_statuslog_exit(struct cui *cui)
487 {
488         cui_set_current(cui, &cui->main->scr);
489         talloc_free(cui->statuslog_screen);
490         cui->statuslog_screen = NULL;
491 }
492
493 void cui_show_statuslog(struct cui *cui)
494 {
495         cui->statuslog_screen = statuslog_screen_init(cui, cui_statuslog_exit);
496         cui_set_current(cui, statuslog_screen_scr(cui->statuslog_screen));
497 }
498
499 static void cui_add_url_exit(struct cui *cui)
500 {
501         cui_set_current(cui, &cui->main->scr);
502         talloc_free(cui->add_url_screen);
503         cui->add_url_screen = NULL;
504 }
505
506 static void cui_plugin_exit(struct cui *cui)
507 {
508         cui_set_current(cui, &cui->plugin_menu->scr);
509         talloc_free(cui->plugin_screen);
510         cui->plugin_screen = NULL;
511 }
512
513 static void cui_plugin_menu_exit(struct pmenu *menu)
514 {
515         struct cui *cui = cui_from_pmenu(menu);
516         cui_set_current(cui, &cui->main->scr);
517 }
518
519 void cui_show_add_url(struct cui *cui)
520 {
521         cui->add_url_screen = add_url_screen_init(cui, cui_add_url_exit);
522         cui_set_current(cui, add_url_screen_scr(cui->add_url_screen));
523 }
524
525 void cui_show_plugin_menu(struct cui *cui)
526 {
527         cui_set_current(cui, &cui->plugin_menu->scr);
528 }
529
530 void cui_show_plugin(struct pmenu_item *item)
531 {
532         struct cui *cui = cui_from_item(item);
533         cui->plugin_screen = plugin_screen_init(cui, item, cui_plugin_exit);
534         cui_set_current(cui, plugin_screen_scr(cui->plugin_screen));
535 }
536
537 static void cui_help_exit(struct cui *cui)
538 {
539         cui_set_current(cui, help_screen_return_scr(cui->help_screen));
540         talloc_free(cui->help_screen);
541         cui->help_screen = NULL;
542 }
543
544 void cui_show_help(struct cui *cui, const char *title,
545                 const struct help_text *text)
546 {
547         if (!cui->current)
548                 return;
549
550         if (cui->help_screen)
551                 return;
552
553         cui->help_screen = help_screen_init(cui, cui->current,
554                         title, text, cui_help_exit);
555
556         if (cui->help_screen)
557                 cui_set_current(cui, help_screen_scr(cui->help_screen));
558 }
559
560 static void cui_subset_exit(struct cui *cui)
561 {
562         cui_set_current(cui, subset_screen_return_scr(cui->subset_screen));
563         talloc_free(cui->subset_screen);
564         cui->subset_screen = NULL;
565 }
566
567 void cui_show_subset(struct cui *cui, const char *title,
568                      void *arg)
569 {
570         if (!cui->current)
571                 return;
572
573         if (cui->subset_screen)
574                 return;
575
576         cui->subset_screen = subset_screen_init(cui, cui->current,
577                         title, arg, cui_subset_exit);
578
579         if (cui->subset_screen)
580                 cui_set_current(cui, subset_screen_scr(cui->subset_screen));
581 }
582
583 /**
584  * cui_set_current - Set the currently active screen and redraw it.
585  */
586
587 struct nc_scr *cui_set_current(struct cui *cui, struct nc_scr *scr)
588 {
589         struct nc_scr *old;
590
591         DBGS("%p -> %p\n", cui->current, scr);
592
593         assert(cui->current != scr);
594
595         old = cui->current;
596         nc_scr_unpost(old);
597
598         cui->current = scr;
599
600         nc_scr_post(cui->current);
601
602         return old;
603 }
604
605 static bool set_temp_autoboot_opt(struct cui *cui, struct autoboot_option *opt)
606 {
607         cui->autoboot_opt = opt;
608         if (cui->client)
609                 discover_client_send_temp_autoboot(cui->client, opt);
610
611         return true;
612 }
613
614 static bool key_cancels_boot(int key)
615 {
616         unsigned int i;
617
618         if (key == 0xc)
619                 return false;
620
621         for (i = 0; i < ARRAY_SIZE(autoboot_override_keys); i++)
622                 if (key == autoboot_override_keys[i].key)
623                         return false;
624
625         return true;
626 }
627
628 static bool process_global_keys(struct cui *cui, int key)
629 {
630         unsigned int i;
631
632         switch (key) {
633         case 0xc:
634                 if (cui->current && cui->current->main_ncw)
635                         wrefresh(curscr);
636                 return true;
637         }
638
639         /* check for autoboot override keys */
640         for (i = 0; i < ARRAY_SIZE(autoboot_override_keys); i++) {
641                 if (key != autoboot_override_keys[i].key)
642                         continue;
643
644                 pb_log("Sending temporary autoboot override\n");
645                 set_temp_autoboot_opt(cui, &autoboot_override_keys[i].opt);
646                 return true;
647         }
648
649         return false;
650 }
651
652 /**
653  * cui_process_key - Process input on stdin.
654  */
655
656 static int cui_process_key(void *arg)
657 {
658         struct cui *cui = cui_from_arg(arg);
659         unsigned int i;
660         char *sequence;
661         int grab;
662
663         assert(cui->current);
664
665         for (;;) {
666                 int c = getch();
667
668                 pb_debug("%s: got key %d\n", __func__, c);
669
670                 if (c == ERR)
671                         break;
672
673                 if (c == 27) {
674                         /*
675                          * If this is a console code sequence try to parse it
676                          * and don't treat this as a key press.
677                          */
678                         grab = getch();
679                         if (grab != ERR && grab != 27) {
680                                 ungetch(grab);
681                                 pb_debug("%s: Caught unhandled command sequence\n",
682                                                 __func__);
683                                 sequence = handle_control_sequence(cui, c);
684                                 pb_debug("Caught sequence ");
685                                 if (sequence) {
686                                         pb_debug("(%zu): ", strlen(sequence));
687                                         for (i = 0; i < strlen(sequence); i++)
688                                                 pb_debug("0%o ", sequence[i]);
689                                         pb_debug("\n");
690                                 } else
691                                         pb_debug("(0): (none)\n");
692                                 continue;
693                         }
694                 }
695
696                 if (cui->preboot_mode) {
697                         /* Turn curses options back on if the user interacts */
698                         cui->preboot_mode = false;
699                         cui_set_curses_options(true);
700                 }
701
702                 if (!cui->has_input && key_cancels_boot(c)) {
703                         cui->has_input = true;
704                         if (cui->client) {
705                                 pb_log("UI input received (key = %d), aborting "
706                                         "default boot\n", c);
707                                 discover_client_cancel_default(cui->client);
708                         } else {
709                                 pb_log("UI input received (key = %d), aborting "
710                                         "once server connects\n", c);
711                         }
712                 }
713
714                 if (process_global_keys(cui, c))
715                         continue;
716
717                 cui->current->process_key(cui->current, c);
718         }
719
720         return 0;
721 }
722
723 /**
724  * cui_process_js - Process joystick events.
725  */
726
727 static int cui_process_js(void *arg)
728 {
729         struct cui *cui = cui_from_arg(arg);
730         int c;
731
732         c = pjs_process_event(cui->pjs);
733
734         if (c) {
735                 ungetch(c);
736                 cui_process_key(arg);
737         }
738
739         return 0;
740 }
741
742 /**
743  * cui_handle_resize - Handle the term resize.
744  */
745
746 static void cui_handle_resize(struct cui *cui)
747 {
748         struct winsize ws;
749
750         if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
751                 pb_log_fn("ioctl failed: %s\n", strerror(errno));
752                 return;
753         }
754
755         pb_debug("%s: {%u,%u}\n", __func__, ws.ws_row, ws.ws_col);
756
757         wclear(cui->current->main_ncw);
758         resize_term(ws.ws_row, ws.ws_col);
759         cui->current->resize(cui->current);
760
761         /* For some reason this makes ncurses redraw the screen */
762         getch();
763         redrawwin(cui->current->main_ncw);
764         wrefresh(cui->current->main_ncw);
765 }
766
767 /**
768  * cui_device_add - Client device_add callback.
769  *
770  * Creates menu_items for all the device boot_options and inserts those
771  * menu_items into the main menu.  Redraws the main menu if it is active.
772  * If a 'plugin' type boot_option appears the plugin menu is updated instead.
773  */
774
775 static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
776                 void *arg)
777 {
778         struct pmenu_item *i, *dev_hdr = NULL;
779         struct cui *cui = cui_from_arg(arg);
780         struct cui_opt_data *cod;
781         const char *tab = "  ";
782         unsigned int insert_pt;
783         int result, rows, cols;
784         struct pmenu *menu;
785         bool plugin_option;
786         ITEM *selected;
787         char *name;
788
789         plugin_option = opt->type == DISCOVER_PLUGIN_OPTION;
790         menu = plugin_option ? cui->plugin_menu : cui->main;
791
792         pb_debug("%s: %p %s\n", __func__, opt, opt->id);
793
794         selected = current_item(menu->ncm);
795         menu_format(menu->ncm, &rows, &cols);
796
797         if (cui->current == &cui->main->scr)
798                 nc_scr_unpost(cui->current);
799         if (plugin_option && cui->current == &cui->plugin_menu->scr)
800                 nc_scr_unpost(cui->current);
801
802         /* Check if the boot device is new */
803         dev_hdr = pmenu_find_device(menu, dev, opt);
804
805         /* All actual boot entries are 'tabbed' across */
806         name = talloc_asprintf(menu, "%s%s",
807                         tab, opt->name ? : "Unknown Name");
808
809         /* Save the item in opt->ui_info for cui_device_remove() */
810         opt->ui_info = i = pmenu_item_create(menu, name);
811         talloc_free(name);
812         if (!i)
813                 return -1;
814
815         if (plugin_option) {
816                 i->on_edit = NULL;
817                 i->on_execute = plugin_install_plugin;
818         } else {
819                 i->on_edit = cui_item_edit;
820                 i->on_execute = cui_boot;
821         }
822
823         i->data = cod = talloc(i, struct cui_opt_data);
824         cod->opt = opt;
825         cod->dev = dev;
826         cod->opt_hash = pb_opt_hash(dev, opt);
827         cod->name = opt->name;
828
829         if (plugin_option) {
830                 cod->pd = talloc(i, struct pb_plugin_data);
831                 cod->pd->plugin_file = talloc_strdup(cod,
832                                 opt->boot_image_file);
833         } else {
834                 cod->bd = talloc(i, struct pb_boot_data);
835                 cod->bd->image = talloc_strdup(cod->bd, opt->boot_image_file);
836                 cod->bd->initrd = talloc_strdup(cod->bd, opt->initrd_file);
837                 cod->bd->dtb = talloc_strdup(cod->bd, opt->dtb_file);
838                 cod->bd->args = talloc_strdup(cod->bd, opt->boot_args);
839                 cod->bd->args_sig_file = talloc_strdup(cod->bd, opt->args_sig_file);
840         }
841
842         /* This disconnects items array from menu. */
843         result = set_menu_items(menu->ncm, NULL);
844
845         if (result)
846                 pb_log_fn("set_menu_items failed: %d\n", result);
847
848         /* Insert new items at insert_pt. */
849         if (dev_hdr) {
850                 insert_pt = pmenu_grow(menu, 2);
851                 pmenu_item_insert(menu, dev_hdr, insert_pt);
852                 pb_log("%s: adding new device hierarchy %s\n",
853                         __func__, opt->device_id);
854                 pmenu_item_insert(menu, i, insert_pt+1);
855         } else {
856                 insert_pt = pmenu_grow(menu, 1);
857                 pmenu_item_add(menu, i, insert_pt);
858         }
859
860         if (plugin_option) {
861                 pb_log_fn("adding plugin '%s'\n", cod->name);
862                 pb_log("   file  '%s'\n", cod->pd->plugin_file);
863         } else {
864                 pb_log_fn("adding opt '%s'\n", cod->name);
865                 pb_log("   image  '%s'\n", cod->bd->image);
866                 pb_log("   initrd '%s'\n", cod->bd->initrd);
867                 pb_log("   args   '%s'\n", cod->bd->args);
868                 pb_log("   argsig '%s'\n", cod->bd->args_sig_file);
869         }
870
871         /* Update the plugin menu label if needed */
872         if (plugin_option) {
873                 struct pmenu_item *item;
874                 unsigned int j;
875                 result = set_menu_items(cui->main->ncm, NULL);
876                 for (j = 0 ; j < cui->main->item_count; j++) {
877                         item = item_userptr(cui->main->items[j]);
878                         if (item->on_execute != menu_plugin_execute)
879                                 continue;
880                         cui->n_plugins++;
881                         char *label = talloc_asprintf(item, _("Plugins (%u)"),
882                                         cui->n_plugins);
883                         pmenu_item_update(item, label);
884                         talloc_free(label);
885                         break;
886                 }
887                 result = set_menu_items(cui->main->ncm, cui->main->items);
888                 if (result)
889                         pb_log_fn("set_menu_items failed: %d\n", result);
890         }
891
892         /* Re-attach the items array. */
893         result = set_menu_items(menu->ncm, menu->items);
894
895         if (result)
896                 pb_log_fn("set_menu_items failed: %d\n", result);
897
898         if (0) {
899                 pb_log("%s\n", __func__);
900                 pmenu_dump_items(menu->items,
901                         item_count(menu->ncm) + 1);
902         }
903
904         if (!item_visible(selected)) {
905                 int idx, top;
906
907                 top = top_row(menu->ncm);
908                 idx = item_index(selected);
909
910                 /* If our index is above the current top row, align
911                  * us to the new top. Otherwise, align us to the new
912                  * bottom */
913                 top = top < idx ? idx - rows + 1 : idx;
914
915                 set_top_row(menu->ncm, top);
916                 set_current_item(menu->ncm, selected);
917         }
918
919         if (cui->current == &menu->scr)
920                 nc_scr_post(cui->current);
921         if (plugin_option && cui->current == &cui->main->scr)
922                 nc_scr_post(cui->current);
923
924         return 0;
925 }
926
927 /**
928  * cui_device_remove - Client device remove callback.
929  *
930  * Removes all the menu_items for the device from the main menu and redraws the
931  * main menu if it is active.
932  */
933 static void cui_device_remove(struct device *dev, void *arg)
934 {
935         struct cui *cui = cui_from_arg(arg);
936         struct boot_option *opt;
937         unsigned int i;
938         int rows, cols, top, last;
939         int result;
940
941         pb_log_fn("%p %s\n", dev, dev->id);
942
943         if (cui->current == &cui->main->scr)
944                 nc_scr_unpost(cui->current);
945         if (cui->current == &cui->plugin_menu->scr)
946                 nc_scr_unpost(cui->current);
947
948         /* This disconnects items array from menu. */
949
950         result = set_menu_items(cui->main->ncm, NULL);
951         result |= set_menu_items(cui->plugin_menu->ncm, NULL);
952
953         if (result)
954                 pb_log_fn("set_menu_items failed: %d\n", result);
955
956         list_for_each_entry(&dev->boot_options, opt, list) {
957                 struct pmenu_item *item = pmenu_item_from_arg(opt->ui_info);
958
959                 assert(pb_protocol_device_cmp(dev, cod_from_item(item)->dev));
960                 if (opt->type == DISCOVER_PLUGIN_OPTION)
961                         pmenu_remove(cui->plugin_menu, item);
962                 else
963                         pmenu_remove(cui->main, item);
964         }
965
966         /* Manually remove remaining device hierarchy item */
967         for (i=0; i < cui->main->item_count; i++) {
968                 struct pmenu_item *item = item_userptr(cui->main->items[i]);
969                 if (!item || !item->data )
970                         continue;
971
972                 struct cui_opt_data *data = item->data;
973                 if (data && data->dev && data->dev == dev)
974                         pmenu_remove(cui->main,item);
975         }
976         /* Look in plugins menu too */
977         for (i=0; i < cui->plugin_menu->item_count; i++) {
978                 struct pmenu_item *item = item_userptr(cui->plugin_menu->items[i]);
979                 if (!item || !item->data )
980                         continue;
981
982                 struct cui_opt_data *data = item->data;
983                 if (data && data->dev && data->dev == dev)
984                         pmenu_remove(cui->plugin_menu,item);
985         }
986
987         /* Re-attach the items array. */
988
989         result = set_menu_items(cui->main->ncm, cui->main->items);
990         result |= set_menu_items(cui->plugin_menu->ncm, cui->plugin_menu->items);
991
992         /* Move cursor to 'Exit' menu entry for the main menu.. */
993         menu_format(cui->main->ncm, &rows, &cols);
994         last = cui->main->item_count - 1;
995         set_current_item(cui->main->ncm, cui->main->items[last]);
996         if (!item_visible(cui->main->items[last])) {
997                 top = last < rows ? 0 : last - rows + 1;
998                 set_top_row(cui->main->ncm, top);
999         }
1000
1001         /* ..and the plugin menu */
1002         menu_format(cui->plugin_menu->ncm, &rows, &cols);
1003         last = cui->plugin_menu->item_count - 1;
1004         set_current_item(cui->plugin_menu->ncm, cui->plugin_menu->items[last]);
1005         if (!item_visible(cui->plugin_menu->items[last])) {
1006                 top = last < rows ? 0 : last - rows + 1;
1007                 set_top_row(cui->plugin_menu->ncm, top);
1008         }
1009
1010         if (result)
1011                 pb_log_fn("set_menu_items failed: %d\n", result);
1012
1013         if (0) {
1014                 pb_log("%s\n", __func__);
1015                 pmenu_dump_items(cui->main->items,
1016                         item_count(cui->main->ncm) + 1);
1017         }
1018
1019         if (cui->current == &cui->main->scr)
1020                 nc_scr_post(cui->current);
1021         if (cui->current == &cui->plugin_menu->scr)
1022                 nc_scr_post(cui->current);
1023 }
1024
1025 static void cui_update_status(struct status *status, void *arg)
1026 {
1027         struct cui *cui = cui_from_arg(arg);
1028
1029         statuslog_append_steal(cui, cui->statuslog, status);
1030
1031         /* Ignore status messages from the backlog */
1032         if (status->backlog)
1033                 return;
1034
1035         nc_scr_status_printf(cui->current, "%s", status->message);
1036
1037         if (cui->preboot_mode &&
1038                 (!status->boot_active || status->type == STATUS_ERROR)) {
1039                 cui_set_curses_options(true);
1040                 cui->preboot_mode = false;
1041         } else {
1042                 cui->preboot_mode = status->boot_active &&
1043                                                 status->type == STATUS_INFO;
1044                 if (cui->preboot_mode)
1045                         cui_set_curses_options(false);
1046         }
1047 }
1048
1049 /*
1050  * Handle a new installed plugin option and update its associated
1051  * (uninstalled) menu item if it exists.
1052  */
1053 static int cui_plugin_option_add(struct plugin_option *opt, void *arg)
1054 {
1055         struct cui_opt_data *cod;
1056         struct cui *cui = cui_from_arg(arg);
1057         struct pmenu_item *item = NULL;
1058         struct boot_option *dummy_opt;
1059         struct device *dev;
1060         unsigned int i;
1061         int result;
1062
1063 fallback:
1064         /* Find uninstalled plugin by matching on plugin_file */
1065         for (i = 0; i < cui->plugin_menu->item_count; i++) {
1066                 item = item_userptr(cui->plugin_menu->items[i]);
1067                 if (!item)
1068                         continue;
1069                 cod = cod_from_item(item);
1070                 if (!cod || !cod->pd)
1071                         continue;
1072                 if (strncmp(cod->pd->plugin_file, opt->plugin_file,
1073                                         strlen(cod->pd->plugin_file)) == 0)
1074                         break;
1075         }
1076
1077         /*
1078          * If pb-plugin was run manually there may not be an associated
1079          * plugin-type boot_option. Pass a fake device and option to
1080          * cui_boot_option_add() so we have an item to work with.
1081          */
1082         if (!item || i >= cui->plugin_menu->item_count) {
1083                 pb_log("New plugin option %s doesn't have a source item\n",
1084                                 opt->id);
1085                 dev = talloc_zero(cui, struct device);
1086                 dev->id = dev->name = talloc_asprintf(dev, "(unknown)");
1087                 dev->type = DEVICE_TYPE_UNKNOWN;
1088                 dummy_opt = talloc_zero(cui, struct boot_option);
1089                 dummy_opt->device_id = talloc_strdup(dummy_opt, dev->id);
1090                 dummy_opt->id = dummy_opt->name = talloc_asprintf(dummy_opt, "dummy");
1091                 dummy_opt->boot_image_file = talloc_strdup(dummy_opt, opt->plugin_file);
1092                 dummy_opt->type = DISCOVER_PLUGIN_OPTION;
1093                 cui_boot_option_add(dev, dummy_opt, cui);
1094                 goto fallback;
1095         }
1096
1097         /*
1098          * If this option was faked above move the context under
1099          * the item so it is cleaned up later in cui_plugins_remove().
1100          */
1101         if (strcmp(cod->opt->id, "dummy") == 0 &&
1102                         cod->dev->type == DEVICE_TYPE_UNKNOWN) {
1103                 talloc_steal(item, cod->dev);
1104                 talloc_steal(item, cod->opt);
1105         }
1106
1107         talloc_free(cod->name);
1108         /* Name is still tabbed across */
1109         cod->name = talloc_asprintf(cod, _("  %s [installed]"), opt->name);
1110
1111         cod->pd->opt = opt;
1112         item->on_execute = NULL;
1113         item->on_edit = cui_show_plugin;
1114
1115         if (cui->current == &cui->plugin_menu->scr)
1116                 nc_scr_unpost(cui->current);
1117
1118         /* This disconnects items array from menu. */
1119         result = set_menu_items(cui->plugin_menu->ncm, NULL);
1120
1121         if (result == E_OK)
1122                 pmenu_item_update(item, cod->name);
1123
1124         /* Re-attach the items array. */
1125         result = set_menu_items(cui->plugin_menu->ncm, cui->plugin_menu->items);
1126
1127         if (cui->current == &cui->plugin_menu->scr)
1128                 nc_scr_post(cui->current);
1129
1130         return result;
1131 }
1132
1133 /*
1134  * Most plugin menu items will be removed via cui_device_remove(). However if
1135  * pb-plugin has been run manually it is possible that there a plugin items
1136  * not associated with a device - remove them here.
1137  */
1138 static int cui_plugins_remove(void *arg)
1139 {
1140         struct cui *cui = cui_from_arg(arg);
1141         struct pmenu_item *item = NULL;
1142         struct cui_opt_data *cod;
1143         unsigned int i = 0;
1144
1145         pb_debug("%s\n", __func__);
1146
1147         if (cui->current == &cui->plugin_menu->scr)
1148                 nc_scr_unpost(cui->current);
1149         if (cui->current == &cui->main->scr)
1150                 nc_scr_unpost(cui->current);
1151
1152         /* This disconnects items array from menu. */
1153         set_menu_items(cui->plugin_menu->ncm, NULL);
1154
1155         while (i < cui->plugin_menu->item_count) {
1156                 item = item_userptr(cui->plugin_menu->items[i]);
1157                 if (!item || !item->data) {
1158                         i++;
1159                         continue;
1160                 }
1161                 cod = cod_from_item(item);
1162                 if (!cod->opt && !cod->dev) {
1163                         i++;
1164                         continue;
1165                 }
1166
1167                 pmenu_remove(cui->plugin_menu, item);
1168                 /* plugin_menu entries will shift, jump to bottom to make sure
1169                  * we remove all plugin option items */
1170                 i = 0;
1171         }
1172
1173         /* Re-attach the items array. */
1174         set_menu_items(cui->plugin_menu->ncm, cui->plugin_menu->items);
1175
1176         set_menu_items(cui->main->ncm, NULL);
1177         for (i = 0; i < cui->main->item_count; i++) {
1178                 item = item_userptr(cui->main->items[i]);
1179                 if (item->on_execute != menu_plugin_execute)
1180                         continue;
1181                 cui->n_plugins = 0;
1182                 pmenu_item_update(item, _("Plugins (0)"));
1183                 break;
1184         }
1185
1186         set_menu_items(cui->main->ncm, cui->main->items);
1187
1188         if (cui->current == &cui->main->scr)
1189                 nc_scr_post(cui->current);
1190
1191         /* If we're currently in a plugin screen jump back to the plugin menu */
1192         if (cui->plugin_screen &&
1193                         cui->current == plugin_screen_scr(cui->plugin_screen))
1194                 cui_plugin_exit(cui);
1195
1196         return 0;
1197 }
1198
1199 static void cui_update_mm_title(struct cui *cui)
1200 {
1201         struct nc_frame *frame = &cui->main->scr.frame;
1202
1203         talloc_free(frame->rtitle);
1204
1205         frame->rtitle = talloc_strdup(cui->main, cui->sysinfo->type);
1206         if (cui->sysinfo->identifier)
1207                 frame->rtitle = talloc_asprintf_append(frame->rtitle,
1208                                 " %s", cui->sysinfo->identifier);
1209
1210         if (cui->current == &cui->main->scr)
1211                 nc_scr_post(cui->current);
1212
1213         frame = &cui->plugin_menu->scr.frame;
1214
1215         talloc_free(frame->rtitle);
1216
1217         frame->rtitle = talloc_strdup(cui->main, cui->sysinfo->type);
1218         if (cui->sysinfo->identifier)
1219                 frame->rtitle = talloc_asprintf_append(frame->rtitle,
1220                                 " %s", cui->sysinfo->identifier);
1221
1222         if (cui->current == &cui->main->scr)
1223                 nc_scr_post(cui->current);
1224 }
1225
1226 static void cui_update_sysinfo(struct system_info *sysinfo, void *arg)
1227 {
1228         struct cui *cui = cui_from_arg(arg);
1229         cui->sysinfo = talloc_steal(cui, sysinfo);
1230
1231         /* if we're currently displaying the system info screen, inform it
1232          * of the updated information. */
1233         if (cui->sysinfo_screen)
1234                 sysinfo_screen_update(cui->sysinfo_screen, sysinfo);
1235
1236         if (cui->subset_screen)
1237                 subset_screen_update(cui->subset_screen);
1238
1239         /* ... and do the same with the config screen... */
1240         if (cui->config_screen)
1241                 config_screen_update(cui->config_screen, cui->config, sysinfo);
1242
1243         /* ... and the boot editor. */
1244         if (cui->boot_editor)
1245                 boot_editor_update(cui->boot_editor, sysinfo);
1246
1247         cui_update_mm_title(cui);
1248 }
1249
1250 static void cui_update_language(struct cui *cui, char *lang)
1251 {
1252         bool repost_menu;
1253         char *cur_lang;
1254
1255         cur_lang = setlocale(LC_ALL, NULL);
1256         if (cur_lang && !strcmp(cur_lang, lang))
1257                 return;
1258
1259         setlocale(LC_ALL, lang);
1260
1261         /* we'll need to update the menu: drop all items and repopulate */
1262         repost_menu = cui->current == &cui->main->scr ||
1263                 cui->current == &cui->plugin_menu->scr;
1264         if (repost_menu)
1265                 nc_scr_unpost(cui->current);
1266
1267         talloc_free(cui->main);
1268         cui->main = main_menu_init(cui);
1269         cui->plugin_menu = plugin_menu_init(cui);
1270
1271         if (repost_menu) {
1272                 cui->current = &cui->main->scr;
1273                 nc_scr_post(cui->current);
1274         }
1275
1276         discover_client_enumerate(cui->client);
1277 }
1278
1279 static void cui_update_config(struct config *config, void *arg)
1280 {
1281         struct cui *cui = cui_from_arg(arg);
1282         cui->config = talloc_steal(cui, config);
1283
1284         if (config->lang)
1285                 cui_update_language(cui, config->lang);
1286
1287         if (cui->subset_screen)
1288                 subset_screen_update(cui->subset_screen);
1289
1290         if (cui->config_screen)
1291                 config_screen_update(cui->config_screen, config, cui->sysinfo);
1292
1293         if (config->safe_mode)
1294                 nc_scr_status_printf(cui->current,
1295                                 _("SAFE MODE: select '%s' to continue"),
1296                                 _("Rescan devices"));
1297 }
1298
1299 int cui_send_config(struct cui *cui, struct config *config)
1300 {
1301         return discover_client_send_config(cui->client, config);
1302 }
1303
1304 int cui_send_url(struct cui *cui, char * url)
1305 {
1306         return discover_client_send_url(cui->client, url);
1307 }
1308
1309 int cui_send_plugin_install(struct cui *cui, char *file)
1310 {
1311         return discover_client_send_plugin_install(cui->client, file);
1312 }
1313
1314 void cui_send_reinit(struct cui *cui)
1315 {
1316         discover_client_send_reinit(cui->client);
1317 }
1318
1319 /**
1320  * pb_mm_init - Setup the main menu instance.
1321  */
1322 static struct pmenu *main_menu_init(struct cui *cui)
1323 {
1324         struct pmenu_item *i;
1325         struct pmenu *m;
1326         int result;
1327         bool lockdown = lockdown_active();
1328
1329         m = pmenu_init(cui, 9, lockdown ? cui_abort_on_exit : cui_on_exit);
1330         if (!m) {
1331                 pb_log_fn("failed\n");
1332                 return NULL;
1333         }
1334
1335         m->n_hot_keys = 1;
1336         m->hot_keys = talloc_array(m, hot_key_fn, m->n_hot_keys);
1337         if (!m->hot_keys) {
1338                 pb_log_fn("failed to allocate hot_keys\n");
1339                 talloc_free(m);
1340                 return NULL;
1341         }
1342         m->hot_keys[0] = pmenu_main_hot_keys;
1343         m->on_new = cui_item_new;
1344
1345         m->scr.frame.ltitle = talloc_asprintf(m,
1346                 "Petitboot (" PACKAGE_VERSION ")");
1347         m->scr.frame.rtitle = NULL;
1348         m->scr.frame.help = talloc_strdup(m,
1349                 _("Enter=accept, e=edit, n=new, x=exit, l=language, g=log, h=help"));
1350         m->scr.frame.status = talloc_strdup(m, _("Welcome to Petitboot"));
1351
1352         /* add a separator */
1353         i = pmenu_item_create(m, " ");
1354         item_opts_off(i->nci, O_SELECTABLE);
1355         pmenu_item_insert(m, i, 0);
1356
1357         /* add system items */
1358         i = pmenu_item_create(m, _("System information"));
1359         i->on_execute = menu_sysinfo_execute;
1360         pmenu_item_insert(m, i, 1);
1361
1362         i = pmenu_item_create(m, _("System configuration"));
1363         i->on_execute = menu_config_execute;
1364         pmenu_item_insert(m, i, 2);
1365
1366         i = pmenu_item_create(m, _("System status log"));
1367         i->on_execute = menu_statuslog_execute;
1368         pmenu_item_insert(m, i, 3);
1369
1370         /* this label isn't translated, so we don't want a gettext() here */
1371         i = pmenu_item_create(m, "Language");
1372         i->on_execute = menu_lang_execute;
1373         pmenu_item_insert(m, i, 4);
1374
1375         i = pmenu_item_create(m, _("Rescan devices"));
1376         i->on_execute = menu_reinit_execute;
1377         pmenu_item_insert(m, i, 5);
1378
1379         i = pmenu_item_create(m, _("Retrieve config from URL"));
1380         i->on_execute = menu_add_url_execute;
1381         pmenu_item_insert(m, i, 6);
1382
1383         i = pmenu_item_create(m, _("Plugins (0)"));
1384         i->on_execute = menu_plugin_execute;
1385         pmenu_item_insert(m, i, 7);
1386
1387         if (lockdown)
1388                 i = pmenu_item_create(m, _("Reboot"));
1389         else
1390                 i = pmenu_item_create(m, _("Exit to shell"));
1391         i->on_execute = pmenu_exit_cb;
1392         pmenu_item_insert(m, i, 8);
1393
1394         result = pmenu_setup(m);
1395
1396         if (result) {
1397                 pb_log("%s:%d: pmenu_setup failed: %s\n", __func__, __LINE__,
1398                         strerror(errno));
1399                 goto fail_setup;
1400         }
1401
1402         m->help_title = _("main menu");
1403         m->help_text = &main_menu_help_text;
1404
1405         menu_opts_off(m->ncm, O_SHOWDESC);
1406         set_menu_mark(m->ncm, " *");
1407         set_current_item(m->ncm, i->nci);
1408
1409         return m;
1410
1411 fail_setup:
1412         talloc_free(m);
1413         return NULL;
1414 }
1415
1416 /*
1417  * plugin_menu_init: Set up the plugin menu instance
1418  */
1419 static struct pmenu *plugin_menu_init(struct cui *cui)
1420 {
1421         struct pmenu_item *i;
1422         struct pmenu *m;
1423         int result;
1424
1425         m = pmenu_init(cui, 2, cui_plugin_menu_exit);
1426         m->scr.frame.ltitle = talloc_asprintf(m, _("Petitboot Plugins"));
1427         m->scr.frame.rtitle = talloc_asprintf(m, "%s", "");
1428         m->scr.frame.help = talloc_strdup(m,
1429                 _("Enter=install, e=details, x=exit, h=help"));
1430         m->scr.frame.status = talloc_asprintf(m,
1431                         _("Available Petitboot Plugins"));
1432
1433         /* add a separator */
1434         i = pmenu_item_create(m, " ");
1435         item_opts_off(i->nci, O_SELECTABLE);
1436         pmenu_item_insert(m, i, 0);
1437
1438         i = pmenu_item_create(m, _("Return to Main Menu"));
1439         i->on_execute = pmenu_exit_cb;
1440         pmenu_item_insert(m, i, 1);
1441
1442         result = pmenu_setup(m);
1443
1444         if (result) {
1445                 pb_log("%s:%d: pmenu_setup failed: %s\n", __func__, __LINE__,
1446                         strerror(errno));
1447                 goto fail_setup;
1448         }
1449
1450         m->help_title = _("plugin menu");
1451         m->help_text = &plugin_menu_help_text;
1452
1453         return m;
1454
1455 fail_setup:
1456         talloc_free(m);
1457         return NULL;
1458 }
1459
1460 static struct discover_client_ops cui_client_ops = {
1461         .device_add = NULL,
1462         .boot_option_add = cui_boot_option_add,
1463         .device_remove = cui_device_remove,
1464         .plugin_option_add = cui_plugin_option_add,
1465         .plugins_remove = cui_plugins_remove,
1466         .update_status = cui_update_status,
1467         .update_sysinfo = cui_update_sysinfo,
1468         .update_config = cui_update_config,
1469 };
1470
1471 /* cui_server_wait - Connect to the discover server.
1472  * @arg: Pointer to the cui instance.
1473  *
1474  * A timeout callback that attempts to connect to the discover server; on
1475  * failure it registers itself with a one second timeout to try again.
1476  * On success the cui->client struct will be set.
1477  *
1478  * Since this updates the status line when called it must not be called
1479  * before the UI is ready.
1480  */
1481 static int cui_server_wait(void *arg)
1482 {
1483         struct cui *cui = cui_from_arg(arg);
1484
1485         if (cui->client) {
1486                 pb_debug("We already have a server!\n");
1487                 return 0;
1488         }
1489
1490         /* We haven't yet connected to the server */
1491         pb_log("Trying to connect...\n");
1492         cui->client = discover_client_init(cui->waitset,
1493                         &cui_client_ops, cui);
1494
1495         if (!cui->client) {
1496                 waiter_register_timeout(cui->waitset, 1000, cui_server_wait,
1497                                         cui);
1498                 nc_scr_status_printf(cui->current,
1499                                      "Info: Waiting for device discovery");
1500         } else {
1501                 nc_scr_status_free(cui->current);
1502                 talloc_steal(cui, cui->client);
1503
1504                 if (cui->has_input) {
1505                         pb_log("Aborting default boot on pb-discover connect\n");
1506                         discover_client_cancel_default(cui->client);
1507                 }
1508
1509                 if (cui->autoboot_opt) {
1510                         pb_log("Sending autoboot override on pb-discover connect\n");
1511                         discover_client_send_temp_autoboot(cui->client,
1512                                         cui->autoboot_opt);
1513                 }
1514         }
1515
1516         return 0;
1517 }
1518
1519 /**
1520  * cui_init - Setup the cui instance.
1521  * @platform_info: A value for the struct cui platform_info member.
1522  *
1523  * Returns a pointer to a struct cui on success, or NULL on error.
1524  *
1525  * Allocates the cui instance, sets up the client and stdin waiters, and
1526  * sets up the ncurses menu screen.
1527  */
1528
1529 struct cui *cui_init(void* platform_info,
1530         int (*js_map)(const struct js_event *e), int start_daemon, int timeout)
1531 {
1532         struct cui *cui;
1533         unsigned int i;
1534
1535         cui = talloc_zero(NULL, struct cui);
1536         if (!cui) {
1537                 pb_log_fn("alloc cui failed.\n");
1538                 fprintf(stderr, _("%s: alloc cui failed.\n"), __func__);
1539                 goto fail_alloc;
1540         }
1541
1542         cui->c_sig = pb_cui_sig;
1543         cui->platform_info = platform_info;
1544         cui->waitset = waitset_create(cui);
1545         cui->statuslog = statuslog_init(cui);
1546
1547         process_init(cui, cui->waitset, false);
1548
1549         /* Loop here for scripts that just started the server. */
1550
1551 retry_start:
1552         for (i = start_daemon ? 2 : 15; i && timeout; i--) {
1553                 cui->client = discover_client_init(cui->waitset,
1554                                 &cui_client_ops, cui);
1555                 if (cui->client || !i)
1556                         break;
1557                 pb_log_fn("waiting for server %d\n", i);
1558                 sleep(1);
1559         }
1560
1561         if (!cui->client && start_daemon) {
1562                 int result;
1563
1564                 start_daemon = 0;
1565
1566                 result = pb_start_daemon(cui);
1567
1568                 if (!result)
1569                         goto retry_start;
1570
1571                 pb_log_fn("discover_client_init failed.\n");
1572                 fprintf(stderr, _("%s: error: discover_client_init failed.\n"),
1573                         __func__);
1574                 fprintf(stderr, _("could not start pb-discover, the petitboot "
1575                         "daemon.\n"));
1576                 goto fail_client_init;
1577         }
1578
1579         if (!cui->client && !timeout) {
1580                 /* Have the first timeout fire immediately so we can check
1581                  * for the server as soon as the UI is ready */
1582                 waiter_register_timeout(cui->waitset, 0,
1583                                         cui_server_wait, cui);
1584         } else if (!cui->client) {
1585                 pb_log_fn("discover_client_init failed.\n");
1586                 fprintf(stderr, _("%s: error: discover_client_init failed.\n"),
1587                         __func__);
1588                 fprintf(stderr, _("check that pb-discover, "
1589                         "the petitboot daemon is running.\n"));
1590                 goto fail_client_init;
1591         }
1592
1593         atexit(cui_atexit);
1594         talloc_steal(cui, cui->client);
1595         cui_start();
1596
1597         cui->main = main_menu_init(cui);
1598         if (!cui->main)
1599                 goto fail_client_init;
1600
1601         cui->plugin_menu = plugin_menu_init(cui);
1602         if (!cui->plugin_menu)
1603                 goto fail_client_init;
1604
1605         waiter_register_io(cui->waitset, STDIN_FILENO, WAIT_IN,
1606                         cui_process_key, cui);
1607
1608         if (js_map) {
1609
1610                 cui->pjs = pjs_init(cui, js_map);
1611
1612                 if (cui->pjs)
1613                         waiter_register_io(cui->waitset, pjs_get_fd(cui->pjs),
1614                                         WAIT_IN, cui_process_js, cui);
1615         }
1616
1617         return cui;
1618
1619 fail_client_init:
1620         talloc_free(cui);
1621 fail_alloc:
1622         return NULL;
1623 }
1624
1625 /**
1626  * cui_cancel_autoboot_on_exit - On exit spin until the server is available.
1627  *
1628  * If the program exits before connecting to the server autoboot won't be
1629  * cancelled even though there has been keyboard activity. A child is forked
1630  * which will spin until the server is connected and told to cancel autoboot.
1631  */
1632 static void cui_cancel_autoboot_on_exit(struct cui *cui)
1633 {
1634         pid_t pid;
1635
1636         if (!cui->client) {
1637                 /* Fork a child to tell the server to cancel autoboot */
1638                 pid = fork();
1639                 if (!pid) {
1640                         cui_detached = true;
1641
1642                         /* Loop until connection established */
1643                         while (!cui->client) {
1644                                 cui->client = discover_client_init(cui->waitset,
1645                                                 &cui_client_ops, cui);
1646                                 if (!cui->client)
1647                                         sleep(1);
1648                         }
1649
1650                         talloc_steal(cui, cui->client);
1651                         discover_client_cancel_default(cui->client);
1652                         exit(EXIT_SUCCESS);
1653                 }
1654                 if (pid < 0)
1655                         pb_log("Failed to fork child on exit: %m\n");
1656         } else
1657                 discover_client_cancel_default(cui->client);
1658 }
1659
1660 /**
1661  * cui_run - The main cui program loop.
1662  * @cui: The cui instance.
1663  * @main: The menu to use as the main menu.
1664  *
1665  * Runs the cui engine.  Does not return until indicated to do so by some
1666  * user action, or an error occurs.  Frees the cui object on return.
1667  * Returns 0 on success (return to shell), -1 on error (should restart).
1668  */
1669
1670 int cui_run(struct cui *cui)
1671 {
1672         assert(cui);
1673         assert(cui->main);
1674
1675         cui->current = &cui->main->scr;
1676         cui->default_item = 0;
1677
1678         nc_scr_post(cui->current);
1679
1680         while (1) {
1681                 int result = waiter_poll(cui->waitset);
1682
1683                 if (result < 0) {
1684                         pb_log_fn("poll: %s\n", strerror(errno));
1685                         break;
1686                 }
1687
1688                 if (cui->abort)
1689                         break;
1690
1691                 while (cui->resize) {
1692                         cui->resize = 0;
1693                         cui_handle_resize(cui);
1694                 }
1695         }
1696
1697         cui_cancel_autoboot_on_exit(cui);
1698
1699         cui_atexit();
1700
1701         return cui->abort ? 0 : -1;
1702 }