The following new command-line arguments are introduced: --current: print values from the current env --output: list of fields to print --raw: raw output mode, e.g. for shell scripting purposes --part: set environment partition to use
The motivation for this change is to have a proper API to determine the current ustate. Previously, one had to parse the output of bg_printenv and rely on an unspecified interface (e.g. there is no guarantee that it will work with the next release of efibootguard). Now there are even tests for exactly this use-case. Examples: * Query the current ustate (and nothing else): $ bg_printenv --current --output ustate * Use raw output mode for shell scripting: $ source <(bg_printenv --current --output kernel,ustate --raw) $ echo "kernel: $KERNEL, ustate: $USTATE" If you only care aboutt the ustate, you could also do the following: $ USTATE=$(bg_printenv --current --output ustate --raw) $ echo "ustate: $USTATE" Signed-off-by: Michael Adler <[email protected]> --- tests/bg_setenv.bats | 40 ++++++ tools/bg_setenv.c | 326 +++++++++++++++++++++++++++++++++---------- 2 files changed, 294 insertions(+), 72 deletions(-) diff --git a/tests/bg_setenv.bats b/tests/bg_setenv.bats index 87779e6..9dc7260 100755 --- a/tests/bg_setenv.bats +++ b/tests/bg_setenv.bats @@ -114,3 +114,43 @@ foo = bar" ]] run md5sum "$envfile" [[ "$output" =~ ^a24b154a48e1f33b79b87e0fa5eff8a1\s*.* ]] } + +@test "bg_printenv ustate" { + local envfile + envfile="$(mktemp -d)/BGENV.DAT" + + create_sample_bgenv "$envfile" + run bg_printenv "--filepath=$envfile" --output ustate + [[ "$output" = "Values: +ustate: 0 (OK)" ]] +} + +@test "bg_printenv ustate raw" { + local envfile + envfile="$(mktemp -d)/BGENV.DAT" + + create_sample_bgenv "$envfile" + run bg_printenv "--filepath=$envfile" --output ustate --raw + [[ "$output" = "USTATE=0" ]] +} + +@test "bg_printenv multiple fields raw" { + local envfile + envfile="$(mktemp -d)/BGENV.DAT" + + create_sample_bgenv "$envfile" + run bg_printenv "--filepath=$envfile" --output ustate,kernel,kernelargs --raw + [[ "$output" = "KERNEL=C:BOOT:kernel.efi +KERNELARGS=root=/dev/sda +USTATE=0" ]] +} + +@test "bg_printenv with all fields is the same as omitting fields" { + local envfile + envfile="$(mktemp -d)/BGENV.DAT" + + create_sample_bgenv "$envfile" + expected_output=$(bg_printenv "--filepath=$envfile") + run bg_printenv "--filepath=$envfile" --output in_progress,revision,kernel,kernelargs,watchdog_timeout,ustate,user + [[ "$output" = "$expected_output" ]] +} diff --git a/tools/bg_setenv.c b/tools/bg_setenv.c index 3f1620c..4498742 100644 --- a/tools/bg_setenv.c +++ b/tools/bg_setenv.c @@ -5,6 +5,7 @@ * * Authors: * Andreas Reichel <[email protected]> + * Michael Adler <[email protected]> * * This work is licensed under the terms of the GNU GPL, version 2. See * the COPYING file in the top-level directory. @@ -29,6 +30,9 @@ static char doc[] = {"filepath", 'f', "ENVFILE", 0, \ "Environment to use. Expects a file name, " \ "usually called BGENV.DAT."}, \ + {"part", 'p', "ENV_PART", 0, \ + "Set environment partition to use. If no partition is specified, " \ + "the one with the smallest revision value above zero is used."}, \ {"verbose", 'v', 0, 0, "Be verbose"}, \ {"version", 'V', 0, 0, "Print version"} // clang-format on @@ -38,9 +42,6 @@ static struct argp_option options_setenv[] = { {"preserve", 'P', 0, 0, "Preserve existing entries"}, {"kernel", 'k', "KERNEL", 0, "Set kernel to load"}, {"args", 'a', "KERNEL_ARGS", 0, "Set kernel arguments"}, - {"part", 'p', "ENV_PART", 0, - "Set environment partition to update. If no partition is specified, " - "the one with the smallest revision value above zero is updated."}, {"revision", 'r', "REVISION", 0, "Set revision value"}, {"ustate", 's', "USTATE", 0, "Set update status for environment"}, {"watchdog", 'w', "WATCHDOG_TIMEOUT", 0, "Watchdog timeout in seconds"}, @@ -56,6 +57,14 @@ static struct argp_option options_setenv[] = { static struct argp_option options_printenv[] = { BG_CLI_OPTIONS_COMMON, + {"current", 'c', 0, 0, + "Only print values from the current environment"}, + {"output", 'o', "LIST", 0, + "Comma-separated list of fields which are printed. " + "Available fields: in_progress, revision, kernel, kernelargs, " + "watchdog_timeout, ustate, user. " + "If omitted, all available fields are printed."}, + {"raw", 'r', 0, 0, "Raw output mode, e.g. for shell scripting"}, {}, }; @@ -63,17 +72,19 @@ static struct argp_option options_printenv[] = { struct arguments_common { char *envfilepath; bool verbosity; + /* which partition to operate on; a negative value means no partition + * was specified. */ + int which_part; + bool part_specified; }; /* Arguments used by bg_setenv. */ struct arguments_setenv { struct arguments_common common; - int which_part; /* auto update feature automatically updates partition with * oldest environment revision (lowest value) */ bool auto_update; - bool part_specified; /* whether to keep existing entries in BGENV before applying new * settings */ bool preserve_env; @@ -82,8 +93,23 @@ struct arguments_setenv { /* Arguments used by bg_printenv. */ struct arguments_printenv { struct arguments_common common; + bool current; + int output_fields; + bool raw; }; +/* Bitmasks */ +#define MASK_IN_PROGRESS (1 << 0) +#define MASK_REVISION (1 << 1) +#define MASK_KERNEL (1 << 2) +#define MASK_KERNELARGS (1 << 3) +#define MASK_WDOG_TIMEOUT (1 << 4) +#define MASK_USTATE (1 << 5) +#define MASK_USER (1 << 6) +#define MASK_ALL \ + (MASK_IN_PROGRESS | MASK_REVISION | MASK_KERNEL | MASK_KERNELARGS | \ + MASK_WDOG_TIMEOUT | MASK_USTATE | MASK_USER) + typedef enum { ENV_TASK_SET, ENV_TASK_DEL } BGENV_TASK; struct stailhead *headp; @@ -250,6 +276,7 @@ static error_t parse_common_opt(int key, char *arg, bool compat_mode, struct arguments_common *arguments) { bool found = false; + int i; switch (key) { case 'f': found = true; @@ -280,6 +307,24 @@ static error_t parse_common_opt(int key, char *arg, bool compat_mode, } } break; + case 'p': + found = true; + i = parse_int(arg); + if (errno) { + fprintf(stderr, "Invalid number specified for -p.\n"); + return 1; + } + if (i >= 0 && i < ENV_NUM_CONFIG_PARTS) { + arguments->which_part = i; + arguments->part_specified = true; + } else { + fprintf(stderr, + "Selected partition out of range. Valid range: " + "0..%d.\n", + ENV_NUM_CONFIG_PARTS - 1); + return 1; + } + break; case 'v': found = true; /* Set verbosity in this program */ @@ -328,23 +373,6 @@ static error_t parse_setenv_opt(int key, char *arg, struct argp_state *state) e = journal_add_action(ENV_TASK_SET, "kernelparams", 0, (uint8_t *)arg, strlen(arg) + 1); break; - case 'p': - i = parse_int(arg); - if (errno) { - fprintf(stderr, "Invalid number specified for -p.\n"); - return 1; - } - if (i >= 0 && i < ENV_NUM_CONFIG_PARTS) { - fprintf(stdout, "Updating config partition #%d\n", i); - arguments->which_part = i; - arguments->part_specified = true; - } else { - fprintf(stderr, - "Selected partition out of range. Valid range: " - "0..%d.\n", ENV_NUM_CONFIG_PARTS - 1); - return 1; - } - break; case 's': i = parse_int(arg); if (errno) { @@ -452,11 +480,50 @@ static error_t parse_setenv_opt(int key, char *arg, struct argp_state *state) return e; } +static error_t parse_output_fields(char *fields, int *output_fields) +{ + char *token; + /* reset */ + *output_fields = 0; + while ((token = strsep(&fields, ","))) { + if (*token == '\0') continue; + if (strcmp(token, "in_progress") == 0) { + *output_fields |= MASK_IN_PROGRESS; + } else if (strcmp(token, "revision") == 0) { + *output_fields |= MASK_REVISION; + } else if (strcmp(token, "kernel") == 0) { + *output_fields |= MASK_KERNEL; + } else if (strcmp(token, "kernelargs") == 0) { + *output_fields |= MASK_KERNELARGS; + } else if (strcmp(token, "watchdog_timeout") == 0) { + *output_fields |= MASK_WDOG_TIMEOUT; + } else if (strcmp(token, "ustate") == 0) { + *output_fields |= MASK_USTATE; + } else if (strcmp(token, "user") == 0) { + *output_fields |= MASK_USER; + } else { + fprintf(stderr, "Unknown output field: %s\n", token); + return 1; + } + } + return 0; +} + static error_t parse_printenv_opt(int key, char *arg, struct argp_state *state) { struct arguments_printenv *arguments = state->input; + error_t e = 0; switch (key) { + case 'c': + arguments->current = true; + break; + case 'o': + e = parse_output_fields(arg, &arguments->output_fields); + break; + case 'r': + arguments->raw = true; + break; case ARGP_KEY_ARG: /* too many arguments - program terminates with call to * argp_usage with non-zero return code */ @@ -466,10 +533,10 @@ static error_t parse_printenv_opt(int key, char *arg, struct argp_state *state) return parse_common_opt(key, arg, false, &arguments->common); } - return 0; + return e; } -static void dump_uservars(uint8_t *udata) +static void dump_uservars(uint8_t *udata, bool raw) { char *key, *value; uint64_t type; @@ -477,13 +544,17 @@ static void dump_uservars(uint8_t *udata) uint64_t val_unum; int64_t val_snum; + if (!raw) { + fprintf(stdout, "\n"); + fprintf(stdout, "user variables:\n"); + } while (*udata) { bgenv_map_uservar(udata, &key, &type, (uint8_t **)&value, &rsize, &dsize); - fprintf(stdout, "%s ", key); + fprintf(stdout, "%s", key); type &= USERVAR_STANDARD_TYPE_MASK; if (type == USERVAR_TYPE_STRING_ASCII) { - fprintf(stdout, "= %s\n", value); + fprintf(stdout, raw ? "=%s\n" : " = %s\n", value); } else if (type >= USERVAR_TYPE_UINT8 && type <= USERVAR_TYPE_UINT64) { switch(type) { @@ -500,8 +571,8 @@ static void dump_uservars(uint8_t *udata) val_unum = *((uint64_t *) value); break; } - fprintf(stdout, "= %llu\n", - (long long unsigned int) val_unum); + fprintf(stdout, raw ? "=%llu\n" : " = %llu\n", + (long long unsigned int)val_unum); } else if (type >= USERVAR_TYPE_SINT8 && type <= USERVAR_TYPE_SINT64) { switch(type) { @@ -518,19 +589,20 @@ static void dump_uservars(uint8_t *udata) val_snum = *((int64_t *) value); break; } - fprintf(stdout, "= %lld\n", - (long long signed int) val_snum); + fprintf(stdout, raw ? "=%lld\n" : " = %lld\n", + (long long signed int)val_snum); } else { switch(type) { case USERVAR_TYPE_CHAR: - fprintf(stdout, "= %c\n", (char) *value); + fprintf(stdout, raw ? "=%c\n" : " = %c\n", + (char)*value); break; case USERVAR_TYPE_BOOL: - fprintf(stdout, "= %s\n", - (bool) *value ? "true" : "false"); + fprintf(stdout, raw ? "=%s\n" : " = %s\n", + (bool)*value ? "true" : "false"); break; default: - fprintf(stdout, "( Type is not printable )\n"); + fprintf(stdout, " ( Type is not printable )\n"); } } @@ -538,25 +610,67 @@ static void dump_uservars(uint8_t *udata) } } -static void dump_env(BG_ENVDATA *env) +static void dump_env(BG_ENVDATA *env, bool raw, int output_fields) { char buffer[ENV_STRING_LENGTH]; - fprintf(stdout, "Values:\n"); - fprintf(stdout, - "in_progress: %s\n",env->in_progress ? "yes" : "no"); - fprintf(stdout, "revision: %u\n", env->revision); - fprintf(stdout, - "kernel: %s\n", str16to8(buffer, env->kernelfile)); - fprintf(stdout, - "kernelargs: %s\n", str16to8(buffer, env->kernelparams)); - fprintf(stdout, - "watchdog timeout: %u seconds\n", env->watchdog_timeout_sec); - fprintf(stdout, "ustate: %u (%s)\n", (uint8_t)env->ustate, - ustate2str(env->ustate)); - fprintf(stdout, "\n"); - fprintf(stdout, "user variables:\n"); - dump_uservars(env->userdata); - fprintf(stdout, "\n\n"); + if (!raw) { + fprintf(stdout, "Values:\n"); + } + if (output_fields & MASK_IN_PROGRESS) { + if (raw) { + fprintf(stdout, "IN_PROGRESS=%d\n", env->in_progress); + } else { + fprintf(stdout, "in_progress: %s\n", + env->in_progress ? "yes" : "no"); + } + } + if (output_fields & MASK_REVISION) { + if (raw) { + fprintf(stdout, "REVISION=%d\n", env->revision); + } else { + fprintf(stdout, "revision: %u\n", + env->revision); + } + } + if (output_fields & MASK_KERNEL) { + char *kernelfile = str16to8(buffer, env->kernelfile); + if (raw) { + fprintf(stdout, "KERNEL=%s\n", kernelfile); + } else { + fprintf(stdout, "kernel: %s\n", kernelfile); + } + } + if (output_fields & MASK_KERNELARGS) { + char *kernelargs = str16to8(buffer, env->kernelparams); + if (raw) { + fprintf(stdout, "KERNELARGS=%s\n", kernelargs); + } else { + fprintf(stdout, "kernelargs: %s\n", kernelargs); + } + } + if (output_fields & MASK_WDOG_TIMEOUT) { + if (raw) { + fprintf(stdout, "WATCHDOG_TIMEOUT=%u\n", + env->watchdog_timeout_sec); + } else { + fprintf(stdout, "watchdog timeout: %u seconds\n", + env->watchdog_timeout_sec); + } + } + if (output_fields & MASK_USTATE) { + if (raw) { + fprintf(stdout, "USTATE=%u\n", env->ustate); + } else { + fprintf(stdout, "ustate: %u (%s)\n", + (uint8_t)env->ustate, ustate2str(env->ustate)); + } + } + if (output_fields & MASK_USER) { + dump_uservars(env->userdata, raw); + } + if (!raw) { + fprintf(stdout, "\n\n"); + } } static void update_environment(BGENV *env, bool verbosity) @@ -578,11 +692,13 @@ static void update_environment(BGENV *env, bool verbosity) } -static void dump_envs(void) +static void dump_envs(bool raw, int output_fields) { for (int i = 0; i < ENV_NUM_CONFIG_PARTS; i++) { - fprintf(stdout, "\n----------------------------\n"); - fprintf(stdout, " Config Partition #%d ", i); + if (!raw) { + fprintf(stdout, "\n----------------------------\n"); + fprintf(stdout, " Config Partition #%d ", i); + } BGENV *env = bgenv_open_by_index(i); if (!env) { fprintf(stderr, "Error, could not read environment " @@ -590,11 +706,33 @@ static void dump_envs(void) i); return; } - dump_env(env->data); + dump_env(env->data, raw, output_fields); bgenv_close(env); } } +static void dump_latest_env(bool raw, int output_fields) +{ + BGENV *env = bgenv_open_latest(); + if (!env) { + fprintf(stderr, "Failed to retrieve latest environment.\n"); + return; + } + dump_env(env->data, raw, output_fields); + bgenv_close(env); +} + +static void dump_env_by_index(uint32_t index, bool raw, int output_fields) +{ + BGENV *env = bgenv_open_by_index(index); + if (!env) { + fprintf(stderr, "Failed to retrieve latest environment.\n"); + return; + } + dump_env(env->data, raw, output_fields); + bgenv_close(env); +} + static bool get_env(char *configfilepath, BG_ENVDATA *data) { FILE *config; @@ -620,13 +758,14 @@ static bool get_env(char *configfilepath, BG_ENVDATA *data) return result; } -static int printenv_from_file(char *envfilepath) { +static int printenv_from_file(char *envfilepath, bool raw, int output_fields) +{ int success = 0; BG_ENVDATA data; success = get_env(envfilepath, &data); if (success) { - dump_env(&data); + dump_env(&data, raw, output_fields); return 0; } else { fprintf(stderr, "Error reading environment file.\n"); @@ -651,7 +790,7 @@ static int dumpenv_to_file(char *envfilepath, bool verbosity, bool preserve_env) update_environment(&env, verbosity); if (verbosity) { - dump_env(env.data); + dump_env(env.data, false, MASK_ALL); } FILE *of = fopen(envfilepath, "wb"); if (of) { @@ -679,23 +818,49 @@ static int dumpenv_to_file(char *envfilepath, bool verbosity, bool preserve_env) /* This is the entrypoint for the command bg_printenv. */ static error_t bg_printenv(int argc, char **argv) { + error_t e = 0; struct argp argp_printenv = { .options = options_printenv, .parser = parse_printenv_opt, .doc = doc, }; - struct arguments_setenv arguments; - memset(&arguments, 0, sizeof(struct arguments_setenv)); + struct arguments_printenv arguments = { + .output_fields = MASK_ALL, + }; - error_t e = argp_parse(&argp_printenv, argc, argv, 0, 0, &arguments); + e = argp_parse(&argp_printenv, argc, argv, 0, 0, &arguments); if (e) { return e; } - if (arguments.common.envfilepath) { - int result = printenv_from_file(arguments.common.envfilepath); - free(arguments.common.envfilepath); - return result; + + const struct arguments_common *common = &arguments.common; + + /* count the number of arguments which result in bg_printenv + * operating on a single partition; to avoid ambiguity, we only + * allow one such argument. */ + int counter = 0; + if (common->envfilepath) ++counter; + if (common->part_specified) ++counter; + if (arguments.current) ++counter; + if (counter > 1) { + fprintf(stderr, "Error, only one of -f/-c/-p can be set.\n"); + return 1; + } + if (arguments.raw && counter != 1) { + /* raw mode makes only sense if applied to a single + * partition */ + fprintf(stderr, "Error, raw is set but " + "current/filepath/which_part is not set. " + "Must use -r and -c/-f/-p simultaneously.\n"); + return 1; + } + + if (common->envfilepath) { + e = printenv_from_file(common->envfilepath, arguments.raw, + arguments.output_fields); + free(common->envfilepath); + return e; } /* not in file mode */ @@ -704,9 +869,23 @@ static error_t bg_printenv(int argc, char **argv) return 1; } - dump_envs(); + if (arguments.current) { + if (!arguments.raw) { + fprintf(stdout, "Using latest config partition\n"); + } + dump_latest_env(arguments.raw, arguments.output_fields); + } else if (common->part_specified) { + if (!arguments.raw) { + fprintf(stdout, "Using config partition #%d\n", + arguments.common.which_part); + } + dump_env_by_index(common->which_part, arguments.raw, + arguments.output_fields); + } else { + dump_envs(arguments.raw, arguments.output_fields); + } bgenv_finalize(); - return 0; + return e; } /* This is the entrypoint for the command bg_setenv. */ @@ -736,7 +915,7 @@ static error_t bg_setenv(int argc, char **argv) return e; } - if (arguments.auto_update && arguments.part_specified) { + if (arguments.auto_update && arguments.common.part_specified) { fprintf(stderr, "Error, both automatic and manual partition " "selection. Cannot use -p and -u " "simultaneously.\n"); @@ -763,7 +942,7 @@ static error_t bg_setenv(int argc, char **argv) } if (arguments.common.verbosity) { - dump_envs(); + dump_envs(false, MASK_ALL); } BGENV *env_new = NULL; @@ -806,8 +985,11 @@ static error_t bg_setenv(int argc, char **argv) bgenv_close(env_current); } else { - if (arguments.part_specified) { - env_new = bgenv_open_by_index(arguments.which_part); + if (arguments.common.part_specified) { + fprintf(stdout, "Using config partition #%d\n", + arguments.common.which_part); + env_new = bgenv_open_by_index( + arguments.common.which_part); } else { env_new = bgenv_open_latest(); } @@ -824,7 +1006,7 @@ static error_t bg_setenv(int argc, char **argv) if (arguments.common.verbosity) { fprintf(stdout, "New environment data:\n"); fprintf(stdout, "---------------------\n"); - dump_env(env_new->data); + dump_env(env_new->data, false, MASK_ALL); } if (!bgenv_write(env_new)) { fprintf(stderr, "Error storing environment.\n"); -- 2.33.0 -- You received this message because you are subscribed to the Google Groups "EFI Boot Guard" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/efibootguard-dev/20211103084039.274022-5-michael.adler%40siemens.com.
