From 03717ae8ccf92fbec7941bd2a4f9e3097e90e89f Mon Sep 17 00:00:00 2001 From: Marius Tomaschewski Date: Thu, 19 Jan 2023 12:56:51 +0100 Subject: [PATCH] client: add firmware extensions|interfaces command - `wicked firmware extensions` shows available firmware extensions table - `wicked firmware interfaces` shows interfaces configured by a firmware --- client/Makefile.am | 1 + client/firmware.c | 262 +++++++++++++++++++++++++++++++++++++++++++++ client/main.c | 4 + client/main.h | 1 + src/firmware.c | 191 +++++++++++++++++++++++++++++++++ src/firmware.h | 17 +++ 6 files changed, 476 insertions(+) create mode 100644 client/firmware.c diff --git a/client/Makefile.am b/client/Makefile.am index 8bc3f32e4..fc2909fac 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -59,6 +59,7 @@ wicked_SOURCES = \ convert.c \ duid.c \ ethtool.c \ + firmware.c \ iaid.c \ ifup.c \ ifdown.c \ diff --git a/client/firmware.c b/client/firmware.c new file mode 100644 index 000000000..566cb9a46 --- /dev/null +++ b/client/firmware.c @@ -0,0 +1,262 @@ +/* + * wicked firmware -- utils to firmware config discovery + * + * Copyright (C) 2023 SUSE LLC + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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, see . + * + * Authors: + * Marius Tomaschewski + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "appconfig.h" +#include "firmware.h" +#include "process.h" +#include "buffer.h" + +#include +#include + +/* + * wicked --root-directory option argument in main + */ +extern char * opt_global_rootdir; + +static int +ni_wicked_firmware_extensions(const char *caller, int argc, char **argv) +{ + enum { + OPTION_HELP = 'h', + }; + static const struct option options[] = { + { "help", no_argument, NULL, OPTION_HELP }, + { NULL } + }; + int opt, status = NI_WICKED_RC_USAGE; + char *argv0, *command = NULL; + const ni_extension_t *ex; + + ni_string_printf(&command, "%s %s", caller, argv[0]); + argv0 = argv[0]; + argv[0] = command; + optind = 1; + while ((opt = getopt_long(argc, argv, "+h", options, NULL)) != -1) { + switch (opt) { + case OPTION_HELP: + status = NI_WICKED_RC_SUCCESS; + /* fall through */ + default: + usage: + fprintf(stderr, + "\nUsage:\n" + "%s [options]\n" + "\n" + "Options:\n" + " --help, -h show this help text and exit.\n" + "\n", command); + goto cleanup; + } + } + + if (argc > optind) + goto usage; + + if (!ni_global.config) { + fprintf(stderr, "%s: application config is not initialized\n", command); + status = NI_WICKED_RC_ERROR; + goto cleanup; + } + + status = NI_WICKED_RC_SUCCESS; + for (ex = ni_global.config->fw_extensions; ex; ex = ex->next) { + const ni_script_action_t *script; + + /* builtins are not supported in netif-firmware-discovery */ + + for (script = ex->actions; script; script = script->next) { + const char *enabled; + + if (ni_string_empty(script->name) || !script->process) + continue; + + if (ni_string_empty(script->process->command)) + continue; + + /* + * all enabled until we've added an enabled/disabled + * flag to extension script actions in appconfig... + * for now, use missing x-bit to emulate disabled... + */ + if (ni_file_executable(script->process->command)) + enabled = "enabled"; + else + enabled = "disabled"; + + printf("%-15s %s\n", script->name, enabled); + } + } + +cleanup: + argv[0] = argv0; + ni_string_free(&command); + return status; +} + +static int +ni_wicked_firmware_interfaces(const char *caller, int argc, char **argv) +{ + enum { + OPTION_HELP = 'h', + }; + static const struct option options[] = { + { "help", no_argument, NULL, OPTION_HELP }, + { NULL } + }; + int opt, status = NI_WICKED_RC_USAGE; + char *argv0, *command = NULL; + ni_netif_firmware_ifnames_t *list = NULL; + ni_netif_firmware_ifnames_t *item = NULL; + + ni_string_printf(&command, "%s %s", caller, argv[0]); + argv0 = argv[0]; + argv[0] = command; + optind = 1; + while ((opt = getopt_long(argc, argv, "+h", options, NULL)) != -1) { + switch (opt) { + case OPTION_HELP: + status = NI_WICKED_RC_SUCCESS; + /* fall through */ + default: + usage: + fprintf(stderr, + "\nUsage:\n" + "%s [options]\n" + "\n" + "Options:\n" + " --help, -h show this help text and exit.\n" + "\n", command); + goto cleanup; + } + } + + if (argc > optind) + goto usage; + + if (!ni_netif_firmware_discover_ifnames(&list, NULL, opt_global_rootdir, NULL)) { + status = NI_WICKED_RC_ERROR; + } else { + for (item = list; item; item = item->next) { + ni_stringbuf_t ifnames = NI_STRINGBUF_INIT_DYNAMIC; + + if (ni_string_empty(item->fwname) || !item->ifnames.count) + continue; + + ni_stringbuf_join(&ifnames, &item->ifnames, " "); + if (ifnames.len) + printf("%-15s %s\n", item->fwname, ifnames.string); + ni_stringbuf_destroy(&ifnames); + } + status = NI_WICKED_RC_SUCCESS; + } + +cleanup: + argv[0] = argv0; + ni_string_free(&command); + ni_netif_firmware_ifnames_list_destroy(&list); + return status; +} + +int +ni_wicked_firmware(const char *caller, int argc, char **argv) +{ + enum { + OPTION_HELP = 'h', + }; + enum { + ACTION_EXTENSIONS, + ACTION_INTERFACES, + }; + static const struct option options[] = { + { "help", no_argument, NULL, OPTION_HELP }, + { NULL } + }; + static const ni_intmap_t actions[] = { + { "extensions", ACTION_EXTENSIONS }, + { "interfaces", ACTION_INTERFACES }, + { NULL, -1U } + }; + int opt, status = NI_WICKED_RC_USAGE; + char *argv0, *command = NULL; + unsigned int action = -1U; + + ni_string_printf(&command, "%s %s", caller, argv[0]); + argv0 = argv[0]; + argv[0] = command; + optind = 1; + while ((opt = getopt_long(argc, argv, "+h", options, NULL)) != -1) { + switch (opt) { + case OPTION_HELP: + status = NI_WICKED_RC_SUCCESS; + /* fall through */ + default: + usage: + fprintf(stderr, + "\nUsage:\n" + "%s [options] \n" + "\n" + "Options:\n" + " --help, -h show this help text and exit.\n" + "\n" + "Actions:\n" + " extensions list firmware config extensions and status\n" + " interfaces list firmware and interface names it configures\n" + "\n", command); + goto cleanup; + } + } + + if (optind >= argc || ni_string_empty(argv[optind]) || + ni_parse_uint_mapped(argv[optind], actions, &action)) { + fprintf(stderr, "%s: please specify an action\n", command); + goto usage; + } + + /* execute actions that do not need decoding */ + switch (action) { + case ACTION_EXTENSIONS: + status = ni_wicked_firmware_extensions(command, + argc - optind, argv + optind); + goto cleanup; + + case ACTION_INTERFACES: + status = ni_wicked_firmware_interfaces(command, + argc - optind, argv + optind); + goto cleanup; + + default: + break; + } + +cleanup: + argv[0] = argv0; + ni_string_free(&command); + return status; +} diff --git a/client/main.c b/client/main.c index 09a9ec8bb..7e7e60162 100644 --- a/client/main.c +++ b/client/main.c @@ -173,6 +173,7 @@ main(int argc, char **argv) " convert [options] [|all]\n" " ethtool [options] <...>\n" " redfish [options] \n" + " firmware [options] \n" " getnames [options]\n" " xpath [options] expr ...\n" " nanny ...\n" @@ -339,6 +340,9 @@ main(int argc, char **argv) } else if (!strcmp(cmd, "redfish")) { status = ni_wicked_redfish(program, argc - optind, argv + optind); + } else + if (!strcmp(cmd, "firmware")) { + status = ni_wicked_firmware(program, argc - optind, argv + optind); } else { fprintf(stderr, "Unsupported command %s\n", cmd); goto usage; diff --git a/client/main.h b/client/main.h index e39a6705f..5e0b45e35 100644 --- a/client/main.h +++ b/client/main.h @@ -32,5 +32,6 @@ extern int ni_do_ethtool(const char *caller, int argc, char **argv); extern int ni_wicked_convert(const char *caller, int argc, char **argv); extern int ni_wicked_redfish(const char *caller, int argc, char **argv); +extern int ni_wicked_firmware(const char *caller, int argc, char **argv); #endif /* WICKED_CLIENT_MAIN_H */ diff --git a/src/firmware.c b/src/firmware.c index 9f3df3cfa..9df111fa4 100644 --- a/src/firmware.c +++ b/src/firmware.c @@ -262,3 +262,194 @@ ni_netif_firmware_discover_ifconfig(xml_document_array_t *docs, else return TRUE; } + +/* + * Run the netif firmware discovery scripts and return interface names + * the firmware configures. + */ +void +ni_netif_firmware_ifnames_free(ni_netif_firmware_ifnames_t *nfi) +{ + if (nfi) { + ni_string_free(&nfi->fwname); + ni_string_array_destroy(&nfi->ifnames); + free(nfi); + } +} + +ni_netif_firmware_ifnames_t * +ni_netif_firmware_ifnames_new(const char *fwname) +{ + ni_netif_firmware_ifnames_t *nfi; + + if (!(nfi = calloc(1, sizeof(*nfi)))) + return NULL; + + if (ni_string_dup(&nfi->fwname, fwname)) + return nfi; + + ni_netif_firmware_ifnames_free(nfi); + return NULL; +} + +ni_bool_t +ni_netif_firmware_ifnames_list_append(ni_netif_firmware_ifnames_t **list, ni_netif_firmware_ifnames_t *item) +{ + if (list && item) { + while (*list) + list = &(*list)->next; + *list = item; + return TRUE; + } + return FALSE; +} + +void +ni_netif_firmware_ifnames_list_destroy(ni_netif_firmware_ifnames_t **list) +{ + ni_netif_firmware_ifnames_t *item; + + if (list) { + while ((item = *list)) { + *list = item->next; + ni_netif_firmware_ifnames_free(item); + } + } +} + +static ni_bool_t +ni_netif_firmware_ifnames_parse(ni_netif_firmware_ifnames_t **list, + const char *fwname, ni_buffer_t *buf) +{ + ni_stringbuf_t line = NI_STRINGBUF_INIT_DYNAMIC; + ni_string_array_t ifnames = NI_STRING_ARRAY_INIT; + ni_netif_firmware_ifnames_t *item = NULL; + ni_bool_t ret = TRUE; + ni_buffer_t rbuf; + int c; + + if (!list || !buf || !fwname) + return FALSE; + + if (!ni_buffer_init_reader(&rbuf, ni_buffer_head(buf), ni_buffer_count(buf))) + return FALSE; + + while (ret && ni_buffer_count(&rbuf)) { + while ((c = ni_buffer_getc(&rbuf)) != EOF) { + if (c == '\n') + break; + ni_stringbuf_putc(&line, c); + } + + if (!ni_string_split(&ifnames, line.string, "\t ", 0)) { + ni_stringbuf_truncate(&line, 0); + continue; + } + ni_stringbuf_truncate(&line, 0); + + if ((item = ni_netif_firmware_ifnames_new(fwname))) { + ni_string_array_move(&item->ifnames, &ifnames); + ret = ni_netif_firmware_ifnames_list_append(list, item); + } else { + ret = FALSE; + } + } + + ni_buffer_destroy(&rbuf); + ni_stringbuf_destroy(&line); + ni_string_array_destroy(&ifnames); + return ret; +} + +int +ni_netif_firmware_discover_script_ifnames(ni_netif_firmware_ifnames_t **list, + ni_script_action_t *script, const char *type, + const char *root, const char *path) +{ + char buffer[BUFSIZ]; + ni_buffer_t buf; + int status; + + ni_assert(list && script); + + /* Use an initial static (8k) buffer, that should be sufficient. + * When it gets full, it will be automatically reallocated... */ + memset(buffer, 0, sizeof(buffer)); + ni_buffer_init(&buf, &buffer, sizeof(buffer)); + + status = ni_netif_firmware_discovery_script_call(&buf, script, + type, root, path, TRUE); + + if (status == NI_PROCESS_SUCCESS && ni_buffer_count(&buf)) { + + if (!ni_netif_firmware_ifnames_parse(list, script->name, &buf)) { + ni_debug_ifconfig("%s discovery script failure: invalid list output", type); + ni_netif_firmware_ifnames_list_destroy(list); + /* hmm... NI_ERROR_DOCUMENT_ERROR? */ + status = NI_WICKED_RC_ERROR; + } + } + ni_buffer_destroy(&buf); + return status; +} + +ni_bool_t +ni_netif_firmware_discover_ifnames(ni_netif_firmware_ifnames_t **list, + const char *type, const char *root, const char *path) +{ + unsigned int success = 0; + unsigned int failure = 0; + ni_extension_t *ex; + char *name = NULL; + + if (!list || !ni_global.config) + return FALSE; + + /* sanity adjustments... */ + if (ni_string_empty(root)) + root = NULL; + if (ni_string_empty(type)) + type = "firmware"; + + if (!ni_netif_firmware_name_from_path(&name, &path)) + return FALSE; + + for (ex = ni_global.config->fw_extensions; ex; ex = ex->next) { + ni_script_action_t *script; + + /* builtins are not supported in netif-firmware-discovery */ + + for (script = ex->actions; script; script = script->next) { + ni_netif_firmware_ifnames_t *curr = NULL; + char *full = NULL; + + /* Check if script is usable/non-empty and executable */ + if (!ni_netif_firmware_extension_script_usable(script)) + continue; + + /* Check if to use specific type/name only (e.g. "ibft") */ + if (name && !ni_string_eq_nocase(name, script->name)) + continue; + + /* Construct full firmware type name, e.g. firmware:ibft */ + if (!ni_string_printf(&full, "%s:%s", type, script->name)) + continue; + + if (ni_netif_firmware_discover_script_ifnames(&curr, + script, full, root, path) == 0) { + ni_netif_firmware_ifnames_list_append(list, curr); + success++; + } else { + failure++; + } + + ni_string_free(&full); + } + } + ni_string_free(&name); + + if (failure && !success) + return FALSE; + else + return TRUE; +} diff --git a/src/firmware.h b/src/firmware.h index da0c19342..bb85851f7 100644 --- a/src/firmware.h +++ b/src/firmware.h @@ -25,6 +25,23 @@ #ifndef WICKED_FIRMWARE_UTILS_H #define WICKED_FIRMWARE_UTILS_H +typedef struct ni_netif_firmware_ifnames ni_netif_firmware_ifnames_t; + +struct ni_netif_firmware_ifnames { + ni_netif_firmware_ifnames_t * next; + char * fwname; + ni_string_array_t ifnames; +}; + +extern void ni_netif_firmware_ifnames_free(ni_netif_firmware_ifnames_t *); +extern ni_netif_firmware_ifnames_t * ni_netif_firmware_ifnames_new(const char *); +extern ni_bool_t ni_netif_firmware_ifnames_list_append(ni_netif_firmware_ifnames_t **, + ni_netif_firmware_ifnames_t *); +extern void ni_netif_firmware_ifnames_list_destroy(ni_netif_firmware_ifnames_t **); + +extern ni_bool_t ni_netif_firmware_discover_ifnames(ni_netif_firmware_ifnames_t **, + const char *, const char *, const char *); + extern ni_bool_t ni_netif_firmware_discover_ifconfig(xml_document_array_t *, const char *, const char *, const char *);