]> git.ozlabs.org Git - petitboot/blobdiff - ui/ncurses/nc-cui.c
discover: Maintain a backlog of status updates
[petitboot] / ui / ncurses / nc-cui.c
index 56e7653af112c67e8f0afc67c6fc45b4d6cb794e..b4f74a793e73f906b3aaaa68b22d7deb66587a75 100644 (file)
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
+#include <sys/reboot.h>
 
 #include "log/log.h"
 #include "pb-protocol/pb-protocol.h"
 #include "nc-sysinfo.h"
 #include "nc-lang.h"
 #include "nc-helpscreen.h"
+#include "nc-statuslog.h"
 #include "nc-subset.h"
 
 extern const struct help_text main_menu_help_text;
 
+static bool cui_detached = false;
+
 static struct pmenu *main_menu_init(struct cui *cui);
 
+static bool lockdown_active(void)
+{
+       bool lockdown = false;
+       if (access(LOCKDOWN_FILE, F_OK) != -1)
+               lockdown = true;
+       return lockdown;
+}
+
 static void cui_start(void)
 {
        initscr();                      /* Initialize ncurses. */
@@ -75,10 +87,15 @@ static void cui_start(void)
         * Petitboot to exit if they're left undefined */
        define_key("\x1b\x5b\x35\x7e", KEY_PPAGE);
        define_key("\x1b\x5b\x36\x7e", KEY_NPAGE);
+       define_key("\x1b\x5b\x31\x7e", KEY_HOME);
+       define_key("\x1b\x5b\x34\x7e", KEY_END);
        define_key("\x1b\x4f\x48", KEY_HOME);
        define_key("\x1b\x4f\x46", KEY_END);
        define_key("OH", KEY_HOME);
        define_key("OF", KEY_END);
+       define_key("\x1b\x5b\x41", KEY_UP);
+       define_key("\x1b\x5b\x42", KEY_DOWN);
+       define_key("\x1b\x5b\x33\x7e", KEY_DC);
 
        while (getch() != ERR)          /* flush stdin */
                (void)0;
@@ -86,9 +103,19 @@ static void cui_start(void)
 
 static void cui_atexit(void)
 {
+       if (cui_detached)
+               return;
+
        clear();
        refresh();
        endwin();
+
+       bool lockdown = lockdown_active();
+
+       while (lockdown) {
+               sync();
+               reboot(RB_AUTOBOOT);
+       }
 }
 
 /**
@@ -309,6 +336,19 @@ void cui_show_lang(struct cui *cui)
        cui_set_current(cui, lang_screen_scr(cui->lang_screen));
 }
 
+static void cui_statuslog_exit(struct cui *cui)
+{
+       cui_set_current(cui, &cui->main->scr);
+       talloc_free(cui->statuslog_screen);
+       cui->statuslog_screen = NULL;
+}
+
+void cui_show_statuslog(struct cui *cui)
+{
+       cui->statuslog_screen = statuslog_screen_init(cui, cui_statuslog_exit);
+       cui_set_current(cui, statuslog_screen_scr(cui->statuslog_screen));
+}
+
 static void cui_add_url_exit(struct cui *cui)
 {
        cui_set_current(cui, &cui->main->scr);
@@ -420,10 +460,15 @@ static int cui_process_key(void *arg)
                        break;
 
                if (!cui->has_input) {
-                       pb_log("UI input received (key = %d), aborting "
-                                       "default boot\n", c);
-                       discover_client_cancel_default(cui->client);
                        cui->has_input = true;
+                       if (cui->client) {
+                               pb_log("UI input received (key = %d), aborting "
+                                       "default boot\n", c);
+                               discover_client_cancel_default(cui->client);
+                       } else {
+                               pb_log("UI input received (key = %d), aborting "
+                                       "once server connects\n", c);
+                       }
                }
 
                if (process_global_keys(cui, c))
@@ -533,6 +578,7 @@ static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
        cod->bd->initrd = talloc_strdup(cod->bd, opt->initrd_file);
        cod->bd->dtb = talloc_strdup(cod->bd, opt->dtb_file);
        cod->bd->args = talloc_strdup(cod->bd, opt->boot_args);
+       cod->bd->args_sig_file = talloc_strdup(cod->bd, opt->args_sig_file);
 
        /* This disconnects items array from menu. */
        result = set_menu_items(cui->main->ncm, NULL);
@@ -556,6 +602,7 @@ static int cui_boot_option_add(struct device *dev, struct boot_option *opt,
        pb_log("   image  '%s'\n", cod->bd->image);
        pb_log("   initrd '%s'\n", cod->bd->initrd);
        pb_log("   args   '%s'\n", cod->bd->args);
+       pb_log("   argsig '%s'\n", cod->bd->args_sig_file);
 
        /* Re-attach the items array. */
        result = set_menu_items(cui->main->ncm, cui->main->items);
@@ -661,16 +708,15 @@ static void cui_device_remove(struct device *dev, void *arg)
                nc_scr_post(cui->current);
 }
 
-static void cui_update_status(struct boot_status *status, void *arg)
+static void cui_update_status(struct status *status, void *arg)
 {
        struct cui *cui = cui_from_arg(arg);
 
-       nc_scr_status_printf(cui->current,
-                       "%s: %s",
-                       status->type == BOOT_STATUS_ERROR ?
-                               _("Error") : _("Info"),
-                       status->message);
+       statuslog_append_steal(cui, cui->statuslog, status);
 
+       /* Ignore status messages from the backlog */
+       if (!status->backlog)
+               nc_scr_status_printf(cui->current, "%s", status->message);
 }
 
 static void cui_update_mm_title(struct cui *cui)
@@ -698,6 +744,9 @@ static void cui_update_sysinfo(struct system_info *sysinfo, void *arg)
        if (cui->sysinfo_screen)
                sysinfo_screen_update(cui->sysinfo_screen, sysinfo);
 
+       if (cui->subset_screen)
+               subset_screen_update(cui->subset_screen);
+
        /* ... and do the same with the config screen... */
        if (cui->config_screen)
                config_screen_update(cui->config_screen, cui->config, sysinfo);
@@ -744,6 +793,9 @@ static void cui_update_config(struct config *config, void *arg)
        if (config->lang)
                cui_update_language(cui, config->lang);
 
+       if (cui->subset_screen)
+               subset_screen_update(cui->subset_screen);
+
        if (cui->config_screen)
                config_screen_update(cui->config_screen, config, cui->sysinfo);
 
@@ -786,15 +838,23 @@ static int menu_lang_execute(struct pmenu_item *item)
        return 0;
 }
 
+static int menu_statuslog_execute(struct pmenu_item *item)
+{
+       cui_show_statuslog(cui_from_item(item));
+       return 0;
+}
+
 static int menu_reinit_execute(struct pmenu_item *item)
 {
-       cui_send_reinit(cui_from_item(item));
+       if (cui_from_item(item)->client)
+               cui_send_reinit(cui_from_item(item));
        return 0;
 }
 
 static int menu_add_url_execute(struct pmenu_item *item)
 {
-       cui_show_add_url(cui_from_item(item));
+       if (cui_from_item(item)->client)
+               cui_show_add_url(cui_from_item(item));
        return 0;
 }
 
@@ -806,8 +866,9 @@ static struct pmenu *main_menu_init(struct cui *cui)
        struct pmenu_item *i;
        struct pmenu *m;
        int result;
+       bool lockdown = lockdown_active();
 
-       m = pmenu_init(cui, 7, cui_on_exit);
+       m = pmenu_init(cui, 8, cui_on_exit);
        if (!m) {
                pb_log("%s: failed\n", __func__);
                return NULL;
@@ -819,7 +880,7 @@ static struct pmenu *main_menu_init(struct cui *cui)
                "Petitboot (" PACKAGE_VERSION ")");
        m->scr.frame.rtitle = NULL;
        m->scr.frame.help = talloc_strdup(m,
-               _("Enter=accept, e=edit, n=new, x=exit, l=language, h=help"));
+               _("Enter=accept, e=edit, n=new, x=exit, l=language, g=log, h=help"));
        m->scr.frame.status = talloc_strdup(m, _("Welcome to Petitboot"));
 
        /* add a separator */
@@ -836,22 +897,29 @@ static struct pmenu *main_menu_init(struct cui *cui)
        i->on_execute = menu_config_execute;
        pmenu_item_insert(m, i, 2);
 
+       i = pmenu_item_create(m, _("System status log"));
+       i->on_execute = menu_statuslog_execute;
+       pmenu_item_insert(m, i, 3);
+
        /* this label isn't translated, so we don't want a gettext() here */
        i = pmenu_item_create(m, "Language");
        i->on_execute = menu_lang_execute;
-       pmenu_item_insert(m, i, 3);
+       pmenu_item_insert(m, i, 4);
 
        i = pmenu_item_create(m, _("Rescan devices"));
        i->on_execute = menu_reinit_execute;
-       pmenu_item_insert(m, i, 4);
+       pmenu_item_insert(m, i, 5);
 
        i = pmenu_item_create(m, _("Retrieve config from URL"));
        i->on_execute = menu_add_url_execute;
-       pmenu_item_insert(m, i, 5);
+       pmenu_item_insert(m, i, 6);
 
-       i = pmenu_item_create(m, _("Exit to shell"));
+       if (lockdown)
+               i = pmenu_item_create(m, _("Reboot"));
+       else
+               i = pmenu_item_create(m, _("Exit to shell"));
        i->on_execute = pmenu_exit_cb;
-       pmenu_item_insert(m, i, 6);
+       pmenu_item_insert(m, i, 7);
 
        result = pmenu_setup(m);
 
@@ -884,6 +952,74 @@ static struct discover_client_ops cui_client_ops = {
        .update_config = cui_update_config,
 };
 
+/* cui_server_wait_on_exit - On exit spin until the server is available.
+ *
+ * If the program exits before connecting to the server autoboot won't be
+ * cancelled even though there has been keyboard activity. This function is
+ * called by a child process which will spin until the server is connected and
+ * told to cancel autoboot.
+ *
+ * Processes exiting from this function will not carry out the cui_atexit()
+ * steps.
+ */
+static void cui_server_wait_on_exit(struct cui *cui)
+{
+       cui_detached = true;
+
+       while (!cui->client) {
+               cui->client = discover_client_init(cui->waitset,
+                               &cui_client_ops, cui);
+               if (!cui->client)
+                       sleep(1);
+       }
+
+       talloc_steal(cui, cui->client);
+       discover_client_cancel_default(cui->client);
+}
+
+/* cui_server_wait - Connect to the discover server.
+ * @arg: Pointer to the cui instance.
+ *
+ * A timeout callback that attempts to connect to the discover server; on
+ * failure it registers itself with a one second timeout to try again.
+ * On success the cui->client struct will be set.
+ *
+ * Since this updates the status line when called it must not be called
+ * before the UI is ready.
+ */
+static int cui_server_wait(void *arg)
+{
+       struct cui *cui = cui_from_arg(arg);
+
+       if (cui->client) {
+               pb_debug("We already have a server!\n");
+               return 0;
+       }
+
+       /* We haven't yet connected to the server */
+       pb_log("Trying to connect...\n");
+       cui->client = discover_client_init(cui->waitset,
+                       &cui_client_ops, cui);
+
+       if (!cui->client) {
+               waiter_register_timeout(cui->waitset, 1000, cui_server_wait,
+                                       cui);
+               nc_scr_status_printf(cui->current,
+                                    "Info: Waiting for device discovery");
+       } else {
+               nc_scr_status_printf(cui->current,
+                                    "Info: Connected to pb-discover!");
+               talloc_steal(cui, cui->client);
+
+               if (cui->has_input) {
+                       pb_log("Aborting default boot on pb-discover connect\n");
+                       discover_client_cancel_default(cui->client);
+               }
+       }
+
+       return 0;
+}
+
 /**
  * cui_init - Setup the cui instance.
  * @platform_info: A value for the struct cui platform_info member.
@@ -895,7 +1031,7 @@ static struct discover_client_ops cui_client_ops = {
  */
 
 struct cui *cui_init(void* platform_info,
-       int (*js_map)(const struct js_event *e), int start_deamon)
+       int (*js_map)(const struct js_event *e), int start_daemon, int timeout)
 {
        struct cui *cui;
        unsigned int i;
@@ -910,13 +1046,14 @@ struct cui *cui_init(void* platform_info,
        cui->c_sig = pb_cui_sig;
        cui->platform_info = platform_info;
        cui->waitset = waitset_create(cui);
+       cui->statuslog = statuslog_init(cui);
 
        process_init(cui, cui->waitset, false);
 
        /* Loop here for scripts that just started the server. */
 
 retry_start:
-       for (i = start_deamon ? 2 : 10; i; i--) {
+       for (i = start_daemon ? 2 : 15; i && timeout; i--) {
                cui->client = discover_client_init(cui->waitset,
                                &cui_client_ops, cui);
                if (cui->client || !i)
@@ -925,10 +1062,10 @@ retry_start:
                sleep(1);
        }
 
-       if (!cui->client && start_deamon) {
+       if (!cui->client && start_daemon) {
                int result;
 
-               start_deamon = 0;
+               start_daemon = 0;
 
                result = pb_start_daemon(cui);
 
@@ -943,7 +1080,12 @@ retry_start:
                goto fail_client_init;
        }
 
-       if (!cui->client) {
+       if (!cui->client && !timeout) {
+               /* Have the first timeout fire immediately so we can check
+                * for the server as soon as the UI is ready */
+               waiter_register_timeout(cui->waitset, 0,
+                                       cui_server_wait, cui);
+       } else if (!cui->client) {
                pb_log("%s: discover_client_init failed.\n", __func__);
                fprintf(stderr, _("%s: error: discover_client_init failed.\n"),
                        __func__);
@@ -992,6 +1134,8 @@ fail_alloc:
 
 int cui_run(struct cui *cui)
 {
+       pid_t pid;
+
        assert(main);
 
        cui->current = &cui->main->scr;
@@ -1018,5 +1162,16 @@ int cui_run(struct cui *cui)
 
        cui_atexit();
 
+       if (!cui->client) {
+               /* Fork a child to tell the server to cancel autoboot */
+               pid = fork();
+               if (!pid) {
+                       cui_server_wait_on_exit(cui);
+                       exit(EXIT_SUCCESS);
+               }
+               if (pid < 0)
+                       pb_log("Failed to fork child on exit: %m\n");
+       }
+
        return cui->abort ? 0 : -1;
 }