From 2857a8397507ca3decb01c188b4d79ee36185424 Mon Sep 17 00:00:00 2001 From: Igor Opaniuk Date: Mon, 24 Mar 2025 15:33:16 +0100 Subject: [PATCH] bootctl: configure a sysfail entry You can configure the sysfail boot entry using the bootctl command: $ bootctl set-sysfail sysfail.conf The value will be stored in the `LoaderEntrySysFail` EFI variable. The `LoaderEntrySysFail` EFI variable would be unset automatically during next boot by `systemd-boot-clear-sysfail.service` if no system failure occured, otherwise it would be kept as it is and a system failure reason will be saved to `LoaderSysFailReason` EFI variable. Signed-off-by: Igor Opaniuk --- man/bootctl.xml | 11 +++-- man/systemd-boot-clear-sysfail.service.xml | 52 ++++++++++++++++++++++ presets/90-systemd.preset | 1 + src/boot/meson.build | 1 + src/bootctl/bootctl-install.c | 1 + src/bootctl/bootctl-set-efivar.c | 8 ++++ src/bootctl/bootctl-status.c | 25 +++++++---- src/bootctl/bootctl.c | 2 + units/meson.build | 4 ++ units/systemd-boot-clear-sysfail.service | 29 ++++++++++++ 10 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 man/systemd-boot-clear-sysfail.service.xml create mode 100644 units/systemd-boot-clear-sysfail.service diff --git a/man/bootctl.xml b/man/bootctl.xml index 91d572e643..f1968a0ba7 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -134,10 +134,14 @@ ID ID + ID Sets the default boot loader entry. Takes a single boot loader entry ID string or a glob pattern as argument. The command will set the default entry only for the next boot, - the will set it persistently for all future boots. + the will set it persistently for all future boots. The command + will set 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. bootctl list can be used to list available boot loader entries and their IDs. @@ -146,8 +150,9 @@ or , which correspond to the current default boot loader entry for all future boots, the current default boot loader entry for the next boot, and the currently booted boot loader entry. These special IDs are resolved to the current values of the EFI variables - LoaderEntryDefault, LoaderEntryOneShot and LoaderEntrySelected, - see Boot Loader Specification for details. + LoaderEntryDefault, LoaderEntrySysFail, LoaderEntryOneShot + and LoaderEntrySelected, see + Boot Loader Specification for details. These special IDs are primarily useful as a quick way to persistently make the currently booted boot loader entry the default choice, or to upgrade the default boot loader entry for the next boot to the default boot loader entry for all future boots, but may be used for other operations too. diff --git a/man/systemd-boot-clear-sysfail.service.xml b/man/systemd-boot-clear-sysfail.service.xml new file mode 100644 index 0000000000..2249486377 --- /dev/null +++ b/man/systemd-boot-clear-sysfail.service.xml @@ -0,0 +1,52 @@ + + + + + + + + systemd-boot-clear-sysfail.service + systemd + + + + systemd-boot-clear-sysfail.service + 8 + + + + systemd-boot-clear-sysfail.service + Clear LoaderEntrySysFail entry + + + + systemd-boot-clear-sysfail.service + + + + Description + + systemd-boot-clear-sysfail.service is a system service that automatically clears the + 'LoaderEntrySysFail' boot loader entry if the boot was successful and the 'LoaderSysFailReason' EFI variable, + which indicates the reason for the system failure, is not set. + + The systemd-boot-random-seed.service unit invokes the bootctl --graceful + set-sysfail "" command, which clears the LoaderEntrySysFail entry. The service is conditionalized + so that it is run only when a LoaderSysFailReason entry is not set.For further details see + bootctl1, regarding + the command this service invokes. + + + + + See Also + + systemd1 + bootctl1 + systemd-boot7 + + + + diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset index 9c13e9c3de..56f9e93706 100644 --- a/presets/90-systemd.preset +++ b/presets/90-systemd.preset @@ -19,6 +19,7 @@ enable machines.target enable getty@.service +enable systemd-boot-clear-sysfail.service enable systemd-boot-update.service enable systemd-confext.service enable systemd-homed.service diff --git a/src/boot/meson.build b/src/boot/meson.build index c2a894d323..fb0e393de9 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -307,6 +307,7 @@ libefi_sources = files( 'secure-boot.c', 'shim.c', 'smbios.c', + 'sysfail.c', 'ticks.c', 'url-discovery.c', 'util.c', diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 8927464c46..7b083c4ad9 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -1224,6 +1224,7 @@ static int remove_loader_variables(void) { EFI_LOADER_VARIABLE_STR("LoaderConfigTimeout"), EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot"), EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), + EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), EFI_LOADER_VARIABLE_STR("LoaderEntryLastBooted"), EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), EFI_LOADER_VARIABLE_STR("LoaderSystemToken")) { diff --git a/src/bootctl/bootctl-set-efivar.c b/src/bootctl/bootctl-set-efivar.c index d53458c491..ead81ff3e0 100644 --- a/src/bootctl/bootctl-set-efivar.c +++ b/src/bootctl/bootctl-set-efivar.c @@ -89,6 +89,11 @@ static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target if (r < 0) return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryDefault': %m"); + } else if (streq(arg1, "@sysfail")) { + r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), NULL, (void *) ret_target, ret_target_size); + if (r < 0) + return log_error_errno(r, "Failed to get EFI variable 'LoaderEntrySysFail': %m"); + } else if (arg1[0] == '@' && !streq(arg1, "@saved")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported special entry identifier: %s", arg1); else { @@ -144,6 +149,9 @@ int verb_set_efivar(int argc, char *argv[], void *userdata) { if (streq(argv[0], "set-default")) { variable = EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"); arg_parser = parse_loader_entry_target_arg; + } else if (streq(argv[0], "set-sysfail")) { + variable = EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"); + arg_parser = parse_loader_entry_target_arg; } else if (streq(argv[0], "set-oneshot")) { variable = EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"); arg_parser = parse_loader_entry_target_arg; diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 38fe5273be..081f5d48f0 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -420,7 +420,7 @@ int verb_status(int argc, char *argv[], void *userdata) { { EFI_STUB_FEATURE_MULTI_PROFILE_UKI, "Stub understands profile selector" }, }; _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL, *stub_path = NULL, - *current_entry = NULL, *oneshot_entry = NULL, *default_entry = NULL; + *current_entry = NULL, *oneshot_entry = NULL, *default_entry = NULL, *sysfail_entry = NULL, *sysfail_reason = NULL; uint64_t loader_features = 0, stub_features = 0; int have; @@ -435,6 +435,8 @@ int verb_status(int argc, char *argv[], void *userdata) { (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), ¤t_entry); (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &oneshot_entry); (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &default_entry); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &sysfail_entry); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderSysFailReason"), &sysfail_reason); SecureBootMode secure = efi_get_secure_boot_mode(); printf("%sSystem:%s\n", ansi_underline(), ansi_normal()); @@ -484,7 +486,7 @@ int verb_status(int argc, char *argv[], void *userdata) { if (loader) { printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal()); - printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal()); + printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal()); for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++) print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name); @@ -502,23 +504,28 @@ int verb_status(int argc, char *argv[], void *userdata) { SD_ID128_FORMAT_VAL(loader_partition_uuid), SD_ID128_FORMAT_VAL(esp_uuid)); - printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", + printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(loader_partition_uuid)); } else if (loader_path) - printf(" Partition: n/a\n"); + printf(" Partition: n/a\n"); if (loader_path) - printf(" Loader: %s%s\n", glyph(GLYPH_TREE_RIGHT), strna(loader_path)); + printf(" Loader: %s%s\n", glyph(GLYPH_TREE_RIGHT), strna(loader_path)); if (loader_url) - printf(" Net Boot URL: %s\n", loader_url); + printf(" Net Boot URL: %s\n", loader_url); + + if (sysfail_entry) + printf("SysFail Reason: %s\n", sysfail_reason); if (current_entry) - printf("Current Entry: %s\n", current_entry); + printf(" Current Entry: %s\n", current_entry); if (default_entry) - printf("Default Entry: %s\n", default_entry); + printf(" Default Entry: %s\n", default_entry); if (oneshot_entry && !streq_ptr(oneshot_entry, default_entry)) - printf("OneShot Entry: %s\n", oneshot_entry); + printf(" OneShot Entry: %s\n", oneshot_entry); + if (sysfail_entry) + printf(" SysFail Entry: %s\n", sysfail_entry); printf("\n"); } diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 047b79254d..790ab2b653 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -259,6 +259,7 @@ static int help(int argc, char *argv[], void *userdata) { "\n%3$sBoot Loader Interface Commands:%4$s\n" " set-default ID Set default boot loader entry\n" " set-oneshot ID Set default boot loader entry, for next boot only\n" + " set-sysfail ID Set boot loader entry used in case of a system failure\n" " set-timeout SECONDS Set the menu timeout\n" " set-timeout-oneshot SECONDS\n" " Set the menu timeout for the next boot only\n" @@ -660,6 +661,7 @@ static int bootctl_main(int argc, char *argv[]) { { "set-oneshot", 2, 2, 0, verb_set_efivar }, { "set-timeout", 2, 2, 0, verb_set_efivar }, { "set-timeout-oneshot", 2, 2, 0, verb_set_efivar }, + { "set-sysfail", 2, 2, 0, verb_set_efivar }, { "random-seed", VERB_ANY, 1, 0, verb_random_seed }, { "reboot-to-firmware", VERB_ANY, 2, 0, verb_reboot_to_firmware }, {} diff --git a/units/meson.build b/units/meson.build index 3dd135942d..a8b0040562 100644 --- a/units/meson.build +++ b/units/meson.build @@ -286,6 +286,10 @@ units = [ 'conditions' : ['ENABLE_BOOTLOADER'], 'symlinks' : ['sysinit.target.wants/'], }, + { + 'file' : 'systemd-boot-clear-sysfail.service', + 'conditions' : ['ENABLE_BOOTLOADER'], + }, { 'file' : 'systemd-boot-update.service', 'conditions' : ['ENABLE_BOOTLOADER'], diff --git a/units/systemd-boot-clear-sysfail.service b/units/systemd-boot-clear-sysfail.service new file mode 100644 index 0000000000..c80735970d --- /dev/null +++ b/units/systemd-boot-clear-sysfail.service @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Clear SysFail Entry If The Boot Is Successful +Documentation=man:systemd-boot-sysfail.service(8) + +DefaultDependencies=no +Conflicts=shutdown.target +Before=sysinit.target shutdown.target + +ConditionPathExists=!/etc/initrd-release +# If LoaderSysFailReason is set we should not clear SysFail entry +ConditionPathExists=!/sys/firmware/efi/efivars/LoaderSysFailReason-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f +ConditionPathExists=/sys/firmware/efi/efivars/LoaderEntrySysFail-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=bootctl --graceful set-sysfail "" + +[Install] +WantedBy=sysinit.target