From e0b2ea65be1639671b952be3c5260aeb4ff1897b Mon Sep 17 00:00:00 2001 From: "Jose M. Guisado" Date: Thu, 18 May 2023 13:59:21 +0200 Subject: [PATCH 1/2] Make some utility functions and types public Make read_u16 and read_order available to call outside efibootmgr.c. Make var_entry_t public to be used by other source files. Prepare efibootmgr for JSON output support by making required utility functions and types publicly available to other source files. Signed-off-by: Jose M. Guisado --- src/efibootmgr.c | 15 ++------------- src/efibootmgr.h | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/efibootmgr.c b/src/efibootmgr.c index 0094e05..ab0dbf3 100644 --- a/src/efibootmgr.c +++ b/src/efibootmgr.c @@ -51,7 +51,6 @@ #include #include -#include "list.h" #include "efi.h" #include "parse_loader_data.h" #include "efibootmgr.h" @@ -63,16 +62,6 @@ int verbose; -typedef struct _var_entry { - char *name; - efi_guid_t guid; - uint8_t *data; - size_t data_size; - uint32_t attributes; - uint16_t num; - list_t list; -} var_entry_t; - /* global variables */ static LIST_HEAD(entry_list); static LIST_HEAD(blk_list); @@ -348,7 +337,7 @@ make_var(const char *prefix, list_t *var_list) return NULL; } -static int +int read_order(const char *name, var_entry_t **order) { int rc; @@ -548,7 +537,7 @@ remove_from_order(const char *name, uint16_t num) return rc; } -static int +int read_u16(const char *name) { efi_guid_t guid = EFI_GLOBAL_GUID; diff --git a/src/efibootmgr.h b/src/efibootmgr.h index 34fa56c..956d9d7 100644 --- a/src/efibootmgr.h +++ b/src/efibootmgr.h @@ -35,6 +35,8 @@ #define EFIBOOTMGR_PATH_ABBREV_NONE 3 #define EFIBOOTMGR_PATH_ABBREV_FILE 4 +#include "list.h" + typedef enum { boot, driver, @@ -97,4 +99,17 @@ typedef struct { uint16_t index; } efibootmgr_opt_t; +typedef struct _var_entry { + char *name; + efi_guid_t guid; + uint8_t *data; + size_t data_size; + uint32_t attributes; + uint16_t num; + list_t list; +} var_entry_t; + extern efibootmgr_opt_t opts; + +int read_u16(const char *name); +int read_order(const char *name, var_entry_t **order); From 975051a51e7438109a9584780f323ea88ff3d4a0 Mon Sep 17 00:00:00 2001 From: "Jose M. Guisado" Date: Thu, 18 May 2023 14:31:35 +0200 Subject: [PATCH 2/2] Add optional JSON output support Enable JSON output for efibootmgr. This provides an interface for third party tools so they do not need to scrap the existing textual output. This feature is optional and is enabled by using JSON=1 when compiling: make ... JSON=1 Add parameter '-j'/--json' to use JSON output. If efibootmgr is not built with JSON output support it will print out this instead: "JSON support is not built-in" This feature adds a new optional dependency with libjansson. Signed-off-by: Jose M. Guisado --- Make.defaults | 3 + src/Makefile | 7 ++ src/efibootmgr.c | 15 +++- src/efibootmgr.h | 1 + src/json.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++ src/json.h | 21 ++++++ 6 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/json.c create mode 100644 src/json.h diff --git a/Make.defaults b/Make.defaults index c4c0592..381526b 100644 --- a/Make.defaults +++ b/Make.defaults @@ -46,6 +46,9 @@ cflags = $(EXTRALIBDIR) $(EXTRAINCDIR) $(CFLAGS) $(SUBDIR_CFLAGS) \ $(if $(findstring clang,$(CC)),$(clang_cflags),) \ $(if $(findstring gcc,$(CC)),$(gcc_cflags),) \ $(call pkg-config-cflags) +ifdef JSON + cflags += -DJSON +endif clang_ccldflags = gcc_ccldflags = -fno-merge-constants \ -Wl,--fatal-warnings,--no-allow-shlib-undefined \ diff --git a/src/Makefile b/src/Makefile index 2f1bec4..7e2612f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -11,14 +11,21 @@ TARGETS=$(BINTARGETS) efibootmgr.8 efibootdump.8 all : deps $(TARGETS) EFIBOOTMGR_SOURCES = efibootmgr.c efi.c parse_loader_data.c +ifdef JSON +EFIBOOTMGR_SOURCES += json.c +endif EFICONMAN_SOURCES = eficonman.c EFIBOOTDUMP_SOURCES = efibootdump.c parse_loader_data.c EFIBOOTNEXT_SOURCES = efibootnext.c ALL_SOURCES=$(EFIBOOTMGR_SOURCES) -include $(call deps-of,$(ALL_SOURCES)) + efibootmgr : $(call objects-of,$(EFIBOOTMGR_SOURCES)) efibootmgr : PKGS=efivar efiboot +ifdef JSON +efibootmgr : PKGS+=jansson +endif eficonman : $(call objects-of,$(EFICONMAN_SOURCES)) eficonman : PKGS=efivar efiboot popt diff --git a/src/efibootmgr.c b/src/efibootmgr.c index ab0dbf3..1e57e99 100644 --- a/src/efibootmgr.c +++ b/src/efibootmgr.c @@ -55,6 +55,7 @@ #include "parse_loader_data.h" #include "efibootmgr.h" #include "error.h" +#include "json.h" #ifndef EFIBOOTMGR_VERSION #define EFIBOOTMGR_VERSION "unknown (fix Makefile!)" @@ -1456,6 +1457,7 @@ usage() printf("\t-g | --gpt Force disk with invalid PMBR to be treated as GPT.\n"); printf("\t-i | --iface name Create a netboot entry for the named interface.\n"); printf("\t-I | --index number When creating an entry, insert it in bootorder at specified position (default: 0).\n"); + printf("\t-j | --json Enable JSON output\n"); printf("\t-l | --loader name (Defaults to \""DEFAULT_LOADER"\").\n"); printf("\t-L | --label label Boot manager display label (defaults to \"Linux\").\n"); printf("\t-m | --mirror-below-4G t|f Mirror memory below 4GB.\n"); @@ -1526,6 +1528,7 @@ parse_opts(int argc, char **argv) {"gpt", no_argument, 0, 'g'}, {"iface", required_argument, 0, 'i'}, {"index", required_argument, 0, 'I'}, + {"json", no_argument, 0, 'j'}, {"keep", no_argument, 0, 'k'}, {"loader", required_argument, 0, 'l'}, {"label", required_argument, 0, 'L'}, @@ -1552,7 +1555,7 @@ parse_opts(int argc, char **argv) }; c = getopt_long(argc, argv, - "aAb:BcCd:De:E:fFgi:I:kl:L:m:M:n:No:Op:qrst:Tuv::Vwy@:h", + "aAb:BcCd:De:E:fFgi:I:jkl:L:m:M:n:No:Op:qrst:Tuv::Vwy@:h", long_options, &option_index); if (c == -1) break; @@ -1666,6 +1669,9 @@ parse_opts(int argc, char **argv) } opts.index = (uint16_t)lindex; break; + case 'j': + opts.json = 1; + break; case 'k': opts.keep_old_entries = 1; break; @@ -2006,7 +2012,7 @@ main(int argc, char **argv) ret=set_mirror(opts.below4g, opts.above4g); } - if (!opts.quiet && ret == 0) { + if (!opts.quiet && !opts.json && ret == 0) { switch (mode) { case boot: num = read_u16("BootNext"); @@ -2035,6 +2041,11 @@ main(int argc, char **argv) break; } } + + if (!opts.quiet && opts.json && ret == 0) { + print_json(&entry_list, mode, prefices, order_name); + } + free_vars(&entry_list); free_array(names); if (ret) diff --git a/src/efibootmgr.h b/src/efibootmgr.h index 956d9d7..0f93f98 100644 --- a/src/efibootmgr.h +++ b/src/efibootmgr.h @@ -95,6 +95,7 @@ typedef struct { unsigned int sysprep:1; unsigned int explicit_label:1; unsigned int list_supported_signature_types:1; + unsigned int json:1; short int timeout; uint16_t index; } efibootmgr_opt_t; diff --git a/src/json.c b/src/json.c new file mode 100644 index 0000000..e599389 --- /dev/null +++ b/src/json.c @@ -0,0 +1,173 @@ +#include +#include + +#include "parse_loader_data.h" +#include "efibootmgr.h" +#include "error.h" +#include "json.h" + +static void +json_fill_bootnext(json_t *root) +{ + char s[5] = {}; + json_t *value; + int num; + + num = read_u16("BootNext"); + cond_warning(opts.verbose >= 2 && num < 0, + "Could not read variable 'BootNext'"); + if (num >= 0) { + snprintf(s, sizeof(s), "%04X", num); + value = json_string(s); + json_object_set_new(root, "BootNext", value); + } +} + +static void +json_fill_bootcurrent(json_t *root) +{ + char s[5] = {}; + json_t *value; + int num; + + num = read_u16("BootCurrent"); + cond_warning(opts.verbose >= 2 && num < 0, + "Could not read variable 'BootCurrent'"); + if (num >= 0) { + snprintf(s, sizeof(s), "%04X", num); + value = json_string(s); + json_object_set_new(root, "BootCurrent", value); + } +} + +static void +json_fill_timeout(json_t *root) +{ + json_t *value; + int num; + + num = read_u16("Timeout"); + cond_warning(opts.verbose >= 2 && num < 0, + "Could not read variable 'Timeout'"); + if (num >= 0) { + value = json_integer(num); + json_object_set_new(root, "Timeout", value); + } +} + +static json_t * +bootorder_json_array(uint16_t *order, int length) +{ + json_t *value, *array; + char s[5] = {}; + int i; + + array = json_array(); + if (!array) + return NULL; + + for (i = 0; i < length; i++) { + snprintf(s, sizeof(s), "%04X", order[i]); + value = json_string(s); + json_array_append_new(array, value); + } + + return array; +} + +static void +json_fill_order(json_t *root, const char *name) +{ + var_entry_t *order = NULL; + uint16_t *data; + json_t *array; + int rc; + + rc = read_order(name, &order); + cond_warning(opts.verbose >= 2 && rc < 0, + "Could not read variable '%s'", name); + + if (rc < 0) { + if (errno == ENOENT) { + if (!strcmp(name, "BootOrder")) + printf("No BootOrder is set; firmware will attempt recovery\n"); + else + printf("No %s is set\n", name); + } else + perror("json_fill_order()"); + return; + } + + data = (uint16_t *)order->data; + if (order->data_size) { + array = bootorder_json_array(data, + order->data_size / sizeof(uint16_t)); + if (array != NULL) + json_object_set_new(root, name, array); + free(order->data); + } + free(order); +} + +static void +json_fill_vars(json_t *root, const char *prefix, list_t *entry_list) +{ + const unsigned char *description; + json_t *boot_json, *vars_json; + efi_load_option *load_option; + char name[16] = {'\0'}; + var_entry_t *boot; + list_t *pos; + int active; + + vars_json = json_array(); + if (!vars_json) + return; + + list_for_each(pos, entry_list) { + boot_json = json_object(); + boot = list_entry(pos, var_entry_t, list); + load_option = (efi_load_option *)boot->data; + description = efi_loadopt_desc(load_option, boot->data_size); + if (boot->name) + json_object_set_new(boot_json, "name", json_string(boot->name)); + else { + snprintf(name, sizeof(name), "%s%04X", prefix, boot->num); + json_object_set_new(boot_json, "name", json_string(boot->name)); + } + + active = efi_loadopt_attrs(load_option) & LOAD_OPTION_ACTIVE ? 1 : 0; + json_object_set_new(boot_json, "active", json_boolean(active)); + json_object_set_new(boot_json, "description", + json_string((char *)description)); + json_array_append_new(vars_json, boot_json); + } + + json_object_set_new(root, "vars", vars_json); +} + +void +__print_json(list_t *entry_list, ebm_mode mode, char **prefices, char **order_name) +{ + json_t *root = json_object(); + char *json_str = NULL; + + switch (mode) { + case boot: + json_fill_bootnext(root); + json_fill_bootcurrent(root); + json_fill_timeout(root); + json_fill_order(root, order_name[mode]); + json_fill_vars(root, prefices[mode], entry_list); + break; + case driver: + case sysprep: + json_fill_order(root, order_name[mode]); + json_fill_vars(root, prefices[mode], entry_list); + break; + } + json_str = json_dumps(root, JSON_COMPACT); + printf("%s\n", json_str); + free(json_str); + json_decref(root); +} diff --git a/src/json.h b/src/json.h new file mode 100644 index 0000000..75c1d01 --- /dev/null +++ b/src/json.h @@ -0,0 +1,21 @@ +#include + +#include "list.h" + +void __print_json(list_t *entry_list, ebm_mode mode, + char **prefices, char **order_name); + +#ifndef JSON +#define __unused __attribute__((unused)) +void print_json(list_t __unused *entry_list, ebm_mode __unused mode, + char __unused **prefices, char __unused **order_name) +{ + printf("JSON support is not built-in\n"); +} +#else +static inline void print_json(list_t *entry_list, ebm_mode mode, + char **prefices, char **order_name) +{ + __print_json(entry_list, mode, prefices, order_name); +} +#endif