diff options
Diffstat (limited to 'debian/patches/efi-variable-storage-minimise-writes.patch')
-rw-r--r-- | debian/patches/efi-variable-storage-minimise-writes.patch | 895 |
1 files changed, 895 insertions, 0 deletions
diff --git a/debian/patches/efi-variable-storage-minimise-writes.patch b/debian/patches/efi-variable-storage-minimise-writes.patch new file mode 100644 index 0000000..c06dbe8 --- /dev/null +++ b/debian/patches/efi-variable-storage-minimise-writes.patch @@ -0,0 +1,895 @@ +From 6e3841fc4abf50d16d819c39ac5de74ccc4de225 Mon Sep 17 00:00:00 2001 +From: Colin Watson <cjwatson@ubuntu.com> +Date: Mon, 11 Mar 2019 11:17:43 +0000 +Subject: Minimise writes to EFI variable storage + +Some UEFI firmware is easily provoked into running out of space in its +variable storage. This is usually due to certain kernel drivers (e.g. +pstore), but regardless of the cause it can cause grub-install to fail +because it currently asks efibootmgr to delete and re-add entries, and +the deletion often doesn't result in an immediate garbage collection. +Writing variables frequently also increases wear on the NVRAM which may +have limited write cycles. For these reasons, it's desirable to find a +way to minimise writes while still allowing grub-install to ensure that +a suitable boot entry exists. + +Unfortunately, efibootmgr doesn't offer an interface that would let +grub-install do this. It doesn't in general make very much effort to +minimise writes; it doesn't allow modifying an existing Boot* variable +entry, except in certain limited ways; and current versions don't have a +way to export the expected variable data so that grub-install can +compare it to the current data. While it would be possible (and perhaps +desirable?) to add at least some of this to efibootmgr, that would still +leave the problem that there isn't a good upstreamable way for +grub-install to guarantee that it has a new enough version of +efibootmgr. In any case, it's cumbersome and slow for grub-install to +have to fork efibootmgr to get things done. + +Fortunately, a few years ago Peter Jones helpfully factored out a +substantial part of efibootmgr to the efivar and efiboot libraries, and +so it's now possible to have grub-install use those directly. We still +have to use some code from efibootmgr, but much less than would +previously have been necessary. + +grub-install now reuses existing boot entries where possible, and avoids +writing to variables when the new contents are the same as the old +contents. In the common upgrade case where nothing needs to change, it +no longer writes to NVRAM at all. It's also now slightly faster, since +using libefivar is faster than forking efibootmgr. + +Fixes Debian bug #891434. + +Signed-off-by: Colin Watson <cjwatson@ubuntu.com> + +Bug-Debian: https://bugs.debian.org/891434 +Forwarded: https://lists.gnu.org/archive/html/grub-devel/2019-03/msg00119.html +Last-Update: 2019-03-23 + +Patch-Name: efi-variable-storage-minimise-writes.patch +--- + INSTALL | 5 + + Makefile.util.def | 20 ++ + configure.ac | 12 + + grub-core/osdep/efivar.c | 3 + + grub-core/osdep/unix/efivar.c | 508 ++++++++++++++++++++++++++++++++ + grub-core/osdep/unix/platform.c | 100 +------ + include/grub/util/install.h | 5 + + util/grub-install.c | 4 +- + 8 files changed, 562 insertions(+), 95 deletions(-) + create mode 100644 grub-core/osdep/efivar.c + create mode 100644 grub-core/osdep/unix/efivar.c + +diff --git a/INSTALL b/INSTALL +index 79a0af7d9..590b52482 100644 +--- a/INSTALL ++++ b/INSTALL +@@ -23,6 +23,11 @@ configuring the GRUB. + * Other standard GNU/Unix tools + * a libc with large file support (e.g. glibc 2.1 or later) + ++On Unix-based systems, you also need: ++ ++* libefivar (recommended) ++* libefiboot (recommended; your OS may ship this together with libefivar) ++ + On GNU/Linux, you also need: + + * libdevmapper 1.02.34 or later (recommended) +diff --git a/Makefile.util.def b/Makefile.util.def +index 27f948291..a3d134b03 100644 +--- a/Makefile.util.def ++++ b/Makefile.util.def +@@ -570,6 +570,8 @@ program = { + common = grub-core/osdep/compress.c; + extra_dist = grub-core/osdep/unix/compress.c; + extra_dist = grub-core/osdep/basic/compress.c; ++ common = grub-core/osdep/efivar.c; ++ extra_dist = grub-core/osdep/unix/efivar.c; + common = util/editenv.c; + common = grub-core/osdep/blocklist.c; + common = grub-core/osdep/config.c; +@@ -583,12 +585,15 @@ program = { + common = grub-core/kern/emu/argp_common.c; + common = grub-core/osdep/init.c; + ++ cflags = '$(EFIVAR_CFLAGS)'; ++ + ldadd = '$(LIBLZMA)'; + ldadd = libgrubmods.a; + ldadd = libgrubgcry.a; + ldadd = libgrubkern.a; + ldadd = grub-core/lib/gnulib/libgnu.a; + ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)'; ++ ldadd = '$(EFIVAR_LIBS)'; + + condition = COND_HAVE_EXEC; + }; +@@ -617,6 +622,8 @@ program = { + extra_dist = grub-core/osdep/basic/no_platform.c; + extra_dist = grub-core/osdep/unix/platform.c; + common = grub-core/osdep/compress.c; ++ common = grub-core/osdep/efivar.c; ++ extra_dist = grub-core/osdep/unix/efivar.c; + common = util/editenv.c; + common = grub-core/osdep/blocklist.c; + common = grub-core/osdep/config.c; +@@ -630,12 +637,15 @@ program = { + common = grub-core/kern/emu/argp_common.c; + common = grub-core/osdep/init.c; + ++ cflags = '$(EFIVAR_CFLAGS)'; ++ + ldadd = '$(LIBLZMA)'; + ldadd = libgrubmods.a; + ldadd = libgrubgcry.a; + ldadd = libgrubkern.a; + ldadd = grub-core/lib/gnulib/libgnu.a; + ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)'; ++ ldadd = '$(EFIVAR_LIBS)'; + }; + + program = { +@@ -657,6 +667,8 @@ program = { + common = grub-core/osdep/platform.c; + common = grub-core/osdep/platform_unix.c; + common = grub-core/osdep/compress.c; ++ common = grub-core/osdep/efivar.c; ++ extra_dist = grub-core/osdep/unix/efivar.c; + common = util/editenv.c; + common = grub-core/osdep/blocklist.c; + common = grub-core/osdep/config.c; +@@ -669,12 +681,15 @@ program = { + common = grub-core/kern/emu/argp_common.c; + common = grub-core/osdep/init.c; + ++ cflags = '$(EFIVAR_CFLAGS)'; ++ + ldadd = '$(LIBLZMA)'; + ldadd = libgrubmods.a; + ldadd = libgrubgcry.a; + ldadd = libgrubkern.a; + ldadd = grub-core/lib/gnulib/libgnu.a; + ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)'; ++ ldadd = '$(EFIVAR_LIBS)'; + }; + + program = { +@@ -696,6 +711,8 @@ program = { + common = grub-core/osdep/platform.c; + common = grub-core/osdep/platform_unix.c; + common = grub-core/osdep/compress.c; ++ common = grub-core/osdep/efivar.c; ++ extra_dist = grub-core/osdep/unix/efivar.c; + common = util/editenv.c; + common = grub-core/osdep/blocklist.c; + common = grub-core/osdep/config.c; +@@ -705,12 +722,15 @@ program = { + common = grub-core/kern/emu/argp_common.c; + common = grub-core/osdep/init.c; + ++ cflags = '$(EFIVAR_CFLAGS)'; ++ + ldadd = '$(LIBLZMA)'; + ldadd = libgrubmods.a; + ldadd = libgrubgcry.a; + ldadd = libgrubkern.a; + ldadd = grub-core/lib/gnulib/libgnu.a; + ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBUTIL) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)'; ++ ldadd = '$(EFIVAR_LIBS)'; + }; + + script = { +diff --git a/configure.ac b/configure.ac +index e11df6bc5..b3fb7437e 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -452,6 +452,18 @@ AC_CHECK_HEADER([util.h], [ + ]) + AC_SUBST([LIBUTIL]) + ++case "$host_os" in ++ cygwin | windows* | mingw32* | aros*) ++ ;; ++ *) ++ # For setting EFI variables in grub-install. ++ PKG_CHECK_MODULES([EFIVAR], [efivar efiboot], [ ++ AC_DEFINE([HAVE_EFIVAR], [1], ++ [Define to 1 if you have the efivar and efiboot libraries.]) ++ ], [:]) ++ ;; ++esac ++ + AC_CACHE_CHECK([whether -Wtrampolines work], [grub_cv_host_cc_wtrampolines], [ + SAVED_CFLAGS="$CFLAGS" + CFLAGS="$HOST_CFLAGS -Wtrampolines -Werror" +diff --git a/grub-core/osdep/efivar.c b/grub-core/osdep/efivar.c +new file mode 100644 +index 000000000..d2750e252 +--- /dev/null ++++ b/grub-core/osdep/efivar.c +@@ -0,0 +1,3 @@ ++#if !defined (__MINGW32__) && !defined (__CYGWIN__) && !defined (__AROS__) ++#include "unix/efivar.c" ++#endif +diff --git a/grub-core/osdep/unix/efivar.c b/grub-core/osdep/unix/efivar.c +new file mode 100644 +index 000000000..4a58328b4 +--- /dev/null ++++ b/grub-core/osdep/unix/efivar.c +@@ -0,0 +1,508 @@ ++/* ++ * GRUB -- GRand Unified Bootloader ++ * Copyright (C) 2013,2019 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/>. ++ */ ++ ++/* Contains portions derived from efibootmgr, licensed as follows: ++ * ++ * Copyright (C) 2001-2004 Dell, Inc. <Matt_Domsch@dell.com> ++ * Copyright 2015-2016 Red Hat, Inc. <pjones@redhat.com> ++ * ++ * This program 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 2 of the License, or ++ * (at your option) any later version. ++ */ ++ ++#include <config.h> ++ ++#ifdef HAVE_EFIVAR ++ ++#include <grub/util/install.h> ++#include <grub/emu/hostdisk.h> ++#include <grub/util/misc.h> ++#include <grub/list.h> ++#include <grub/misc.h> ++#include <grub/emu/exec.h> ++#include <sys/types.h> ++#include <ctype.h> ++#include <errno.h> ++#include <stdlib.h> ++#include <string.h> ++ ++#include <efiboot.h> ++#include <efivar.h> ++ ++struct efi_variable { ++ struct efi_variable *next; ++ struct efi_variable **prev; ++ char *name; ++ efi_guid_t guid; ++ uint8_t *data; ++ size_t data_size; ++ uint32_t attributes; ++ int num; ++}; ++ ++/* Boot option attributes. */ ++#define LOAD_OPTION_ACTIVE 0x00000001 ++ ++/* GUIDs. */ ++#define BLKX_UNKNOWN_GUID \ ++ EFI_GUID (0x47c7b225, 0xc42a, 0x11d2, 0x8e57, 0x00, 0xa0, 0xc9, 0x69, \ ++ 0x72, 0x3b) ++ ++/* Log all errors recorded by libefivar/libefiboot. */ ++static void ++show_efi_errors (void) ++{ ++ int i; ++ int saved_errno = errno; ++ ++ for (i = 0; ; ++i) ++ { ++ char *filename, *function, *message = NULL; ++ int line, error = 0, rc; ++ ++ rc = efi_error_get (i, &filename, &function, &line, &message, &error); ++ if (rc < 0) ++ /* Give up. The caller is going to log an error anyway. */ ++ break; ++ if (rc == 0) ++ /* No more errors. */ ++ break; ++ grub_util_warn ("%s: %s: %s", function, message, strerror (error)); ++ } ++ ++ efi_error_clear (); ++ errno = saved_errno; ++} ++ ++static struct efi_variable * ++new_efi_variable (void) ++{ ++ struct efi_variable *new = xmalloc (sizeof (*new)); ++ memset (new, 0, sizeof (*new)); ++ return new; ++} ++ ++static struct efi_variable * ++new_boot_variable (void) ++{ ++ struct efi_variable *new = new_efi_variable (); ++ new->guid = EFI_GLOBAL_GUID; ++ new->attributes = EFI_VARIABLE_NON_VOLATILE | ++ EFI_VARIABLE_BOOTSERVICE_ACCESS | ++ EFI_VARIABLE_RUNTIME_ACCESS; ++ return new; ++} ++ ++static void ++free_efi_variable (struct efi_variable *entry) ++{ ++ if (entry) ++ { ++ free (entry->name); ++ free (entry->data); ++ free (entry); ++ } ++} ++ ++static int ++read_efi_variable (const char *name, struct efi_variable **entry) ++{ ++ struct efi_variable *new = new_efi_variable (); ++ int rc; ++ ++ rc = efi_get_variable (EFI_GLOBAL_GUID, name, ++ &new->data, &new->data_size, &new->attributes); ++ if (rc < 0) ++ { ++ free_efi_variable (new); ++ new = NULL; ++ } ++ ++ if (new) ++ { ++ /* Latest Apple firmware sets the high bit which appears invalid ++ to the Linux kernel if we write it back, so let's zero it out if it ++ is set since it would be invalid to set it anyway. */ ++ new->attributes = new->attributes & ~(1 << 31); ++ ++ new->name = xstrdup (name); ++ new->guid = EFI_GLOBAL_GUID; ++ } ++ ++ *entry = new; ++ return rc; ++} ++ ++/* Set an EFI variable, but only if it differs from the current value. ++ Some firmware implementations are liable to fill up flash space if we set ++ variables unnecessarily, so try to keep write activity to a minimum. */ ++static int ++set_efi_variable (const char *name, struct efi_variable *entry) ++{ ++ struct efi_variable *old = NULL; ++ int rc = 0; ++ ++ read_efi_variable (name, &old); ++ efi_error_clear (); ++ if (old && old->attributes == entry->attributes && ++ old->data_size == entry->data_size && ++ memcmp (old->data, entry->data, entry->data_size) == 0) ++ grub_util_info ("skipping unnecessary update of EFI variable %s", name); ++ else ++ { ++ rc = efi_set_variable (EFI_GLOBAL_GUID, name, ++ entry->data, entry->data_size, entry->attributes, ++ 0644); ++ if (rc < 0) ++ grub_util_warn (_("Cannot set EFI variable %s"), name); ++ } ++ free_efi_variable (old); ++ return rc; ++} ++ ++static int ++cmpvarbyname (const void *p1, const void *p2) ++{ ++ const struct efi_variable *var1 = *(const struct efi_variable **)p1; ++ const struct efi_variable *var2 = *(const struct efi_variable **)p2; ++ return strcmp (var1->name, var2->name); ++} ++ ++static int ++read_boot_variables (struct efi_variable **varlist) ++{ ++ int rc; ++ efi_guid_t *guid = NULL; ++ char *name = NULL; ++ struct efi_variable **newlist = NULL; ++ int nentries = 0; ++ int i; ++ ++ while ((rc = efi_get_next_variable_name (&guid, &name)) > 0) ++ { ++ const char *snum = name + sizeof ("Boot") - 1; ++ struct efi_variable *var = NULL; ++ unsigned int num; ++ ++ if (memcmp (guid, &efi_guid_global, sizeof (efi_guid_global)) != 0 || ++ strncmp (name, "Boot", sizeof ("Boot") - 1) != 0 || ++ !grub_isxdigit (snum[0]) || !grub_isxdigit (snum[1]) || ++ !grub_isxdigit (snum[2]) || !grub_isxdigit (snum[3])) ++ continue; ++ ++ rc = read_efi_variable (name, &var); ++ if (rc < 0) ++ break; ++ ++ if (sscanf (var->name, "Boot%04X-%*s", &num) == 1 && num < 65536) ++ var->num = num; ++ ++ newlist = xrealloc (newlist, (++nentries) * sizeof (*newlist)); ++ newlist[nentries - 1] = var; ++ } ++ if (rc == 0 && newlist) ++ { ++ qsort (newlist, nentries, sizeof (*newlist), cmpvarbyname); ++ for (i = nentries - 1; i >= 0; --i) ++ grub_list_push (GRUB_AS_LIST_P (varlist), GRUB_AS_LIST (newlist[i])); ++ } ++ else if (newlist) ++ { ++ for (i = 0; i < nentries; ++i) ++ free_efi_variable (newlist[i]); ++ free (newlist); ++ } ++ return rc; ++} ++ ++#define GET_ORDER(data, i) \ ++ ((uint16_t) ((data)[(i) * 2]) + ((data)[(i) * 2 + 1] << 8)) ++#define SET_ORDER(data, i, num) \ ++ do { \ ++ (data)[(i) * 2] = (num) & 0xFF; \ ++ (data)[(i) * 2 + 1] = ((num) >> 8) & 0xFF; \ ++ } while (0) ++ ++static void ++remove_from_boot_order (struct efi_variable *order, uint16_t num) ++{ ++ unsigned int old_i, new_i; ++ ++ /* We've got an array (in order->data) of the order. Squeeze out any ++ instance of the entry we're deleting by shifting the remainder down. */ ++ for (old_i = 0, new_i = 0; ++ old_i < order->data_size / sizeof (uint16_t); ++ ++old_i) ++ { ++ uint16_t old_num = GET_ORDER (order->data, old_i); ++ if (old_num != num) ++ { ++ if (new_i != old_i) ++ SET_ORDER (order->data, new_i, old_num); ++ ++new_i; ++ } ++ } ++ ++ order->data_size = new_i * sizeof (uint16_t); ++} ++ ++static void ++add_to_boot_order (struct efi_variable *order, uint16_t num) ++{ ++ int i; ++ size_t new_data_size; ++ uint8_t *new_data; ++ ++ /* Check whether this entry is already in the boot order. If it is, leave ++ it alone. */ ++ for (i = 0; i < order->data_size / sizeof (uint16_t); ++i) ++ if (GET_ORDER (order->data, i) == num) ++ return; ++ ++ new_data_size = order->data_size + sizeof (uint16_t); ++ new_data = xmalloc (new_data_size); ++ SET_ORDER (new_data, 0, num); ++ memcpy (new_data + sizeof (uint16_t), order->data, order->data_size); ++ free (order->data); ++ order->data = new_data; ++ order->data_size = new_data_size; ++} ++ ++static int ++find_free_boot_num (struct efi_variable *entries) ++{ ++ int num_vars = 0, i; ++ struct efi_variable *entry; ++ ++ FOR_LIST_ELEMENTS (entry, entries) ++ ++num_vars; ++ ++ if (num_vars == 0) ++ return 0; ++ ++ /* O(n^2), but n is small and this is easy. */ ++ for (i = 0; i < num_vars; ++i) ++ { ++ int found = 0; ++ FOR_LIST_ELEMENTS (entry, entries) ++ { ++ if (entry->num == i) ++ { ++ found = 1; ++ break; ++ } ++ } ++ if (!found) ++ return i; ++ } ++ ++ return i; ++} ++ ++static int ++get_edd_version (void) ++{ ++ efi_guid_t blkx_guid = BLKX_UNKNOWN_GUID; ++ uint8_t *data = NULL; ++ size_t data_size = 0; ++ uint32_t attributes; ++ efidp_header *path; ++ int rc; ++ ++ rc = efi_get_variable (blkx_guid, "blk0", &data, &data_size, &attributes); ++ if (rc < 0) ++ return rc; ++ ++ path = (efidp_header *) data; ++ if (path->type == 2 && path->subtype == 1) ++ return 3; ++ return 1; ++} ++ ++static struct efi_variable * ++make_boot_variable (int num, const char *disk, int part, const char *loader, ++ const char *label) ++{ ++ struct efi_variable *entry = new_boot_variable (); ++ uint32_t options; ++ uint32_t edd10_devicenum; ++ ssize_t dp_needed, loadopt_needed; ++ efidp dp = NULL; ++ ++ options = EFIBOOT_ABBREV_HD; ++ switch (get_edd_version ()) { ++ case 1: ++ options = EFIBOOT_ABBREV_EDD10; ++ break; ++ case 3: ++ options = EFIBOOT_ABBREV_NONE; ++ break; ++ } ++ ++ /* This may not be the right disk; but it's probably only an issue on very ++ old hardware anyway. */ ++ edd10_devicenum = 0x80; ++ ++ dp_needed = efi_generate_file_device_path_from_esp (NULL, 0, disk, part, ++ loader, options, ++ edd10_devicenum); ++ if (dp_needed < 0) ++ goto err; ++ ++ dp = xmalloc (dp_needed); ++ dp_needed = efi_generate_file_device_path_from_esp ((uint8_t *) dp, ++ dp_needed, disk, part, ++ loader, options, ++ edd10_devicenum); ++ if (dp_needed < 0) ++ goto err; ++ ++ loadopt_needed = efi_loadopt_create (NULL, 0, LOAD_OPTION_ACTIVE, ++ dp, dp_needed, (unsigned char *) label, ++ NULL, 0); ++ if (loadopt_needed < 0) ++ goto err; ++ entry->data_size = loadopt_needed; ++ entry->data = xmalloc (entry->data_size); ++ loadopt_needed = efi_loadopt_create (entry->data, entry->data_size, ++ LOAD_OPTION_ACTIVE, dp, dp_needed, ++ (unsigned char *) label, NULL, 0); ++ if (loadopt_needed < 0) ++ goto err; ++ ++ entry->name = xasprintf ("Boot%04X", num); ++ entry->num = num; ++ ++ return entry; ++ ++err: ++ free_efi_variable (entry); ++ free (dp); ++ return NULL; ++} ++ ++int ++grub_install_efivar_register_efi (grub_device_t efidir_grub_dev, ++ const char *efifile_path, ++ const char *efi_distributor) ++{ ++ const char *efidir_disk; ++ int efidir_part; ++ struct efi_variable *entries = NULL, *entry; ++ struct efi_variable *order; ++ int entry_num = -1; ++ int rc; ++ ++ efidir_disk = grub_util_biosdisk_get_osdev (efidir_grub_dev->disk); ++ efidir_part = efidir_grub_dev->disk->partition ? efidir_grub_dev->disk->partition->number + 1 : 1; ++ ++#ifdef __linux__ ++ /* ++ * Linux uses efivarfs (mounted on /sys/firmware/efi/efivars) to access the ++ * EFI variable store. Some legacy systems may still use the deprecated ++ * efivars interface (accessed through /sys/firmware/efi/vars). Where both ++ * are present, libefivar will use the former in preference, so attempting ++ * to load efivars will not interfere with later operations. ++ */ ++ grub_util_exec_redirect_all ((const char * []){ "modprobe", "efivars", NULL }, ++ NULL, NULL, "/dev/null"); ++#endif ++ ++ if (!efi_variables_supported ()) ++ { ++ grub_util_warn ("%s", ++ _("EFI variables are not supported on this system.")); ++ /* Let the user continue. Perhaps they can still arrange to boot GRUB ++ manually. */ ++ return 0; ++ } ++ ++ rc = read_boot_variables (&entries); ++ if (rc < 0) ++ { ++ grub_util_warn ("%s", _("Cannot read EFI Boot* variables")); ++ goto err; ++ } ++ rc = read_efi_variable ("BootOrder", &order); ++ if (rc < 0) ++ { ++ order = new_boot_variable (); ++ order->name = xstrdup ("BootOrder"); ++ efi_error_clear (); ++ } ++ ++ /* Delete old entries from the same distributor. */ ++ FOR_LIST_ELEMENTS (entry, entries) ++ { ++ efi_load_option *load_option = (efi_load_option *) entry->data; ++ const char *label; ++ ++ if (entry->num < 0) ++ continue; ++ label = (const char *) efi_loadopt_desc (load_option, entry->data_size); ++ if (strcasecmp (label, efi_distributor) != 0) ++ continue; ++ ++ /* To avoid problems with some firmware implementations, reuse the first ++ matching variable we find rather than deleting and recreating it. */ ++ if (entry_num == -1) ++ entry_num = entry->num; ++ else ++ { ++ grub_util_info ("deleting superfluous EFI variable %s (%s)", ++ entry->name, label); ++ rc = efi_del_variable (EFI_GLOBAL_GUID, entry->name); ++ if (rc < 0) ++ { ++ grub_util_warn (_("Cannot delete EFI variable %s"), entry->name); ++ goto err; ++ } ++ } ++ ++ remove_from_boot_order (order, (uint16_t) entry->num); ++ } ++ ++ if (entry_num == -1) ++ entry_num = find_free_boot_num (entries); ++ entry = make_boot_variable (entry_num, efidir_disk, efidir_part, ++ efifile_path, efi_distributor); ++ if (!entry) ++ goto err; ++ ++ grub_util_info ("setting EFI variable %s", entry->name); ++ rc = set_efi_variable (entry->name, entry); ++ if (rc < 0) ++ goto err; ++ ++ add_to_boot_order (order, (uint16_t) entry_num); ++ ++ grub_util_info ("setting EFI variable BootOrder"); ++ rc = set_efi_variable ("BootOrder", order); ++ if (rc < 0) ++ goto err; ++ ++ return 0; ++ ++err: ++ show_efi_errors (); ++ return errno; ++} ++ ++#endif /* HAVE_EFIVAR */ +diff --git a/grub-core/osdep/unix/platform.c b/grub-core/osdep/unix/platform.c +index 9c439326a..b561174ea 100644 +--- a/grub-core/osdep/unix/platform.c ++++ b/grub-core/osdep/unix/platform.c +@@ -19,15 +19,12 @@ + #include <config.h> + + #include <grub/util/install.h> +-#include <grub/emu/hostdisk.h> + #include <grub/util/misc.h> + #include <grub/misc.h> + #include <grub/i18n.h> + #include <grub/emu/exec.h> + #include <sys/types.h> +-#include <dirent.h> + #include <string.h> +-#include <errno.h> + + static char * + get_ofpathname (const char *dev) +@@ -78,102 +75,19 @@ get_ofpathname (const char *dev) + dev); + } + +-static int +-grub_install_remove_efi_entries_by_distributor (const char *efi_distributor) +-{ +- int fd; +- pid_t pid = grub_util_exec_pipe ((const char * []){ "efibootmgr", NULL }, &fd); +- char *line = NULL; +- size_t len = 0; +- int rc = 0; +- +- if (!pid) +- { +- grub_util_warn (_("Unable to open stream from %s: %s"), +- "efibootmgr", strerror (errno)); +- return errno; +- } +- +- FILE *fp = fdopen (fd, "r"); +- if (!fp) +- { +- grub_util_warn (_("Unable to open stream from %s: %s"), +- "efibootmgr", strerror (errno)); +- return errno; +- } +- +- line = xmalloc (80); +- len = 80; +- while (1) +- { +- int ret; +- char *bootnum; +- ret = getline (&line, &len, fp); +- if (ret == -1) +- break; +- if (grub_memcmp (line, "Boot", sizeof ("Boot") - 1) != 0 +- || line[sizeof ("Boot") - 1] < '0' +- || line[sizeof ("Boot") - 1] > '9') +- continue; +- if (!strcasestr (line, efi_distributor)) +- continue; +- bootnum = line + sizeof ("Boot") - 1; +- bootnum[4] = '\0'; +- if (!verbosity) +- rc = grub_util_exec ((const char * []){ "efibootmgr", "-q", +- "-b", bootnum, "-B", NULL }); +- else +- rc = grub_util_exec ((const char * []){ "efibootmgr", +- "-b", bootnum, "-B", NULL }); +- } +- +- free (line); +- return rc; +-} +- + int + grub_install_register_efi (grub_device_t efidir_grub_dev, + const char *efifile_path, + const char *efi_distributor) + { +- const char * efidir_disk; +- int efidir_part; +- int ret; +- efidir_disk = grub_util_biosdisk_get_osdev (efidir_grub_dev->disk); +- efidir_part = efidir_grub_dev->disk->partition ? efidir_grub_dev->disk->partition->number + 1 : 1; +- +- if (grub_util_exec_redirect_null ((const char * []){ "efibootmgr", "--version", NULL })) +- { +- /* TRANSLATORS: This message is shown when required executable `%s' +- isn't found. */ +- grub_util_error (_("%s: not found"), "efibootmgr"); +- } +- +- /* On Linux, we need the efivars kernel modules. */ +-#ifdef __linux__ +- grub_util_exec ((const char * []){ "modprobe", "-q", "efivars", NULL }); ++#ifdef HAVE_EFIVAR ++ return grub_install_efivar_register_efi (efidir_grub_dev, efifile_path, ++ efi_distributor); ++#else ++ grub_util_error ("%s", ++ _("GRUB was not built with efivar support; " ++ "cannot register EFI boot entry")); + #endif +- /* Delete old entries from the same distributor. */ +- ret = grub_install_remove_efi_entries_by_distributor (efi_distributor); +- if (ret) +- return ret; +- +- char *efidir_part_str = xasprintf ("%d", efidir_part); +- +- if (!verbosity) +- ret = grub_util_exec ((const char * []){ "efibootmgr", "-q", +- "-c", "-d", efidir_disk, +- "-p", efidir_part_str, "-w", +- "-L", efi_distributor, "-l", +- efifile_path, NULL }); +- else +- ret = grub_util_exec ((const char * []){ "efibootmgr", +- "-c", "-d", efidir_disk, +- "-p", efidir_part_str, "-w", +- "-L", efi_distributor, "-l", +- efifile_path, NULL }); +- free (efidir_part_str); +- return ret; + } + + void +diff --git a/include/grub/util/install.h b/include/grub/util/install.h +index 135ba48d2..134b862ec 100644 +--- a/include/grub/util/install.h ++++ b/include/grub/util/install.h +@@ -226,6 +226,11 @@ grub_install_get_default_x86_platform (void); + const char * + grub_install_get_default_powerpc_machtype (void); + ++int ++grub_install_efivar_register_efi (grub_device_t efidir_grub_dev, ++ const char *efifile_path, ++ const char *efi_distributor); ++ + int + grub_install_register_efi (grub_device_t efidir_grub_dev, + const char *efifile_path, +diff --git a/util/grub-install.c b/util/grub-install.c +index 58f1453ba..05b695226 100644 +--- a/util/grub-install.c ++++ b/util/grub-install.c +@@ -2086,7 +2086,7 @@ main (int argc, char *argv[]) + "\\System\\Library\\CoreServices", + efi_distributor); + if (ret) +- grub_util_error (_("efibootmgr failed to register the boot entry: %s"), ++ grub_util_error (_("failed to register the EFI boot entry: %s"), + strerror (ret)); + } + +@@ -2203,7 +2203,7 @@ main (int argc, char *argv[]) + ret = grub_install_register_efi (efidir_grub_dev, + efifile_path, efi_distributor); + if (ret) +- grub_util_error (_("efibootmgr failed to register the boot entry: %s"), ++ grub_util_error (_("failed to register the EFI boot entry: %s"), + strerror (ret)); + } + break; |