diff options
-rw-r--r-- | ui/common/discover-client.c | 48 | ||||
-rw-r--r-- | ui/common/discover-client.h | 10 | ||||
-rw-r--r-- | ui/ncurses/Makefile.am | 7 | ||||
-rw-r--r-- | ui/ncurses/nc-cui.c | 411 | ||||
-rw-r--r-- | ui/ncurses/nc-cui.h | 13 | ||||
-rw-r--r-- | ui/ncurses/nc-menu.c | 30 | ||||
-rw-r--r-- | ui/ncurses/nc-menu.h | 1 | ||||
-rw-r--r-- | ui/ncurses/nc-plugin-help.c | 7 | ||||
-rw-r--r-- | ui/ncurses/nc-plugin-menu-help.c | 7 | ||||
-rw-r--r-- | ui/ncurses/nc-plugin.c | 418 | ||||
-rw-r--r-- | ui/ncurses/nc-plugin.h | 34 | ||||
-rw-r--r-- | ui/ncurses/nc-scr.h | 1 |
12 files changed, 941 insertions, 46 deletions
diff --git a/ui/common/discover-client.c b/ui/common/discover-client.c index dce74f9..8ad4611 100644 --- a/ui/common/discover-client.c +++ b/ui/common/discover-client.c @@ -112,6 +112,21 @@ static void device_remove(struct discover_client *client, const char *id) talloc_free(device); } +static void plugin_option_add(struct discover_client *client, + struct plugin_option *opt) +{ + talloc_steal(client, opt); + + if (client->ops.plugin_option_add) + client->ops.plugin_option_add(opt, client->ops.cb_arg); +} + +static void plugins_remove(struct discover_client *client) +{ + if (client->ops.plugins_remove) + client->ops.plugins_remove(client->ops.cb_arg); +} + void discover_client_enumerate(struct discover_client *client) { struct boot_option *opt; @@ -155,6 +170,7 @@ static int discover_client_process(void *arg) { struct discover_client *client = arg; struct pb_protocol_message *message; + struct plugin_option *p_opt; struct system_info *sysinfo; struct boot_option *opt; struct status *status; @@ -235,6 +251,20 @@ static int discover_client_process(void *arg) } update_config(client, config); break; + case PB_PROTOCOL_ACTION_PLUGIN_OPTION_ADD: + p_opt = talloc_zero(ctx, struct plugin_option); + + rc = pb_protocol_deserialise_plugin_option(p_opt, message); + if (rc) { + pb_log("%s: no plugin_option?\n", __func__); + goto out; + } + + plugin_option_add(client, p_opt); + break; + case PB_PROTOCOL_ACTION_PLUGINS_REMOVE: + plugins_remove(client); + break; default: pb_log("%s: unknown action %d\n", __func__, message->action); } @@ -404,3 +434,21 @@ int discover_client_send_url(struct discover_client *client, return pb_protocol_write_message(client->fd, message); } + +int discover_client_send_plugin_install(struct discover_client *client, + char *file) +{ + struct pb_protocol_message *message; + int len; + + len = pb_protocol_url_len(file); + + message = pb_protocol_create_message(client, + PB_PROTOCOL_ACTION_PLUGIN_INSTALL, len); + if (!message) + return -1; + + pb_protocol_serialise_url(file, message->payload, len); + + return pb_protocol_write_message(client->fd, message); +} diff --git a/ui/common/discover-client.h b/ui/common/discover-client.h index 95a5d9e..7224691 100644 --- a/ui/common/discover-client.h +++ b/ui/common/discover-client.h @@ -14,6 +14,11 @@ struct pb_boot_data { char *args_sig_file; }; +struct pb_plugin_data { + char *plugin_file; + struct plugin_option *opt; +}; + /** * struct discover_client_ops - Application supplied client info. * @device_add: PB_PROTOCOL_ACTION_ADD event callback. @@ -35,6 +40,8 @@ struct discover_client_ops { int (*boot_option_add)(struct device *dev, struct boot_option *option, void *arg); void (*device_remove)(struct device *device, void *arg); + int (*plugin_option_add)(struct plugin_option *option, void *arg); + int (*plugins_remove)(void *arg); void (*update_status)(struct status *status, void *arg); void (*update_sysinfo)(struct system_info *sysinfo, void *arg); void (*update_config)(struct config *sysinfo, void *arg); @@ -91,5 +98,8 @@ void discover_client_enumerate(struct discover_client *client); /* Send url to config to the discover server */ int discover_client_send_url(struct discover_client *client, char *url); +/* Send plugin file path to discover server to install */ +int discover_client_send_plugin_install(struct discover_client *client, + char *file); #endif diff --git a/ui/ncurses/Makefile.am b/ui/ncurses/Makefile.am index feec51d..40e11b8 100644 --- a/ui/ncurses/Makefile.am +++ b/ui/ncurses/Makefile.am @@ -51,8 +51,11 @@ ui_ncurses_libpbnc_la_SOURCES = \ ui/ncurses/nc-subset.c \ ui/ncurses/nc-subset.h \ ui/ncurses/nc-statuslog.c \ - ui/ncurses/nc-statuslog.h - + ui/ncurses/nc-statuslog.h \ + ui/ncurses/nc-plugin.c \ + ui/ncurses/nc-plugin.h \ + ui/ncurses/nc-plugin-help.c \ + ui/ncurses/nc-plugin-menu-help.c sbin_PROGRAMS += ui/ncurses/petitboot-nc diff --git a/ui/ncurses/nc-cui.c b/ui/ncurses/nc-cui.c index 84e4bf0..46b7839 100644 --- a/ui/ncurses/nc-cui.c +++ b/ui/ncurses/nc-cui.c @@ -44,12 +44,15 @@ #include "nc-helpscreen.h" #include "nc-statuslog.h" #include "nc-subset.h" +#include "nc-plugin.h" extern const struct help_text main_menu_help_text; +extern const struct help_text plugin_menu_help_text; static bool cui_detached = false; static struct pmenu *main_menu_init(struct cui *cui); +static struct pmenu *plugin_menu_init(struct cui *cui); static bool lockdown_active(void) { @@ -376,12 +379,37 @@ static void cui_add_url_exit(struct cui *cui) cui->add_url_screen = NULL; } +static void cui_plugin_exit(struct cui *cui) +{ + cui_set_current(cui, &cui->plugin_menu->scr); + talloc_free(cui->plugin_screen); + cui->plugin_screen = NULL; +} + +static void cui_plugin_menu_exit(struct pmenu *menu) +{ + struct cui *cui = cui_from_pmenu(menu); + cui_set_current(cui, &cui->main->scr); +} + void cui_show_add_url(struct cui *cui) { cui->add_url_screen = add_url_screen_init(cui, cui_add_url_exit); cui_set_current(cui, add_url_screen_scr(cui->add_url_screen)); } +void cui_show_plugin_menu(struct cui *cui) +{ + cui_set_current(cui, &cui->plugin_menu->scr); +} + +void cui_show_plugin(struct pmenu_item *item) +{ + struct cui *cui = cui_from_item(item); + cui->plugin_screen = plugin_screen_init(cui, item, cui_plugin_exit); + cui_set_current(cui, plugin_screen_scr(cui->plugin_screen)); +} + static void cui_help_exit(struct cui *cui) { cui_set_current(cui, help_screen_return_scr(cui->help_screen)); @@ -549,6 +577,7 @@ static void cui_handle_resize(struct cui *cui) * * Creates menu_items for all the device boot_options and inserts those * menu_items into the main menu. Redraws the main menu if it is active. + * If a 'plugin' type boot_option appears the plugin menu is updated instead. */ static int cui_boot_option_add(struct device *dev, struct boot_option *opt, @@ -560,86 +589,130 @@ static int cui_boot_option_add(struct device *dev, struct boot_option *opt, const char *tab = " "; unsigned int insert_pt; int result, rows, cols; + struct pmenu *menu; + bool plugin_option; ITEM *selected; char *name; + plugin_option = opt->type == DISCOVER_PLUGIN_OPTION; + menu = plugin_option ? cui->plugin_menu : cui->main; + pb_debug("%s: %p %s\n", __func__, opt, opt->id); - selected = current_item(cui->main->ncm); - menu_format(cui->main->ncm, &rows, &cols); + selected = current_item(menu->ncm); + menu_format(menu->ncm, &rows, &cols); if (cui->current == &cui->main->scr) nc_scr_unpost(cui->current); + if (plugin_option && cui->current == &cui->plugin_menu->scr) + nc_scr_unpost(cui->current); /* Check if the boot device is new */ - dev_hdr = pmenu_find_device(cui->main, dev, opt); + dev_hdr = pmenu_find_device(menu, dev, opt); /* All actual boot entries are 'tabbed' across */ - name = talloc_asprintf(cui->main, "%s%s", + name = talloc_asprintf(menu, "%s%s", tab, opt->name ? : "Unknown Name"); /* Save the item in opt->ui_info for cui_device_remove() */ - opt->ui_info = i = pmenu_item_create(cui->main, name); + opt->ui_info = i = pmenu_item_create(menu, name); talloc_free(name); if (!i) return -1; - i->on_edit = cui_item_edit; - i->on_execute = cui_boot; - i->data = cod = talloc(i, struct cui_opt_data); + if (plugin_option) { + i->on_edit = NULL; + i->on_execute = plugin_install_plugin; + } else { + i->on_edit = cui_item_edit; + i->on_execute = cui_boot; + } + i->data = cod = talloc(i, struct cui_opt_data); cod->opt = opt; cod->dev = dev; cod->opt_hash = pb_opt_hash(dev, opt); cod->name = opt->name; - cod->bd = talloc(i, struct pb_boot_data); - cod->bd->image = talloc_strdup(cod->bd, opt->boot_image_file); - 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); + if (plugin_option) { + cod->pd = talloc(i, struct pb_plugin_data); + cod->pd->plugin_file = talloc_strdup(cod, + opt->boot_image_file); + } else { + cod->bd = talloc(i, struct pb_boot_data); + cod->bd->image = talloc_strdup(cod->bd, opt->boot_image_file); + 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); + result = set_menu_items(menu->ncm, NULL); if (result) pb_log("%s: set_menu_items failed: %d\n", __func__, result); /* Insert new items at insert_pt. */ if (dev_hdr) { - insert_pt = pmenu_grow(cui->main, 2); - pmenu_item_insert(cui->main, dev_hdr, insert_pt); + insert_pt = pmenu_grow(menu, 2); + pmenu_item_insert(menu, dev_hdr, insert_pt); pb_log("%s: adding new device hierarchy %s\n", - __func__,opt->device_id); - pmenu_item_insert(cui->main, i, insert_pt+1); + __func__, opt->device_id); + pmenu_item_insert(menu, i, insert_pt+1); } else { - insert_pt = pmenu_grow(cui->main, 1); - pmenu_item_add(cui->main, i, insert_pt); + insert_pt = pmenu_grow(menu, 1); + pmenu_item_add(menu, i, insert_pt); } - pb_log("%s: adding opt '%s'\n", __func__, cod->name); - 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); + if (plugin_option) { + pb_log("%s: adding plugin '%s'\n", __func__, cod->name); + pb_log(" file '%s'\n", cod->pd->plugin_file); + } else { + pb_log("%s: adding opt '%s'\n", __func__, cod->name); + 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); + } + + /* Update the plugin menu label if needed */ + if (plugin_option) { + struct pmenu_item *item; + unsigned int j; + result = set_menu_items(cui->main->ncm, NULL); + for (j = 0 ; j < cui->main->item_count; j++) { + item = item_userptr(cui->main->items[j]); + if (strncmp(item->nci->name.str, "Plugins", strlen("Plugins"))) + continue; + cui->n_plugins++; + char *label = talloc_asprintf(item, "Plugins (%u)", + cui->n_plugins); + pmenu_item_update(item, label); + talloc_free(label); + break; + } + result = set_menu_items(cui->main->ncm, cui->main->items); + if (result) + pb_log("%s: set_menu_items failed: %d\n", __func__, result); + } /* Re-attach the items array. */ - result = set_menu_items(cui->main->ncm, cui->main->items); + result = set_menu_items(menu->ncm, menu->items); if (result) pb_log("%s: set_menu_items failed: %d\n", __func__, result); if (0) { pb_log("%s\n", __func__); - pmenu_dump_items(cui->main->items, - item_count(cui->main->ncm) + 1); + pmenu_dump_items(menu->items, + item_count(menu->ncm) + 1); } if (!item_visible(selected)) { int idx, top; - top = top_row(cui->main->ncm); + top = top_row(menu->ncm); idx = item_index(selected); /* If our index is above the current top row, align @@ -647,11 +720,13 @@ static int cui_boot_option_add(struct device *dev, struct boot_option *opt, * bottom */ top = top < idx ? idx - rows + 1 : idx; - set_top_row(cui->main->ncm, top); - set_current_item(cui->main->ncm, selected); + set_top_row(menu->ncm, top); + set_current_item(menu->ncm, selected); } - if (cui->current == &cui->main->scr) + if (cui->current == &menu->scr) + nc_scr_post(cui->current); + if (plugin_option && cui->current == &cui->main->scr) nc_scr_post(cui->current); return 0; @@ -663,7 +738,6 @@ static int cui_boot_option_add(struct device *dev, struct boot_option *opt, * Removes all the menu_items for the device from the main menu and redraws the * main menu if it is active. */ - static void cui_device_remove(struct device *dev, void *arg) { struct cui *cui = cui_from_arg(arg); @@ -676,10 +750,13 @@ static void cui_device_remove(struct device *dev, void *arg) if (cui->current == &cui->main->scr) nc_scr_unpost(cui->current); + if (cui->current == &cui->plugin_menu->scr) + nc_scr_unpost(cui->current); /* This disconnects items array from menu. */ result = set_menu_items(cui->main->ncm, NULL); + result |= set_menu_items(cui->plugin_menu->ncm, NULL); if (result) pb_log("%s: set_menu_items failed: %d\n", __func__, result); @@ -688,7 +765,10 @@ static void cui_device_remove(struct device *dev, void *arg) struct pmenu_item *item = pmenu_item_from_arg(opt->ui_info); assert(pb_protocol_device_cmp(dev, cod_from_item(item)->dev)); - pmenu_remove(cui->main, item); + if (opt->type == DISCOVER_PLUGIN_OPTION) + pmenu_remove(cui->plugin_menu, item); + else + pmenu_remove(cui->main, item); } /* Manually remove remaining device hierarchy item */ @@ -701,12 +781,23 @@ static void cui_device_remove(struct device *dev, void *arg) if (data && data->dev && data->dev == dev) pmenu_remove(cui->main,item); } + /* Look in plugins menu too */ + for (i=0; i < cui->plugin_menu->item_count; i++) { + struct pmenu_item *item = item_userptr(cui->plugin_menu->items[i]); + if (!item || !item->data ) + continue; + + struct cui_opt_data *data = item->data; + if (data && data->dev && data->dev == dev) + pmenu_remove(cui->plugin_menu,item); + } /* Re-attach the items array. */ result = set_menu_items(cui->main->ncm, cui->main->items); + result |= set_menu_items(cui->plugin_menu->ncm, cui->plugin_menu->items); - /* Move cursor to 'Exit' menu entry */ + /* Move cursor to 'Exit' menu entry for the main menu.. */ menu_format(cui->main->ncm, &rows, &cols); last = cui->main->item_count - 1; set_current_item(cui->main->ncm, cui->main->items[last]); @@ -715,6 +806,15 @@ static void cui_device_remove(struct device *dev, void *arg) set_top_row(cui->main->ncm, top); } + /* ..and the plugin menu */ + menu_format(cui->plugin_menu->ncm, &rows, &cols); + last = cui->plugin_menu->item_count - 1; + set_current_item(cui->plugin_menu->ncm, cui->plugin_menu->items[last]); + if (!item_visible(cui->plugin_menu->items[last])) { + top = last < rows ? 0 : last - rows + 1; + set_top_row(cui->plugin_menu->ncm, top); + } + if (result) pb_log("%s: set_menu_items failed: %d\n", __func__, result); @@ -726,6 +826,8 @@ static void cui_device_remove(struct device *dev, void *arg) if (cui->current == &cui->main->scr) nc_scr_post(cui->current); + if (cui->current == &cui->plugin_menu->scr) + nc_scr_post(cui->current); } static void cui_update_status(struct status *status, void *arg) @@ -739,6 +841,156 @@ static void cui_update_status(struct status *status, void *arg) nc_scr_status_printf(cui->current, "%s", status->message); } +/* + * Handle a new installed plugin option and update its associated + * (uninstalled) menu item if it exists. + */ +static int cui_plugin_option_add(struct plugin_option *opt, void *arg) +{ + struct cui_opt_data *cod; + struct cui *cui = cui_from_arg(arg); + struct pmenu_item *item = NULL; + struct boot_option *dummy_opt; + struct device *dev; + unsigned int i; + int result; + +fallback: + /* Find uninstalled plugin by matching on plugin_file */ + for (i = 0; i < cui->plugin_menu->item_count; i++) { + item = item_userptr(cui->plugin_menu->items[i]); + if (!item) + continue; + cod = cod_from_item(item); + if (!cod || !cod->pd) + continue; + if (strncmp(cod->pd->plugin_file, opt->plugin_file, + strlen(cod->pd->plugin_file)) == 0) + break; + } + + /* + * If pb-plugin was run manually there may not be an associated + * plugin-type boot_option. Pass a fake device and option to + * cui_boot_option_add() so we have an item to work with. + */ + if (!item || i >= cui->plugin_menu->item_count) { + pb_log("New plugin option %s doesn't have a source item\n", + opt->id); + dev = talloc_zero(cui, struct device); + dev->id = dev->name = talloc_asprintf(dev, "(unknown)"); + dev->type = DEVICE_TYPE_UNKNOWN; + dummy_opt = talloc_zero(cui, struct boot_option); + dummy_opt->device_id = talloc_strdup(dummy_opt, dev->id); + dummy_opt->id = dummy_opt->name = talloc_asprintf(dummy_opt, "dummy"); + dummy_opt->boot_image_file = talloc_strdup(dummy_opt, opt->plugin_file); + dummy_opt->type = DISCOVER_PLUGIN_OPTION; + cui_boot_option_add(dev, dummy_opt, cui); + goto fallback; + } + + /* + * If this option was faked above move the context under + * the item so it is cleaned up later in cui_plugins_remove(). + */ + if (strncmp(cod->opt->id, "dummy", strlen("dummy") == 0 && + cod->dev->type == DEVICE_TYPE_UNKNOWN)) { + talloc_steal(item, cod->dev); + talloc_steal(item, cod->opt); + } + + talloc_free(cod->name); + /* Name is still tabbed across */ + cod->name = talloc_asprintf(cod, " %s [installed]", opt->name); + + cod->pd->opt = opt; + item->on_execute = NULL; + item->on_edit = cui_show_plugin; + + if (cui->current == &cui->plugin_menu->scr) + nc_scr_unpost(cui->current); + + /* This disconnects items array from menu. */ + result = set_menu_items(cui->plugin_menu->ncm, NULL); + + if (result == E_OK) + pmenu_item_update(item, cod->name); + + /* Re-attach the items array. */ + result = set_menu_items(cui->plugin_menu->ncm, cui->plugin_menu->items); + + if (cui->current == &cui->plugin_menu->scr) + nc_scr_post(cui->current); + + return result; +} + +/* + * Most plugin menu items will be removed via cui_device_remove(). However if + * pb-plugin has been run manually it is possible that there a plugin items + * not associated with a device - remove them here. + */ +static int cui_plugins_remove(void *arg) +{ + struct cui *cui = cui_from_arg(arg); + struct pmenu_item *item = NULL; + struct cui_opt_data *cod; + unsigned int i = 0; + + pb_debug("%s\n", __func__); + + if (cui->current == &cui->plugin_menu->scr) + nc_scr_unpost(cui->current); + if (cui->current == &cui->main->scr) + nc_scr_unpost(cui->current); + + /* This disconnects items array from menu. */ + set_menu_items(cui->plugin_menu->ncm, NULL); + + while (i < cui->plugin_menu->item_count) { + item = item_userptr(cui->plugin_menu->items[i]); + if (!item || !item->data) { + i++; + continue; + } + cod = cod_from_item(item); + if (!cod->opt && !cod->dev) { + i++; + continue; + } + + pmenu_remove(cui->plugin_menu, item); + /* plugin_menu entries will shift, jump to bottom to make sure + * we remove all plugin option items */ + i = 0; + } + + /* Re-attach the items array. */ + set_menu_items(cui->plugin_menu->ncm, cui->plugin_menu->items); + + set_menu_items(cui->main->ncm, NULL); + for (i = 0; i < cui->main->item_count; i++) { + item = item_userptr(cui->main->items[i]); + if (strncmp(item->nci->name.str, "Plugins", strlen("Plugins"))) + continue; + cui->n_plugins = 0; + pmenu_item_update(item, "Plugins (0)"); + break; + } + + set_menu_items(cui->main->ncm, cui->main->items); + + if (cui->current == &cui->main->scr) + nc_scr_post(cui->current); + + /* If we're currently in a plugin screen jump back to the plugin menu */ + if (cui->plugin_screen && + cui->current == plugin_screen_scr(cui->plugin_screen)) + cui_plugin_exit(cui); + + return 0; +} + static void cui_update_mm_title(struct cui *cui) { struct nc_frame *frame = &cui->main->scr.frame; @@ -752,6 +1004,18 @@ static void cui_update_mm_title(struct cui *cui) if (cui->current == &cui->main->scr) nc_scr_post(cui->current); + + frame = &cui->plugin_menu->scr.frame; + + talloc_free(frame->rtitle); + + frame->rtitle = talloc_strdup(cui->main, cui->sysinfo->type); + if (cui->sysinfo->identifier) + frame->rtitle = talloc_asprintf_append(frame->rtitle, + " %s", cui->sysinfo->identifier); + + if (cui->current == &cui->main->scr) + nc_scr_post(cui->current); } static void cui_update_sysinfo(struct system_info *sysinfo, void *arg) @@ -790,12 +1054,14 @@ static void cui_update_language(struct cui *cui, char *lang) setlocale(LC_ALL, lang); /* we'll need to update the menu: drop all items and repopulate */ - repost_menu = cui->current == &cui->main->scr; + repost_menu = cui->current == &cui->main->scr || + cui->current == &cui->plugin_menu->scr; if (repost_menu) nc_scr_unpost(cui->current); talloc_free(cui->main); cui->main = main_menu_init(cui); + cui->plugin_menu = plugin_menu_init(cui); if (repost_menu) { cui->current = &cui->main->scr; @@ -835,6 +1101,11 @@ int cui_send_url(struct cui *cui, char * url) return discover_client_send_url(cui->client, url); } +int cui_send_plugin_install(struct cui *cui, char *file) +{ + return discover_client_send_plugin_install(cui->client, file); +} + void cui_send_reinit(struct cui *cui) { discover_client_send_reinit(cui->client); @@ -878,6 +1149,13 @@ static int menu_add_url_execute(struct pmenu_item *item) return 0; } +static int menu_plugin_execute(struct pmenu_item *item) +{ + if (cui_from_item(item)->client) + cui_show_plugin_menu(cui_from_item(item)); + return 0; +} + /** * pb_mm_init - Setup the main menu instance. */ @@ -888,7 +1166,7 @@ static struct pmenu *main_menu_init(struct cui *cui) int result; bool lockdown = lockdown_active(); - m = pmenu_init(cui, 8, cui_on_exit); + m = pmenu_init(cui, 9, cui_on_exit); if (!m) { pb_log("%s: failed\n", __func__); return NULL; @@ -934,12 +1212,16 @@ static struct pmenu *main_menu_init(struct cui *cui) i->on_execute = menu_add_url_execute; pmenu_item_insert(m, i, 6); + i = pmenu_item_create(m, _("Plugins (0)")); + i->on_execute = menu_plugin_execute; + pmenu_item_insert(m, i, 7); + 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, 7); + pmenu_item_insert(m, i, 8); result = pmenu_setup(m); @@ -963,10 +1245,57 @@ fail_setup: return NULL; } +/* + * plugin_menu_init: Set up the plugin menu instance + */ +static struct pmenu *plugin_menu_init(struct cui *cui) +{ + struct pmenu_item *i; + struct pmenu *m; + int result; + + m = pmenu_init(cui, 2, cui_plugin_menu_exit); + m->on_new = cui_item_new; + m->scr.frame.ltitle = talloc_asprintf(m, _("Petitboot Plugins")); + m->scr.frame.rtitle = talloc_asprintf(m, NULL); + m->scr.frame.help = talloc_strdup(m, + _("Enter=install, e=details, x=exit, h=help")); + m->scr.frame.status = talloc_asprintf(m, + _("Available Petitboot Plugins")); + + /* add a separator */ + i = pmenu_item_create(m, " "); + item_opts_off(i->nci, O_SELECTABLE); + pmenu_item_insert(m, i, 0); + + i = pmenu_item_create(m, ("Return to Main Menu")); + i->on_execute = pmenu_exit_cb; + pmenu_item_insert(m, i, 1); + + result = pmenu_setup(m); + + if (result) { + pb_log("%s:%d: pmenu_setup failed: %s\n", __func__, __LINE__, + strerror(errno)); + goto fail_setup; + } + + m->help_title = _("plugin menu"); + m->help_text = &plugin_menu_help_text; + + return m; + +fail_setup: + talloc_free(m); + return NULL; +} + static struct discover_client_ops cui_client_ops = { .device_add = NULL, .boot_option_add = cui_boot_option_add, .device_remove = cui_device_remove, + .plugin_option_add = cui_plugin_option_add, + .plugins_remove = cui_plugins_remove, .update_status = cui_update_status, .update_sysinfo = cui_update_sysinfo, .update_config = cui_update_config, @@ -1121,6 +1450,10 @@ retry_start: if (!cui->main) goto fail_client_init; + cui->plugin_menu = plugin_menu_init(cui); + if (!cui->plugin_menu) + goto fail_client_init; + waiter_register_io(cui->waitset, STDIN_FILENO, WAIT_IN, cui_process_key, cui); diff --git a/ui/ncurses/nc-cui.h b/ui/ncurses/nc-cui.h index d8a5f8b..b310f4a 100644 --- a/ui/ncurses/nc-cui.h +++ b/ui/ncurses/nc-cui.h @@ -26,8 +26,11 @@ #include "nc-helpscreen.h" struct cui_opt_data { - const char *name; - struct pb_boot_data *bd; + char *name; + union { + struct pb_boot_data *bd; + struct pb_plugin_data *pd; + }; /* optional data */ const struct device *dev; @@ -53,6 +56,8 @@ struct cui { sig_atomic_t resize; struct nc_scr *current; struct pmenu *main; + struct pmenu *plugin_menu; + unsigned int n_plugins; struct waitset *waitset; struct discover_client *client; struct system_info *sysinfo; @@ -61,6 +66,7 @@ struct cui { struct config *config; struct config_screen *config_screen; struct add_url_screen *add_url_screen; + struct plugin_screen *plugin_screen; struct boot_editor *boot_editor; struct lang_screen *lang_screen; struct help_screen *help_screen; @@ -88,8 +94,11 @@ void cui_show_help(struct cui *cui, const char *title, void cui_show_subset(struct cui *cui, const char *title, void *arg); void cui_show_add_url(struct cui *cui); +void cui_show_plugin(struct pmenu_item *item); +void cui_show_plugin_menu(struct cui *cui); int cui_send_config(struct cui *cui, struct config *config); int cui_send_url(struct cui *cui, char *url); +int cui_send_plugin_install(struct cui *cui, char *file); void cui_send_reinit(struct cui *cui); /* convenience routines */ diff --git a/ui/ncurses/nc-menu.c b/ui/ncurses/nc-menu.c index 90a2c0a..54d82ff 100644 --- a/ui/ncurses/nc-menu.c +++ b/ui/ncurses/nc-menu.c @@ -124,6 +124,31 @@ static const char *pmenu_item_label(struct pmenu_item *item, const char *name) } /** + * pmenu_item_update - Update the label of an existing pmenu_item. + * + * The item array must be disconnected prior to calling. + */ +int pmenu_item_update(struct pmenu_item *item, const char *name) +{ + const char *label; + ITEM *i; + + if (!item || !item->nci) + return -1; + + label = pmenu_item_label(item, name); + + if (!label) + return -1; + + i = item->nci; + i->name.str = label; + i->name.length = strncols(label); + + return 0; +} + +/** * pmenu_item_create - Allocate and initialize a new pmenu_item instance. * * Returns a pointer the the initialized struct pmenu_item instance or NULL @@ -230,7 +255,7 @@ struct pmenu_item *pmenu_find_device(struct pmenu *menu, struct device *dev, for (i = 0; i < menu->item_count; i++) { item = item_userptr(menu->items[i]); - cod = item->data; + cod = cod_from_item(item); /* boot entries will have opt defined */ if (!cod || cod->opt) continue; @@ -314,10 +339,9 @@ struct pmenu_item *pmenu_find_device(struct pmenu *menu, struct device *dev, /* We identify dev_hdr items as having a valid c->name, * but a NULL c->opt */ - cod = talloc(dev_hdr, struct cui_opt_data); + cod = talloc_zero(dev_hdr, struct cui_opt_data); cod->name = talloc_strdup(dev_hdr, opt->device_id); cod->dev = dev; - cod->opt = NULL; dev_hdr->data = cod; pb_debug("%s: returning %s\n",__func__,cod->name); diff --git a/ui/ncurses/nc-menu.h b/ui/ncurses/nc-menu.h index 136bf66..54f83af 100644 --- a/ui/ncurses/nc-menu.h +++ b/ui/ncurses/nc-menu.h @@ -53,6 +53,7 @@ struct pmenu_item { int (*on_execute)(struct pmenu_item *item); }; +int pmenu_item_update(struct pmenu_item *item, const char *name); struct pmenu_item *pmenu_item_create(struct pmenu *menu, const char *name); struct pmenu_item *pmenu_find_device(struct pmenu *menu, struct device *dev, struct boot_option *opt); diff --git a/ui/ncurses/nc-plugin-help.c b/ui/ncurses/nc-plugin-help.c new file mode 100644 index 0000000..4af3e10 --- /dev/null +++ b/ui/ncurses/nc-plugin-help.c @@ -0,0 +1,7 @@ +#include "nc-helpscreen.h" + +struct help_text plugin_help_text = define_help_text("\ +This screen lists the details and available commands of an installed plugin.\n" +"To run a plugin command choose it in the list and select the \"Run\" button. \ +The Petitboot UI will temporarily exit to run the command, then return to \ +this screen."); diff --git a/ui/ncurses/nc-plugin-menu-help.c b/ui/ncurses/nc-plugin-menu-help.c new file mode 100644 index 0000000..3281e33 --- /dev/null +++ b/ui/ncurses/nc-plugin-menu-help.c @@ -0,0 +1,7 @@ +#include "nc-helpscreen.h" + +struct help_text plugin_menu_help_text = define_help_text("\ +Plugins discovered by Petitboot are listed in this menu.\n" +"Press Enter to install the selected plugin. Once installed the plugin details \ +can be seen by pressing 'e'. From the detailed view plugin commands can also \ +be run."); diff --git a/ui/ncurses/nc-plugin.c b/ui/ncurses/nc-plugin.c new file mode 100644 index 0000000..ad8210f --- /dev/null +++ b/ui/ncurses/nc-plugin.c @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2017 IBM Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <talloc/talloc.h> +#include <types/types.h> +#include <i18n/i18n.h> +#include <log/log.h> + +#include "nc-cui.h" +#include "nc-plugin.h" +#include "nc-widgets.h" +#include "nc-menu.h" +#include "ui/common/discover-client.h" +#include "process/process.h" +#include "system/system.h" + +#define N_FIELDS 15 + +extern const struct help_text plugin_help_text; + +struct plugin_screen { + struct nc_scr scr; + struct cui *cui; + struct nc_widgetset *widgetset; + WINDOW *pad; + + bool exit; + bool show_help; + bool need_redraw; + void (*on_exit)(struct cui *); + + int label_x; + int field_x; + int scroll_y; + + const struct plugin_option *opt; + + struct { + struct nc_widget_label *id_l; + struct nc_widget_label *id_f; + struct nc_widget_label *name_l; + struct nc_widget_label *name_f; + struct nc_widget_label *vendor_l; + struct nc_widget_label *vendor_f; + struct nc_widget_label *vendor_id_l; + struct nc_widget_label *vendor_id_f; + struct nc_widget_label *version_l; + struct nc_widget_label *version_f; + struct nc_widget_label *date_l; + struct nc_widget_label *date_f; + + struct nc_widget_label *commands_l; + struct nc_widget_select *commands_f; + + struct nc_widget_button *run_b; + } widgets; +}; + +static void plugin_screen_draw(struct plugin_screen *screen, + struct pmenu_item *item); + +static struct plugin_screen *plugin_screen_from_scr(struct nc_scr *scr) +{ + struct plugin_screen *plugin_screen; + + assert(scr->sig == pb_plugin_screen_sig); + plugin_screen = (struct plugin_screen *) + ((char *)scr - (size_t)&((struct plugin_screen *)0)->scr); + assert(plugin_screen->scr.sig == pb_plugin_screen_sig); + return plugin_screen; +} + +struct nc_scr *plugin_screen_scr(struct plugin_screen *screen) +{ + return &screen->scr; +} + +static void pad_refresh(struct plugin_screen *screen) +{ + int y, x, rows, cols; + + getmaxyx(screen->scr.sub_ncw, rows, cols); + getbegyx(screen->scr.sub_ncw, y, x); + + prefresh(screen->pad, screen->scroll_y, 0, y, x, rows, cols); +} + +static void plugin_screen_widget_focus(struct nc_widget *widget, void *arg) +{ + struct plugin_screen *screen = arg; + int w_y, w_height, w_focus, s_max, adjust; + + w_height = widget_height(widget); + w_focus = widget_focus_y(widget); + w_y = widget_y(widget) + w_focus; + s_max = getmaxy(screen->scr.sub_ncw) - 1; + + if (w_y < screen->scroll_y) + screen->scroll_y = w_y; + + else if (w_y + screen->scroll_y + 1 > s_max) { + /* Fit as much of the widget into the screen as possible */ + adjust = min(s_max - 1, w_height - w_focus); + if (w_y + adjust >= screen->scroll_y + s_max) + screen->scroll_y = max(0, 1 + w_y + adjust - s_max); + } else + return; + + pad_refresh(screen); +} + +static void plugin_screen_process_key(struct nc_scr *scr, int key) +{ + struct plugin_screen *screen = plugin_screen_from_scr(scr); + bool handled; + + handled = widgetset_process_key(screen->widgetset, key); + + if (!handled) { + pb_log("Not handled by widgetset\n"); + switch (key) { + case 'x': + case 27: /* esc */ + screen->exit = true; + break; + case 'h': + screen->show_help = true; + break; + } + } + + if (screen->exit) { + screen->on_exit(screen->cui); + + } else if (screen->show_help) { + screen->show_help = false; + screen->need_redraw = true; + cui_show_help(screen->cui, _("Petitboot Plugin"), + &plugin_help_text); + + } else if (handled) { + pad_refresh(screen); + } +} + +static int plugin_screen_post(struct nc_scr *scr) +{ + struct plugin_screen *screen = plugin_screen_from_scr(scr); + + widgetset_post(screen->widgetset); + + nc_scr_frame_draw(scr); + if (screen->need_redraw) { + redrawwin(scr->main_ncw); + screen->need_redraw = false; + } + wrefresh(screen->scr.main_ncw); + pad_refresh(screen); + return 0; +} + +static int plugin_screen_unpost(struct nc_scr *scr) +{ + widgetset_unpost(plugin_screen_from_scr(scr)->widgetset); + return 0; +} + +static void plugin_screen_resize(struct nc_scr *scr) +{ + /* FIXME: forms can't be resized, need to recreate here */ + plugin_screen_unpost(scr); + plugin_screen_post(scr); +} + +static void plugin_run_command(void *arg) +{ + struct plugin_screen *screen = arg; + char *cmd; + int i, result; + + i = widget_select_get_value(screen->widgets.commands_f); + /* pb-plugin copies all executables to the wrapper directory */ + cmd = talloc_asprintf(screen, "%s/%s", "/var/lib/pb-plugins/bin", + basename(screen->opt->executables[i])); + + if (!cmd) { + pb_log("nc-plugin: plugin option has missing command %d\n", i); + return; + } + + const char *argv[] = { + pb_system_apps.pb_exec, + cmd, + NULL + }; + + /* Drop our pad before running plugin */ + delwin(screen->pad); + screen->pad = NULL; + + result = cui_run_cmd(screen->cui, argv); + + if (result) + pb_log("Failed to run plugin command %s\n", cmd); + else + nc_scr_status_printf(screen->cui->current, _("Finished: %s"), cmd); + + plugin_screen_draw(screen, NULL); + + talloc_free(cmd); +} + +/* Call pb-plugin to install a plugin specified by plugin_file */ +int plugin_install_plugin(struct pmenu_item *item) +{ + struct cui *cui = cui_from_item(item); + struct cui_opt_data *cod = cod_from_item(item); + int rc; + + assert(cui->current == &cui->plugin_menu->scr); + + nc_scr_status_printf(cui->current, _("Installing plugin %s"), + cod->pd->plugin_file); + + rc = cui_send_plugin_install(cui, cod->pd->plugin_file); + + if (rc) { + pb_log("cui_send_plugin_install failed!\n"); + nc_scr_status_printf(cui->current, + _("Failed to send install request")); + } else + pb_debug("cui_send_plugin_install sent!\n"); + + return rc; +} + +static void plugin_screen_setup_widgets(struct plugin_screen *screen) +{ + const struct plugin_option *opt = screen->opt; + struct nc_widgetset *set = screen->widgetset; + unsigned int i; + + build_assert(sizeof(screen->widgets) / sizeof(struct widget *) + == N_FIELDS); + + screen->widgets.id_l = widget_new_label(set, 0, 0, _("ID:")); + screen->widgets.id_f = widget_new_label(set, 0, 0, opt->id); + screen->widgets.name_l = widget_new_label(set, 0, 0, _("Name:")); + screen->widgets.name_f = widget_new_label(set, 0, 0, opt->name); + screen->widgets.vendor_l = widget_new_label(set, 0, 0, _("Vendor:")); + screen->widgets.vendor_f = widget_new_label(set, 0, 0, opt->vendor); + screen->widgets.vendor_id_l = widget_new_label(set, 0, 0, + _("Vendor ID:")); + screen->widgets.vendor_id_f = widget_new_label(set, 0, 0, + opt->vendor_id); + screen->widgets.version_l = widget_new_label(set, 0, 0, _("Version:")); + screen->widgets.version_f = widget_new_label(set, 0, 0, + opt->version); + screen->widgets.date_l = widget_new_label(set, 0, 0, _("Date")); + screen->widgets.date_f = widget_new_label(set, 0, 0, opt->date); + + screen->widgets.commands_l = widget_new_label(set, 0, 0, + _("Commands:")); + screen->widgets.commands_f = widget_new_select(set, 0, 0, + COLS - screen->field_x - 1); + for (i = 0; i < opt->n_executables; i++) { + widget_select_add_option(screen->widgets.commands_f, i, + basename(opt->executables[i]), i == 0); + } + + screen->widgets.run_b = widget_new_button(set, 0, 0, 30, + _("Run selected command"), plugin_run_command, screen); +} + +static int layout_pair(struct plugin_screen *screen, int y, + struct nc_widget_label *label, + struct nc_widget *field) +{ + struct nc_widget *label_w = widget_label_base(label); + widget_move(label_w, y, screen->label_x); + widget_move(field, y, screen->field_x); + return max(widget_height(label_w), widget_height(field)); +} + +static void plugin_screen_layout_widgets(struct plugin_screen *screen) +{ + unsigned int y; + + /* list of details (static) */ + + y = 1; + + layout_pair(screen, y++, screen->widgets.id_l, + widget_label_base(screen->widgets.id_f)); + layout_pair(screen, y++, screen->widgets.name_l, + widget_label_base(screen->widgets.name_f)); + layout_pair(screen, y++, screen->widgets.vendor_l, + widget_label_base(screen->widgets.vendor_f)); + layout_pair(screen, y++, screen->widgets.vendor_id_l, + widget_label_base(screen->widgets.vendor_id_f)); + layout_pair(screen, y++, screen->widgets.version_l, + widget_label_base(screen->widgets.version_f)); + layout_pair(screen, y++, screen->widgets.date_l, + widget_label_base(screen->widgets.date_f)); + + y += 1; + + /* available commands */ + widget_move(widget_label_base(screen->widgets.commands_l), y, + screen->label_x); + widget_move(widget_select_base(screen->widgets.commands_f), y, + screen->field_x); + + y += 2; + + widget_move(widget_button_base(screen->widgets.run_b), y++, screen->field_x); + +} + +static void plugin_screen_draw(struct plugin_screen *screen, + struct pmenu_item *item) +{ + struct cui_opt_data *cod; + int height = N_FIELDS * 2; + bool repost = false; + + if (item) { + /* First init or update */ + cod = cod_from_item(item); + screen->opt = cod->pd->opt; + } + + if (!screen->pad || getmaxy(screen->pad) < height) { + if (screen->pad) + delwin(screen->pad); + screen->pad = newpad(height, COLS); + } + + if (screen->widgetset) { + widgetset_unpost(screen->widgetset); + talloc_free(screen->widgetset); + repost = true; + } + + screen->widgetset = widgetset_create(screen, screen->scr.main_ncw, + screen->pad); + widgetset_set_widget_focus(screen->widgetset, + plugin_screen_widget_focus, screen); + + plugin_screen_setup_widgets(screen); + plugin_screen_layout_widgets(screen); + + if (repost) + widgetset_post(screen->widgetset); +} + +static int plugin_screen_destroy(void *arg) +{ + struct plugin_screen *screen = arg; + if (screen->pad) + delwin(screen->pad); + return 0; +} + +struct plugin_screen *plugin_screen_init(struct cui *cui, + struct pmenu_item *item, + void (*on_exit)(struct cui *)) +{ + struct plugin_screen *screen = talloc_zero(cui, struct plugin_screen); + talloc_set_destructor(screen, plugin_screen_destroy); + + nc_scr_init(&screen->scr, pb_plugin_screen_sig, 0, + cui, plugin_screen_process_key, + plugin_screen_post, plugin_screen_unpost, + plugin_screen_resize); + + + screen->cui = cui; + screen->on_exit = on_exit; + screen->label_x = 2; + screen->field_x = 25; + + screen->scr.frame.ltitle = talloc_strdup(screen, + _("Petitboot Plugin")); + screen->scr.frame.rtitle = NULL; + screen->scr.frame.help = talloc_strdup(screen, + _("tab=next, shift+tab=previous, x=exit, h=help")); + nc_scr_frame_draw(&screen->scr); + + scrollok(screen->scr.sub_ncw, true); + + plugin_screen_draw(screen, item); + + return screen; +} + diff --git a/ui/ncurses/nc-plugin.h b/ui/ncurses/nc-plugin.h new file mode 100644 index 0000000..6dfd4ae --- /dev/null +++ b/ui/ncurses/nc-plugin.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 IBM Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _NC_PLUGIN_H +#define _NC_PLUGIN_H + +#include "nc-cui.h" + +struct plugin_screen; + +struct plugin_screen *plugin_screen_init(struct cui *cui, + struct pmenu_item *item, + void (*on_exit)(struct cui *)); + +struct nc_scr *plugin_screen_scr(struct plugin_screen *screen); +void plugin_screen_update(struct plugin_screen *screen); + +int plugin_install_plugin(struct pmenu_item *item); + +#endif /* defined _NC_PLUGIN_H */ diff --git a/ui/ncurses/nc-scr.h b/ui/ncurses/nc-scr.h index be99b48..5671a6b 100644 --- a/ui/ncurses/nc-scr.h +++ b/ui/ncurses/nc-scr.h @@ -49,6 +49,7 @@ enum pb_nc_sig { pb_lang_screen_sig = 777, pb_add_url_screen_sig = 888, pb_subset_screen_sig = 101, + pb_plugin_screen_sig = 202, pb_removed_sig = -999, }; |