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 b39b72230c6f..730e8c8d75a4 100644
--- a/docs/grub.texi
+++ b/docs/grub.texi
@@ -4324,6 +4324,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
@@ -4784,6 +4786,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 0b447dd6dc67..a6e6b8cb135a 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -825,6 +825,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 4b60495333e0..2b6bd7e2413d 100644
--- a/grub-core/kern/efi/efi.c
+++ b/grub-core/kern/efi/efi.c
@@ -224,6 +224,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 ce55ad1f8118..443297779839 100644
--- a/grub-core/lib/envblk.c
+++ b/grub-core/lib/envblk.c
@@ -214,6 +214,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

Reply via email to