]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-boot: add support for a sysfail entry
authorIgor Opaniuk <igor.opaniuk@foundries.io>
Mon, 24 Mar 2025 14:30:49 +0000 (15:30 +0100)
committerIgor Opaniuk <igor.opaniuk@foundries.io>
Mon, 12 May 2025 13:37:46 +0000 (15:37 +0200)
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>
docs/BOOT.md
docs/BOOT_LOADER_INTERFACE.md
man/systemd-boot.xml
src/boot/boot.c
src/boot/sysfail.c [new file with mode: 0644]
src/boot/sysfail.h [new file with mode: 0644]
src/shared/bootspec.c
src/shared/bootspec.h

index a9666583a16024122079c9f410074a987bbc3b07..c72b31456b3457cbdb2cab4c2fe401e10a8e3aea 100644 (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 |
index e264c2cc3c1ac482c6356a6acff209c7dea0a442..d4d9a77370203417238692a4e229f3a90e71d46e 100644 (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
index 6da27145810b913c3e8874c061482b1a0a5d5153..d0912739d1853aa1bb07326a74c9246799c23609 100644 (file)
 
       <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
index ab043deca95d369fa1428efe34b3a8b793e8b767..133f16acfe911a962d94f644e8f96f7009ce7cbd 100644 (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(
diff --git a/src/boot/sysfail.c b/src/boot/sysfail.c
new file mode 100644 (file)
index 0000000..6bbbffe
--- /dev/null
@@ -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;
+}
diff --git a/src/boot/sysfail.h b/src/boot/sysfail.h
new file mode 100644 (file)
index 0000000..4ccb548
--- /dev/null
@@ -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);
index e5ca2ccbb3c7fab9a73a943d4d29e3e8f4ae3766..5dd8d452ab3855a2974fc16e67836b86af88cdef 100644 (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;
 }
 
index 95675214b2ad0866605eb0ddf800878eed7d4078..1d114e10d4a60642571207c1befb10c687154049 100644 (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;