From: Peter Jones <pjo...@redhat.com> This adds "efi-export-env VARIABLES" and "efi-load-env", which manipulate the environment block stored in the EFI variable GRUB_ENV-91376aff-cba6-42be-949d-06fde81128e8.
Signed-off-by: Peter Jones <pjo...@redhat.com> Fix rebase, cherry-pick issues, grub_efi_guid_t -> grub_guid_t, module name bug, missing return bug, memory leaks, formatting Add support for specifying multiple variables at once for efi-export-env. Signed-off-by: Glenn Washburn <developm...@efficientek.com> --- docs/grub.texi | 24 +++++ grub-core/Makefile.core.def | 6 ++ grub-core/commands/efi/env.c | 182 +++++++++++++++++++++++++++++++++++ grub-core/kern/efi/efi.c | 3 + grub-core/lib/envblk.c | 43 +++++++++ include/grub/lib/envblk.h | 3 + 6 files changed, 261 insertions(+) create mode 100644 grub-core/commands/efi/env.c diff --git a/docs/grub.texi b/docs/grub.texi index 2ea6c56d100e..4d53777dce14 100644 --- a/docs/grub.texi +++ b/docs/grub.texi @@ -6391,6 +6391,8 @@ you forget a command, you can run the command @command{help} * distrust:: Remove a pubkey from trusted keys * drivemap:: Map a drive to another * echo:: Display a line of text +* efi-export-env:: Export environment variable to UEFI +* efi-load-env:: Load the grub environment from UEFI * efitextmode:: Set/Get text output mode resolution * eval:: Evaluate agruments as GRUB commands * export:: Export an environment variable @@ -6860,6 +6862,28 @@ character will print that character. @end deffn +@node efi-export-env +@subsection efi-export-env + +@deffn Command efi-export-env varname [varname @dots{}] +Export the given GRUB variables to the GRUB_ENV UEFI variable as a GRUB +environment block. The contents of an existing environment block in GRUB_ENV +will be merged. So variables existing in GRUB_ENV but not given as arguements +will be unaffected by this command. +@end deffn + + +@node efi-load-env +@subsection efi-load-env + +@deffn Command efi-load-env +Load the grub environment from the GRUB_ENV UEFI variable. This will overwrite +existing variables if they exist in the environment block present in the UEFI +variable. Other existing GRUB variables will be unchanged. This can allow for +the persistence of state across boots. +@end deffn + + @node efitextmode @subsection efitextmode diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index 5699736dad20..640f9176d306 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -829,6 +829,12 @@ module = { enable = efi; }; +module = { + name = efienv; + common = commands/efi/env.c; + enable = efi; +}; + module = { name = efifwsetup; efi = commands/efi/efifwsetup.c; diff --git a/grub-core/commands/efi/env.c b/grub-core/commands/efi/env.c new file mode 100644 index 000000000000..b5775bcbd4bc --- /dev/null +++ b/grub-core/commands/efi/env.c @@ -0,0 +1,182 @@ +/* env.c - commands to load/export environment block to EFI variable */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2023 Free Software Foundation, Inc. + * + * GRUB 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 3 of the License, or + * (at your option) any later version. + * + * GRUB 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 GRUB. If not, see <http://www.gnu.org/licenses/>. + */ +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/efi/api.h> +#include <grub/efi/efi.h> +#include <grub/env.h> +#include <grub/lib/envblk.h> +#include <grub/command.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static const grub_guid_t grub_env_guid = GRUB_EFI_GRUB_VARIABLE_GUID; + +static grub_err_t +grub_efi_export_env (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + const char *value; + char *old_value; + struct grub_envblk envblk_s = { NULL, 0 }; + grub_envblk_t envblk = &envblk_s; + grub_err_t err = GRUB_ERR_NONE; + int i, changed = 1; + grub_efi_status_t status; + + grub_dprintf ("efienv", "argc:%d\n", argc); + for (i = 0; i < argc; i++) + grub_dprintf ("efienv", "argv[%d]: %s\n", i, argv[i]); + + if (argc < 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("variable names expected")); + + status = grub_efi_get_variable ("GRUB_ENV", &grub_env_guid, &envblk_s.size, + (void **) &envblk_s.buf); + if (envblk_s.buf == NULL || envblk_s.size < 1 || status != GRUB_EFI_SUCCESS) + { + char *buf = grub_malloc (DEFAULT_ENVBLK_SIZE + 1); + if (!buf) + return grub_errno; + + grub_memcpy (buf, GRUB_ENVBLK_SIGNATURE, sizeof (GRUB_ENVBLK_SIGNATURE) - 1); + grub_memset (buf + sizeof (GRUB_ENVBLK_SIGNATURE) - 1, '#', + DEFAULT_ENVBLK_SIZE - sizeof (GRUB_ENVBLK_SIGNATURE) + 1); + buf[DEFAULT_ENVBLK_SIZE] = '\0'; + + envblk_s.buf = buf; + envblk_s.size = DEFAULT_ENVBLK_SIZE; + } + else + { + char *buf = grub_realloc (envblk_s.buf, envblk_s.size + 1); + if (buf == NULL) + { + err = grub_errno; + goto cleanup; + } + + envblk_s.buf = buf; + envblk_s.buf[envblk_s.size] = '\0'; + } + + for (i = 0; i < argc; i++) + { + err = grub_envblk_get (envblk, argv[i], &old_value); + if (err != GRUB_ERR_NONE) + { + grub_dprintf ("efienv", "grub_envblk_get returned %d\n", err); + goto cleanup; + } + + value = grub_env_get (argv[i]); + if ((value == NULL && old_value == NULL) || + (value && old_value && !grub_strcmp (old_value, value))) + changed = 0; + + if (old_value != NULL) + grub_free (old_value); + + if (changed == 0) + { + grub_dprintf ("efienv", "No changes necessary for \"%s\"\n", argv[i]); + continue; + } + + if (value != NULL) + { + grub_dprintf ("efienv", "setting \"%s\" to \"%s\"\n", argv[i], value); + grub_envblk_set (envblk, argv[i], value); + } + else + { + grub_dprintf ("efienv", "deleting \"%s\" from envblk\n", argv[i]); + grub_envblk_delete (envblk, argv[0]); + } + + grub_dprintf ("efienv", "envblk is %" PRIuGRUB_SIZE " bytes:\n\"%s\"\n", + envblk_s.size, envblk_s.buf); + } + + grub_dprintf ("efienv", "removing GRUB_ENV\n"); + status = grub_efi_set_variable ("GRUB_ENV", &grub_env_guid, NULL, 0); + if (status != GRUB_EFI_SUCCESS) + grub_dprintf ("efienv", "removal returned 0x%" PRIxGRUB_EFI_UINTN_T "\n", status); + + grub_dprintf ("efienv", "setting GRUB_ENV\n"); + status = grub_efi_set_variable ("GRUB_ENV", &grub_env_guid, + envblk_s.buf, envblk_s.size); + if (status != GRUB_EFI_SUCCESS) + grub_dprintf ("efienv", "setting GRUB_ENV returned %" PRIxGRUB_EFI_UINTN_T "\n", status); + + cleanup: + grub_free (envblk_s.buf); + + return err; +} + +static int +set_var (const char *name, const char *value, + void *whitelist __attribute__((__unused__))) +{ + grub_env_set (name, value); + return 0; +} + +static grub_err_t +grub_efi_load_env (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[] __attribute__((__unused__))) +{ + struct grub_envblk envblk_s = { NULL, 0 }; + grub_envblk_t envblk = &envblk_s; + + if (argc > 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unexpected argument")); + + grub_efi_get_variable ("GRUB_ENV", &grub_env_guid, &envblk_s.size, + (void **) &envblk_s.buf); + if (envblk_s.buf == NULL || envblk_s.size < 1) + return 0; + + grub_envblk_iterate (envblk, NULL, set_var); + grub_free (envblk_s.buf); + + return GRUB_ERR_NONE; +} + +static grub_command_t export_cmd, loadenv_cmd; + +GRUB_MOD_INIT(efienv) +{ + export_cmd = grub_register_command ("efi-export-env", grub_efi_export_env, + N_("VARIABLE_NAME [VARIABLE_NAME ...]"), + N_("Export environment variable to UEFI.")); + loadenv_cmd = grub_register_command ("efi-load-env", grub_efi_load_env, + NULL, N_("Load the grub environment from UEFI.")); +} + +GRUB_MOD_FINI(efienv) +{ + grub_unregister_command (export_cmd); + grub_unregister_command (loadenv_cmd); +} diff --git a/grub-core/kern/efi/efi.c b/grub-core/kern/efi/efi.c index b93ae3aba12d..38e508ff6c28 100644 --- a/grub-core/kern/efi/efi.c +++ b/grub-core/kern/efi/efi.c @@ -222,6 +222,9 @@ grub_efi_set_variable_with_attributes (const char *var, const grub_guid_t *guid, if (status == GRUB_EFI_SUCCESS) return GRUB_ERR_NONE; + if (status == GRUB_EFI_NOT_FOUND && datasize == 0) + return GRUB_ERR_NONE; + return grub_error (GRUB_ERR_IO, "could not set EFI variable `%s'", var); } diff --git a/grub-core/lib/envblk.c b/grub-core/lib/envblk.c index 2e4e78b132d4..ed7eb258e1c7 100644 --- a/grub-core/lib/envblk.c +++ b/grub-core/lib/envblk.c @@ -223,6 +223,49 @@ grub_envblk_delete (grub_envblk_t envblk, const char *name) } } +struct get_var_state { + const char * const name; + char * value; + int found; +}; + +static int +get_var (const char * const name, const char * const value, void *statep) +{ + struct get_var_state *state = (struct get_var_state *) statep; + + if (!grub_strcmp (state->name, name)) + { + state->found = 1; + state->value = grub_strdup (value); + if (state->value == NULL) + grub_errno = grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory")); + + return 1; + } + + return 0; +} + +grub_err_t +grub_envblk_get (grub_envblk_t envblk, const char * const name, char ** const value) +{ + struct get_var_state state = { + .name = name, + .value = NULL, + .found = 0, + }; + + grub_envblk_iterate (envblk, (void *) &state, get_var); + + *value = state.value; + + if (state.found && state.value == NULL) + return grub_errno; + + return GRUB_ERR_NONE; +} + void grub_envblk_iterate (grub_envblk_t envblk, void *hook_data, diff --git a/include/grub/lib/envblk.h b/include/grub/lib/envblk.h index c3e655921709..ab969af24612 100644 --- a/include/grub/lib/envblk.h +++ b/include/grub/lib/envblk.h @@ -22,6 +22,8 @@ #define GRUB_ENVBLK_SIGNATURE "# GRUB Environment Block\n" #define GRUB_ENVBLK_DEFCFG "grubenv" +#define DEFAULT_ENVBLK_SIZE 1024 + #ifndef ASM_FILE struct grub_envblk @@ -33,6 +35,7 @@ typedef struct grub_envblk *grub_envblk_t; grub_envblk_t grub_envblk_open (char *buf, grub_size_t size); int grub_envblk_set (grub_envblk_t envblk, const char *name, const char *value); +grub_err_t grub_envblk_get (grub_envblk_t envblk, const char * const name, char ** const value); void grub_envblk_delete (grub_envblk_t envblk, const char *name); void grub_envblk_iterate (grub_envblk_t envblk, void *hook_data, -- 2.34.1 _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel