]> git.ozlabs.org Git - petitboot/blob - ui/ncurses/nc-cui.c
6a6169431c8cedbc47e95b0b90559a024746a1ea
[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 <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/ioctl.h>
28
29 #include "log/log.h"
30 #include "pb-protocol/pb-protocol.h"
31 #include "talloc/talloc.h"
32 #include "waiter/waiter.h"
33 #include "process/process.h"
34 #include "ui/common/discover-client.h"
35 #include "ui/common/ui-system.h"
36 #include "nc-cui.h"
37 #include "nc-boot-editor.h"
38 #include "nc-config.h"
39 #include "nc-sysinfo.h"
40 #include "nc-helpscreen.h"
41
42 static void cui_start(void)
43 {
44         initscr();                      /* Initialize ncurses. */
45         cbreak();                       /* Disable line buffering. */
46         noecho();                       /* Disable getch() echo. */
47         keypad(stdscr, TRUE);           /* Enable num keypad keys. */
48         nonl();                         /* Disable new-line translation. */
49         intrflush(stdscr, FALSE);       /* Disable interrupt flush. */
50         curs_set(0);                    /* Make cursor invisible */
51         nodelay(stdscr, TRUE);          /* Enable non-blocking getch() */
52
53         /* We may be operating with an incorrect $TERM type; in this case
54          * the keymappings will be slightly broken. We want at least
55          * backspace to work though, so we'll define both DEL and ^H to
56          * map to backspace */
57         define_key("\x7f", KEY_BACKSPACE);
58         define_key("\x08", KEY_BACKSPACE);
59
60         /* we need backtab too, for form navigation. vt220 doesn't include
61          * this (kcbt), but we don't want to require a full linux/xterm termcap
62          */
63         define_key("\x1b[Z", KEY_BTAB);
64
65         while (getch() != ERR)          /* flush stdin */
66                 (void)0;
67 }
68
69 static void cui_atexit(void)
70 {
71         clear();
72         refresh();
73         endwin();
74 }
75
76 /**
77  * cui_abort - Signal the main cui program loop to exit.
78  *
79  * Sets cui.abort, which causes the cui_run() routine to return.
80  */
81
82 void cui_abort(struct cui *cui)
83 {
84         pb_log("%s: exiting\n", __func__);
85         cui->abort = 1;
86 }
87
88 /**
89  * cui_resize - Signal the main cui program loop to resize
90  *
91  * Called at SIGWINCH.
92  */
93
94 void cui_resize(struct cui *cui)
95 {
96         pb_debug("%s: resizing\n", __func__);
97         cui->resize = 1;
98 }
99
100 /**
101  * cui_on_exit - A generic main menu exit callback.
102  */
103
104 void cui_on_exit(struct pmenu *menu)
105 {
106         cui_abort(cui_from_pmenu(menu));
107 }
108
109 /**
110  * cui_run_cmd - A generic cb to run the supplied command.
111  */
112
113 int cui_run_cmd(struct pmenu_item *item)
114 {
115         int result;
116         struct cui *cui = cui_from_item(item);
117         const char **cmd_argv = item->data;
118
119         nc_scr_status_printf(cui->current, "Running %s...", cmd_argv[0]);
120
121         def_prog_mode();
122
123         result = process_run_simple_argv(item, cmd_argv);
124
125         reset_prog_mode();
126         redrawwin(cui->current->main_ncw);
127
128         if (result) {
129                 pb_log("%s: failed: '%s'\n", __func__, cmd_argv[0]);
130                 nc_scr_status_printf(cui->current, "Failed: %s", cmd_argv[0]);
131         }
132
133         return result;
134 }
135
136 /**
137  * cui_boot - A generic cb to run kexec.
138  */
139
140 static int cui_boot(struct pmenu_item *item)
141 {
142         int result;
143         struct cui *cui = cui_from_item(item);
144         struct cui_opt_data *cod = cod_from_item(item);
145
146         assert(cui->current == &cui->main->scr);
147
148         pb_debug("%s: %s\n", __func__, cod->name);
149
150         nc_scr_status_printf(cui->current, "Booting %s...", cod->name);
151
152         result = discover_client_boot(cui->client, NULL, cod->opt, cod->bd);
153
154         if (result) {
155                 nc_scr_status_printf(cui->current,
156                                 "Failed: boot %s", cod->bd->image);
157         }
158
159         return 0;
160 }
161
162 static void cui_boot_editor_on_exit(struct cui *cui,
163                 struct pmenu_item *item,
164                 struct pb_boot_data *bd)
165 {
166         struct pmenu *menu = cui->main;
167         struct cui_opt_data *cod;
168         static int user_idx = 0;
169
170         /* Was the edit cancelled? */
171         if (!bd) {
172                 cui_set_current(cui, &cui->main->scr);
173                 talloc_free(cui->boot_editor);
174                 cui->boot_editor = NULL;
175                 return;
176         }
177
178         /* Is this was a new item, we'll need to update the menu */
179         if (!item) {
180                 int insert_pt;
181
182                 cod = talloc_zero(NULL, struct cui_opt_data);
183                 cod->name = talloc_asprintf(cod, "User item %u", ++user_idx);
184
185                 item = pmenu_item_create(menu, cod->name);
186                 if (!item) {
187                         talloc_free(cod);
188                         goto out;
189                 }
190
191                 item->on_edit = cui_item_edit;
192                 item->on_execute = cui_boot;
193                 item->data = cod;
194
195                 talloc_steal(item, cod);
196
197                 /* Detach the items array. */
198                 set_menu_items(menu->ncm, NULL);
199
200                 /* Insert new item at insert_pt. */
201                 insert_pt = pmenu_grow(menu, 1);
202                 pmenu_item_insert(menu, item, insert_pt);
203
204                 /* Re-attach the items array. */
205                 set_menu_items(menu->ncm, menu->items);
206                 nc_scr_post(&menu->scr);
207         } else {
208                 cod = item->data;
209         }
210
211         cod->bd = talloc_steal(cod, bd);
212
213         set_current_item(item->pmenu->ncm, item->nci);
214 out:
215         cui_set_current(cui, &cui->main->scr);
216         talloc_free(cui->boot_editor);
217         cui->boot_editor = NULL;
218 }
219
220 void cui_item_edit(struct pmenu_item *item)
221 {
222         struct cui *cui = cui_from_item(item);
223         cui->boot_editor = boot_editor_init(cui, item, cui->sysinfo,
224                                         cui_boot_editor_on_exit);
225         cui_set_current(cui, boot_editor_scr(cui->boot_editor));
226 }
227
228 void cui_item_new(struct pmenu *menu)
229 {
230         struct cui *cui = cui_from_pmenu(menu);
231         cui->boot_editor = boot_editor_init(cui, NULL, cui->sysinfo,
232                                         cui_boot_editor_on_exit);
233         cui_set_current(cui, boot_editor_scr(cui->boot_editor));
234 }
235
236 static void cui_sysinfo_exit(struct cui *cui)
237 {
238         cui_set_current(cui, &cui->main->scr);
239         talloc_free(cui->sysinfo_screen);
240         cui->sysinfo_screen = NULL;
241 }
242
243 void cui_show_sysinfo(struct cui *cui)
244 {
245         cui->sysinfo_screen = sysinfo_screen_init(cui, cui->sysinfo,
246                         cui_sysinfo_exit);
247         cui_set_current(cui, sysinfo_screen_scr(cui->sysinfo_screen));
248 }
249
250 static void cui_config_exit(struct cui *cui)
251 {
252         cui_set_current(cui, &cui->main->scr);
253         talloc_free(cui->config_screen);
254         cui->config_screen = NULL;
255 }
256
257 void cui_show_config(struct cui *cui)
258 {
259         cui->config_screen = config_screen_init(cui, cui->config,
260                         cui->sysinfo, cui_config_exit);
261         cui_set_current(cui, config_screen_scr(cui->config_screen));
262 }
263
264 static void cui_help_exit(struct cui *cui)
265 {
266         cui_set_current(cui, help_screen_return_scr(cui->help_screen));
267         talloc_free(cui->help_screen);
268         cui->help_screen = NULL;
269 }
270
271 void cui_show_help(struct cui *cui, const char *title, const char *text)
272 {
273         if (!cui->current)
274                 return;
275
276         if (cui->help_screen)
277                 return;
278
279         cui->help_screen = help_screen_init(cui, cui->current,
280                         title, text, cui_help_exit);
281
282         if (cui->help_screen)
283                 cui_set_current(cui, help_screen_scr(cui->help_screen));
284 }
285
286 /**
287  * cui_set_current - Set the currently active screen and redraw it.
288  */
289
290 struct nc_scr *cui_set_current(struct cui *cui, struct nc_scr *scr)
291 {
292         struct nc_scr *old;
293
294         DBGS("%p -> %p\n", cui->current, scr);
295
296         assert(cui->current != scr);
297
298         old = cui->current;
299         nc_scr_unpost(old);
300
301         cui->current = scr;
302
303         nc_scr_post(cui->current);
304
305         return old;
306 }
307
308 static bool process_global_keys(struct cui *cui, int key)
309 {
310         switch (key) {
311         case 0xc:
312                 if (cui->current && cui->current->main_ncw)
313                         wrefresh(curscr);
314                 return true;
315         }
316         return false;
317 }
318
319 /**
320  * cui_process_key - Process input on stdin.
321  */
322
323 static int cui_process_key(void *arg)
324 {
325         struct cui *cui = cui_from_arg(arg);
326
327         assert(cui->current);
328
329         for (;;) {
330                 int c = getch();
331
332                 pb_debug("%s: got key %d\n", __func__, c);
333
334                 if (c == ERR)
335                         break;
336
337                 if (!cui->has_input) {
338                         pb_log("UI input received (key = %d), aborting "
339                                         "default boot\n", c);
340                         discover_client_cancel_default(cui->client);
341                         cui->has_input = true;
342                 }
343
344                 if (process_global_keys(cui, c))
345                         continue;
346
347                 cui->current->process_key(cui->current, c);
348         }
349
350         return 0;
351 }
352
353 /**
354  * cui_process_js - Process joystick events.
355  */
356
357 static int cui_process_js(void *arg)
358 {
359         struct cui *cui = cui_from_arg(arg);
360         int c;
361
362         c = pjs_process_event(cui->pjs);
363
364         if (c) {
365                 ungetch(c);
366                 cui_process_key(arg);
367         }
368
369         return 0;
370 }
371
372 /**
373  * cui_handle_resize - Handle the term resize.
374  */
375
376 static void cui_handle_resize(struct cui *cui)
377 {
378         struct winsize ws;
379
380         if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
381                 pb_log("%s: ioctl failed: %s\n", __func__, strerror(errno));
382                 return;
383         }
384
385         pb_debug("%s: {%u,%u}\n", __func__, ws.ws_row, ws.ws_col);
386
387         wclear(cui->current->main_ncw);
388         resize_term(ws.ws_row, ws.ws_col);
389         cui->current->resize(cui->current);
390
391         /* For some reason this makes ncurses redraw the screen */
392         getch();
393         redrawwin(cui->current->main_ncw);
394         wrefresh(cui->current->main_ncw);
395 }
396
397 /**
398  * cui_device_add - Client device_add callback.
399  *
400  * Creates menu_items for all the device boot_options and inserts those
401  * menu_items into the main menu.  Redraws the main menu if it is active.
402  */
403
404 static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
405                 void *arg)
406 {
407         struct pmenu_item *i, *dev_hdr = NULL;
408         struct cui *cui = cui_from_arg(arg);
409         struct cui_opt_data *cod;
410         const char *tab = "  ";
411         unsigned int insert_pt;
412         int result, rows, cols;
413         ITEM *selected;
414         char *name;
415
416         pb_debug("%s: %p %s\n", __func__, opt, opt->id);
417
418         selected = current_item(cui->main->ncm);
419         menu_format(cui->main->ncm, &rows, &cols);
420
421         if (cui->current == &cui->main->scr)
422                 nc_scr_unpost(cui->current);
423
424         /* Check if the boot device is new */
425         dev_hdr = pmenu_find_device(cui->main, dev, opt);
426
427         /* All actual boot entries are 'tabbed' across */
428         name = talloc_asprintf(cui->main, "%s%s",
429                         tab, opt->name ? : "Unknown Name");
430
431         /* Save the item in opt->ui_info for cui_device_remove() */
432         opt->ui_info = i = pmenu_item_create(cui->main, name);
433         talloc_free(name);
434         if (!i)
435                 return -1;
436
437         i->on_edit = cui_item_edit;
438         i->on_execute = cui_boot;
439         i->data = cod = talloc(i, struct cui_opt_data);
440
441         cod->opt = opt;
442         cod->dev = dev;
443         cod->opt_hash = pb_opt_hash(dev, opt);
444         cod->name = opt->name;
445         cod->bd = talloc(i, struct pb_boot_data);
446
447         cod->bd->image = talloc_strdup(cod->bd, opt->boot_image_file);
448         cod->bd->initrd = talloc_strdup(cod->bd, opt->initrd_file);
449         cod->bd->dtb = talloc_strdup(cod->bd, opt->dtb_file);
450         cod->bd->args = talloc_strdup(cod->bd, opt->boot_args);
451
452         /* This disconnects items array from menu. */
453         result = set_menu_items(cui->main->ncm, NULL);
454
455         if (result)
456                 pb_log("%s: set_menu_items failed: %d\n", __func__, result);
457
458         /* Insert new items at insert_pt. */
459         if (dev_hdr) {
460                 insert_pt = pmenu_grow(cui->main, 2);
461                 pmenu_item_insert(cui->main, dev_hdr, insert_pt);
462                 pb_log("%s: adding new device hierarchy %s\n",
463                         __func__,opt->device_id);
464                 pmenu_item_insert(cui->main, i, insert_pt+1);
465         } else {
466                 insert_pt = pmenu_grow(cui->main, 1);
467                 pmenu_item_add(cui->main, i, insert_pt);
468         }
469
470         pb_log("%s: adding opt '%s'\n", __func__, cod->name);
471         pb_log("   image  '%s'\n", cod->bd->image);
472         pb_log("   initrd '%s'\n", cod->bd->initrd);
473         pb_log("   args   '%s'\n", cod->bd->args);
474
475         /* Re-attach the items array. */
476         result = set_menu_items(cui->main->ncm, cui->main->items);
477
478         if (result)
479                 pb_log("%s: set_menu_items failed: %d\n", __func__, result);
480
481         if (0) {
482                 pb_log("%s\n", __func__);
483                 pmenu_dump_items(cui->main->items,
484                         item_count(cui->main->ncm) + 1);
485         }
486
487         if (!item_visible(selected)) {
488                 int idx, top;
489
490                 top = top_row(cui->main->ncm);
491                 idx = item_index(selected);
492
493                 /* If our index is above the current top row, align
494                  * us to the new top. Otherwise, align us to the new
495                  * bottom */
496                 top = top < idx ? idx - rows : idx;
497
498                 set_top_row(cui->main->ncm, top);
499                 set_current_item(cui->main->ncm, selected);
500         }
501
502         if (cui->current == &cui->main->scr)
503                 nc_scr_post(cui->current);
504
505         return 0;
506 }
507
508 /**
509  * cui_device_remove - Client device remove callback.
510  *
511  * Removes all the menu_items for the device from the main menu and redraws the
512  * main menu if it is active.
513  */
514
515 static void cui_device_remove(struct device *dev, void *arg)
516 {
517         struct cui *cui = cui_from_arg(arg);
518         struct boot_option *opt;
519         unsigned int i;
520         int result;
521
522         pb_log("%s: %p %s\n", __func__, dev, dev->id);
523
524         if (cui->current == &cui->main->scr)
525                 nc_scr_unpost(cui->current);
526
527         /* This disconnects items array from menu. */
528
529         result = set_menu_items(cui->main->ncm, NULL);
530
531         if (result)
532                 pb_log("%s: set_menu_items failed: %d\n", __func__, result);
533
534         list_for_each_entry(&dev->boot_options, opt, list) {
535                 struct pmenu_item *item = pmenu_item_from_arg(opt->ui_info);
536
537                 assert(pb_protocol_device_cmp(dev, cod_from_item(item)->dev));
538                 pmenu_remove(cui->main, item);
539         }
540
541         /* Manually remove remaining device hierarchy item */
542         for (i=0; i < cui->main->item_count; i++) {
543                 struct pmenu_item *item = item_userptr(cui->main->items[i]);
544                 if (!item || !item->data )
545                         continue;
546
547                 struct cui_opt_data *data = item->data;
548                 if (data && data->dev && data->dev == dev)
549                         pmenu_remove(cui->main,item);
550         }
551
552         /* Re-attach the items array. */
553
554         result = set_menu_items(cui->main->ncm, cui->main->items);
555
556         if (result)
557                 pb_log("%s: set_menu_items failed: %d\n", __func__, result);
558
559         if (0) {
560                 pb_log("%s\n", __func__);
561                 pmenu_dump_items(cui->main->items,
562                         item_count(cui->main->ncm) + 1);
563         }
564
565         if (cui->current == &cui->main->scr)
566                 nc_scr_post(cui->current);
567 }
568
569 static void cui_update_status(struct boot_status *status, void *arg)
570 {
571         struct cui *cui = cui_from_arg(arg);
572
573         nc_scr_status_printf(cui->current,
574                         "%s: %s",
575                         status->type == BOOT_STATUS_ERROR ? "Error" : "Info",
576                         status->message);
577
578 }
579
580 static void cui_update_mm_title(struct cui *cui)
581 {
582         struct nc_frame *frame = &cui->main->scr.frame;
583
584         talloc_free(frame->rtitle);
585
586         frame->rtitle = talloc_strdup(cui->main, cui->sysinfo->type);
587         if (cui->sysinfo->identifier)
588                 frame->rtitle = talloc_asprintf_append(frame->rtitle,
589                                 " %s", cui->sysinfo->identifier);
590
591         if (cui->current == &cui->main->scr)
592                 nc_scr_post(cui->current);
593 }
594
595 static void cui_update_sysinfo(struct system_info *sysinfo, void *arg)
596 {
597         struct cui *cui = cui_from_arg(arg);
598         cui->sysinfo = talloc_steal(cui, sysinfo);
599
600         /* if we're currently displaying the system info screen, inform it
601          * of the updated information. */
602         if (cui->sysinfo_screen)
603                 sysinfo_screen_update(cui->sysinfo_screen, sysinfo);
604
605         /* ... and do the same with the config screen... */
606         if (cui->config_screen)
607                 config_screen_update(cui->config_screen, cui->config, sysinfo);
608
609         /* ... and the boot editor. */
610         if (cui->boot_editor)
611                 boot_editor_update(cui->boot_editor, sysinfo);
612
613         cui_update_mm_title(cui);
614 }
615
616 static void cui_update_config(struct config *config, void *arg)
617 {
618         struct cui *cui = cui_from_arg(arg);
619         cui->config = talloc_steal(cui, config);
620
621         if (cui->config_screen)
622                 config_screen_update(cui->config_screen, config, cui->sysinfo);
623
624         if (config->safe_mode)
625                 nc_scr_status_printf(cui->current,
626                                 "SAFE MODE: select '%s' to continue",
627                                 "Rescan devices");
628 }
629
630 int cui_send_config(struct cui *cui, struct config *config)
631 {
632         return discover_client_send_config(cui->client, config);
633 }
634
635 void cui_send_reinit(struct cui *cui)
636 {
637         discover_client_send_reinit(cui->client);
638 }
639
640 static struct discover_client_ops cui_client_ops = {
641         .device_add = NULL,
642         .boot_option_add = cui_boot_option_add,
643         .device_remove = cui_device_remove,
644         .update_status = cui_update_status,
645         .update_sysinfo = cui_update_sysinfo,
646         .update_config = cui_update_config,
647 };
648
649 /**
650  * cui_init - Setup the cui instance.
651  * @platform_info: A value for the struct cui platform_info member.
652  *
653  * Returns a pointer to a struct cui on success, or NULL on error.
654  *
655  * Allocates the cui instance, sets up the client and stdin waiters, and
656  * sets up the ncurses menu screen.
657  */
658
659 struct cui *cui_init(void* platform_info,
660         int (*js_map)(const struct js_event *e), int start_deamon)
661 {
662         struct cui *cui;
663         unsigned int i;
664
665         cui = talloc_zero(NULL, struct cui);
666
667         if (!cui) {
668                 pb_log("%s: alloc cui failed.\n", __func__);
669                 fprintf(stderr, "%s: alloc cui failed.\n", __func__);
670                 goto fail_alloc;
671         }
672
673         cui->c_sig = pb_cui_sig;
674         cui->platform_info = platform_info;
675         cui->waitset = waitset_create(cui);
676
677         process_init(cui, cui->waitset, false);
678
679         /* Loop here for scripts that just started the server. */
680
681 retry_start:
682         for (i = start_deamon ? 2 : 10; i; i--) {
683                 cui->client = discover_client_init(cui->waitset,
684                                 &cui_client_ops, cui);
685                 if (cui->client || !i)
686                         break;
687                 pb_log("%s: waiting for server %d\n", __func__, i);
688                 sleep(1);
689         }
690
691         if (!cui->client && start_deamon) {
692                 int result;
693
694                 start_deamon = 0;
695
696                 result = pb_start_daemon(cui);
697
698                 if (!result)
699                         goto retry_start;
700
701                 pb_log("%s: discover_client_init failed.\n", __func__);
702                 fprintf(stderr, "%s: error: discover_client_init failed.\n",
703                         __func__);
704                 fprintf(stderr, "could not start pb-discover, the petitboot "
705                         "daemon.\n");
706                 goto fail_client_init;
707         }
708
709         if (!cui->client) {
710                 pb_log("%s: discover_client_init failed.\n", __func__);
711                 fprintf(stderr, "%s: error: discover_client_init failed.\n",
712                         __func__);
713                 fprintf(stderr, "check that pb-discover, "
714                         "the petitboot daemon is running.\n");
715                 goto fail_client_init;
716         }
717
718         atexit(cui_atexit);
719         talloc_steal(cui, cui->client);
720         cui_start();
721
722         waiter_register_io(cui->waitset, STDIN_FILENO, WAIT_IN,
723                         cui_process_key, cui);
724
725         if (js_map) {
726
727                 cui->pjs = pjs_init(cui, js_map);
728
729                 if (cui->pjs)
730                         waiter_register_io(cui->waitset, pjs_get_fd(cui->pjs),
731                                         WAIT_IN, cui_process_js, cui);
732         }
733
734         return cui;
735
736 fail_client_init:
737         talloc_free(cui);
738 fail_alloc:
739         return NULL;
740 }
741
742 /**
743  * cui_run - The main cui program loop.
744  * @cui: The cui instance.
745  * @main: The menu to use as the main menu.
746  *
747  * Runs the cui engine.  Does not return until indicated to do so by some
748  * user action, or an error occurs.  Frees the cui object on return.
749  * Returns 0 on success (return to shell), -1 on error (should restart).
750  */
751
752 int cui_run(struct cui *cui, struct pmenu *main, unsigned int default_item)
753 {
754         assert(main);
755
756         cui->main = main;
757         cui->current = &cui->main->scr;
758         cui->default_item = default_item;
759
760         nc_scr_post(cui->current);
761
762         while (1) {
763                 int result = waiter_poll(cui->waitset);
764
765                 if (result < 0) {
766                         pb_log("%s: poll: %s\n", __func__, strerror(errno));
767                         break;
768                 }
769
770                 if (cui->abort)
771                         break;
772
773                 while (cui->resize) {
774                         cui->resize = 0;
775                         cui_handle_resize(cui);
776                 }
777         }
778
779         cui_atexit();
780
781         return cui->abort ? 0 : -1;
782 }