sd-boot: add support for a sysfail entry

Add support for a sysfail boot entry. Sysfail boot entries can be
used for optional tweaking the automatic selection order in case a
failure state of the system in some form is detected (boot firmware
failure etc).

The EFI variable `LoaderEntrySysFail` holds the boot loader entry to
be used in the event of a system failure. If a failure occurs, the reason
will be stored in the `LoaderSysFailReason` EFI variable.

sysfail_check() expected to be extented to support possible
conditions when we should boot sysfail("recovery") boot entry.

Signed-off-by: Igor Opaniuk <igor.opaniuk@foundries.io>
This commit is contained in:
Igor Opaniuk
2025-05-12 15:37:46 +02:00
parent bd543bca11
commit 004e3e4082
8 changed files with 83 additions and 0 deletions
+2
View File
@@ -102,6 +102,8 @@ Some EFI variables control the loader or exported the loaders state to the start
| EFI Variables |
|---------------|------------------------|-------------------------------|
| LoaderEntryDefault | entry identifier to select as default at bootup | non-volatile |
| LoaderEntrySysFail | sysfail entry identifier | non-volatile |
| LoaderSysFailReason | system failure reason | volatile |
| LoaderConfigTimeout | timeout in seconds to show the menu | non-volatile |
| LoaderEntryOneShot | entry identifier to select at the next and only the next bootup | non-volatile |
| LoaderDeviceIdentifier | list of identifiers of the volume the loader was started from | volatile |
+12
View File
@@ -58,6 +58,18 @@ variables. All EFI variables use the vendor UUID
* The EFI variable `LoaderEntryDefault` contains the default boot loader entry
to use. It contains a NUL-terminated boot loader entry identifier.
* The EFI variable `LoaderEntrySysFail` specifies the boot loader entry to be
used in case of a system failure. System failure (SysFail) boot entries can
optionally modify the automatic selection order in the event of a failure,
such as a boot firmware update failure with the failure status recorded in
the EFI system table. If a system failure occurs and `LoaderEntrySysFail` is set,
systemd-boot will use this boot entry and store the actual SysFail reason in
the `LoaderSysFailReason` EFI variable.
* The EFI variable `LoaderSysFailReason` contains the system failure reason.
This variable is used in cooperation with `LoaderEntrySysFail` boot entry.
If system failure doesn't occur `LoaderSysFailReason` is not set at all.
* Similarly, the EFI variable `LoaderEntryOneShot` contains the default boot
loader entry to use for a single following boot. It is set by the OS in order
to request booting into a specific menu entry on the following boot. When set
+1
View File
@@ -461,6 +461,7 @@
<varlistentry>
<term><varname>LoaderEntryDefault</varname></term>
<term><varname>LoaderEntrySysFail</varname></term>
<term><varname>LoaderEntryOneShot</varname></term>
<listitem><para>The identifier of the default boot loader entry. Set primarily by the OS and read by the boot
+36
View File
@@ -30,6 +30,7 @@
#include "shim.h"
#include "smbios.h"
#include "strv-fundamental.h"
#include "sysfail.h"
#include "ticks.h"
#include "tpm2-pcr.h"
#include "uki.h"
@@ -136,6 +137,7 @@ typedef struct {
char16_t *entry_default_efivar;
char16_t *entry_oneshot;
char16_t *entry_saved;
char16_t *entry_sysfail;
bool editor;
bool auto_entries;
bool auto_firmware;
@@ -149,6 +151,7 @@ typedef struct {
bool use_saved_entry;
bool use_saved_entry_efivar;
bool beep;
bool sysfail_occured;
int64_t console_mode;
int64_t console_mode_efivar;
} Config;
@@ -328,6 +331,8 @@ static void print_status(Config *config, char16_t *loaded_image_path) {
printf(" default (EFI var): %ls\n", config->entry_default_efivar);
if (config->entry_oneshot)
printf(" default (one-shot): %ls\n", config->entry_oneshot);
if (config->entry_sysfail)
printf(" sysfail: %ls\n", config->entry_sysfail);
if (config->entry_saved)
printf(" saved entry: %ls\n", config->entry_saved);
printf(" editor: %ls\n", yes_no(config->editor));
@@ -1499,11 +1504,13 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
(void) efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderEntryOneShot", EFI_VARIABLE_NON_VOLATILE);
(void) efivar_get_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntryDefault", &config->entry_default_efivar);
(void) efivar_get_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntrySysFail", &config->entry_sysfail);
strtolower16(config->entry_default_config);
strtolower16(config->entry_default_efivar);
strtolower16(config->entry_oneshot);
strtolower16(config->entry_saved);
strtolower16(config->entry_sysfail);
config->use_saved_entry = streq16(config->entry_default_config, u"@saved");
config->use_saved_entry_efivar = streq16(config->entry_default_efivar, u"@saved");
@@ -1686,11 +1693,38 @@ static size_t config_find_entry(Config *config, const char16_t *pattern) {
return IDX_INVALID;
}
static bool sysfail_process(Config *config) {
SysFailType sysfail_type;
assert(config);
sysfail_type = sysfail_check();
if (sysfail_type == SYSFAIL_NO_FAILURE)
return false;
/* Store reason string in LoaderSysFailReason EFI variable */
const char16_t *reason_str = sysfail_get_error_str(sysfail_type);
if (reason_str)
(void) efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderSysFailReason", reason_str, 0);
config->sysfail_occured = true;
return true;
}
static void config_select_default_entry(Config *config) {
size_t i;
assert(config);
if (config->sysfail_occured) {
i = config_find_entry(config, config->entry_sysfail);
if (i != IDX_INVALID) {
config->idx_default = i;
return;
}
}
i = config_find_entry(config, config->entry_oneshot);
if (i != IDX_INVALID) {
config->idx_default = i;
@@ -2685,6 +2719,7 @@ static void config_free(Config *config) {
free(config->entry_default_efivar);
free(config->entry_oneshot);
free(config->entry_saved);
free(config->entry_sysfail);
}
static void config_write_entries_to_variable(Config *config) {
@@ -2983,6 +3018,7 @@ static EFI_STATUS run(EFI_HANDLE image) {
_cleanup_free_ char16_t *loaded_image_path = NULL;
(void) device_path_to_str(loaded_image->FilePath, &loaded_image_path);
config_load_all_entries(&config, loaded_image, loaded_image_path, root_dir);
(void) sysfail_process(&config);
if (config.n_entries == 0)
return log_error_status(
+12
View File
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sysfail.h"
#include "util.h"
SysFailType sysfail_check(void) {
return SYSFAIL_NO_FAILURE;
}
const char16_t* sysfail_get_error_str(SysFailType fail_type) {
return NULL;
}
+12
View File
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "efivars-fundamental.h"
typedef enum SysFailType {
SYSFAIL_NO_FAILURE,
_SYSFAIL_MAX,
} SysFailType;
SysFailType sysfail_check(void);
const char16_t* sysfail_get_error_str(SysFailType fail_type);
+7
View File
@@ -457,6 +457,7 @@ void boot_config_free(BootConfig *config) {
free(config->entry_oneshot);
free(config->entry_default);
free(config->entry_selected);
free(config->entry_sysfail);
FOREACH_ARRAY(i, config->entries, config->n_entries)
boot_entry_free(i);
@@ -1437,6 +1438,12 @@ static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) {
if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m");
r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &config->entry_sysfail);
if (r == -ENOMEM)
return log_oom();
if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySysFail\", ignoring: %m");
return 1;
}
+1
View File
@@ -80,6 +80,7 @@ typedef struct BootConfig {
char *entry_oneshot;
char *entry_default;
char *entry_selected;
char *entry_sysfail;
BootEntry *entries;
size_t n_entries;