From 975051a51e7438109a9584780f323ea88ff3d4a0 Mon Sep 17 00:00:00 2001 From: "Jose M. Guisado" Date: Thu, 18 May 2023 14:31:35 +0200 Subject: [PATCH] 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