]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
systemd-boot: add a preferred setting that's similar to default but avoids booting...
authorr-vdp <ramses@well-founded.dev>
Sun, 11 Jan 2026 18:49:34 +0000 (19:49 +0100)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 17 Feb 2026 18:28:12 +0000 (03:28 +0900)
Motivation:
Currently, when setting the default boot pattern, boot assessment status
is not taken into account. This means that with boot assessment enabled,
when an explicit boot entry is configured as the default entry using an
EFI var, as is common for instance in A/B boot schemes, the configured
entry will be booted indefinitly, regardless of the entry's boot
assessment status.
In order to allow for this use case in combination with boot assessment,
we introduce a new `preferred` keyword, both in the config file and in the
bootctl CLI, that acts very similar to the existing `default` keyword but
takes boot assessment into account and never selects any entries that
have been marked as bad.
If the preferred pattern does not resolve to any bootable entry, and a
default pattern is also specified, then the default pattern will be
considered next, and we may then still select a known-bad entry to be
booted.

Fixes: https://github.com/systemd/systemd/issues/31215
Fixes: https://github.com/systemd/systemd/issues/40192
12 files changed:
docs/BOOT.md
docs/BOOT_LOADER_INTERFACE.md
man/bootctl.xml
man/systemd-boot.xml
src/boot/boot.c
src/bootctl/bootctl-install.c
src/bootctl/bootctl-set-efivar.c
src/bootctl/bootctl-status.c
src/bootctl/bootctl.c
src/fundamental/efivars-fundamental.h
src/shared/bootspec.c
src/shared/bootspec.h

index 0912d0c9082f48f5f18b0a5f29869aad651a6da2..96f92d722a7ce010b1e51a8a37e00a04857dc1d3 100644 (file)
@@ -101,7 +101,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 |
+| LoaderEntryDefault | entry identifier to select as default at bootup, ignoring boot assessment | non-volatile |
+| LoaderEntryPreferred | entry identifier to select as default at bootup, respecting boot assessment | non-volatile |
 | LoaderEntrySysFail | sysfail entry identifier | non-volatile |
 | LoaderSysFailReason | system failure reason | volatile |
 | LoaderConfigTimeout | timeout in seconds to show the menu | non-volatile |
index a2e70fc429a876c1d7c82d7ef1b63e13ef30ca0d..5c2e74f29011df553d5a0bc1f5a8a900141145af 100644 (file)
@@ -61,8 +61,24 @@ Variables will be listed below using the Linux efivarfs naming,
   The list should be in the order the entries are shown on screen during boot.
   See below regarding the recommended vocabulary for boot loader entry identifiers.
 
+* The EFI variable `LoaderEntryPreferred-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`
+  contains the preferred boot loader entry to use.
+  This takes boot assessment into account by not selecting boot entries that have
+  been marked as bad,
+  see <ulink url="https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT">Automatic Boot Assessment</ulink>
+  for more details on boot assessment.
+  If no entry was selected by the preferred setting (from either the EFI var or
+  the config file), then the boot loader will look at the default setting, which
+  does not skip entries that were marked as bad.
+  It contains a NUL-terminated boot loader entry identifier.
+
 * The EFI variable `LoaderEntryDefault-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`
   contains the default boot loader entry to use.
+  This ignores boot assessment and can select boot entries that have been marked
+  as bad by boot assessment,
+  see <ulink url="https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT">Automatic Boot Assessment</ulink>
+  for more details on boot assessment as well as the documentation on the
+  `LoaderEntryPreferred` EFI var.
   It contains a NUL-terminated boot loader entry identifier.
 
 * The EFI variable `LoaderEntrySysFail-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`
@@ -83,10 +99,10 @@ Variables will be listed below using the Linux efivarfs naming,
   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 overrides `LoaderEntryDefault`.
+  When set overrides `LoaderEntryPreferred` and `LoaderEntryDefault`.
   It is removed automatically after being read by the boot loader,
   to ensure it only takes effect a single time.
-  This value is formatted the same way as `LoaderEntryDefault`.
+  This value is formatted the same way as `LoaderEntryDefault` and `LoaderEntryPreferred`.
 
 * The EFI variable `LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`
   contains the boot loader entry identifier that was booted.
@@ -126,6 +142,7 @@ Variables will be listed below using the Linux efivarfs naming,
                 [Boot Loader Specification](https://uapi-group.org/specifications/specs/boot_loader_specification).
   * `1 << 18` → The boot loader reports active TPM2 PCR banks in the
                 EFI variable `LoaderTpm2ActivePcrBanks-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`.
+  * `1 << 19` → The boot loader supports the `LoaderEntryPreferred` variable when set.
 
 * The EFI variable `LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`
   contains binary random data,
@@ -168,7 +185,8 @@ variables.
 While boot loader entries may be named relatively freely,
 it's highly recommended to follow these rules when picking identifiers for the entries,
 so that programs (and users) can derive basic context and meaning from the identifiers
-as passed in `LoaderEntries`, `LoaderEntryDefault`, `LoaderEntryOneShot`, `LoaderEntrySelected`,
+as passed in `LoaderEntries`, `LoaderEntryPreferred`, `LoaderEntryDefault`,
+`LoaderEntryOneShot`, `LoaderEntrySelected`,
 and possibly show nicely localized names for them in UIs.
 
 1. When boot loader entries are defined through the
index 77c824df95a7738e8f1721ddfe73e5ab9d8b3e1a..0c46edb6105927cbb1411a460f0793369dd2f9de 100644 (file)
 
     <variablelist>
       <varlistentry>
+        <term><option>set-preferred</option> <replaceable>ID</replaceable></term>
         <term><option>set-default</option> <replaceable>ID</replaceable></term>
         <term><option>set-oneshot</option> <replaceable>ID</replaceable></term>
         <term><option>set-sysfail</option> <replaceable>ID</replaceable></term>
 
         <listitem><para>Sets the default boot loader entry. Takes a single boot loader entry ID string or a glob
         pattern as argument. The <option>set-oneshot</option> command will set the default entry only for the next boot,
-        the <option>set-default</option> will set it persistently for all future boots. The <option>set-sysfail</option> command
+        <option>set-default</option> will set it persistently for all future boots,
+        <option>set-preferred</option> is like <option>set-default</option>,
+        but is aware of boot assessment and will skip boot entries that have their tries-left counter set to zero.
+        The <option>set-sysfail</option> 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.</para>
index 78ef02768fa1aeb976998485fee6b2212f05867c..dab10ed8ef12a78409c547c7c89af0537dbc2d70 100644 (file)
 
       <varlistentry>
         <term><keycap>d</keycap></term>
-        <listitem><para>Make selected entry the default</para>
+        <listitem><para>Make selected entry the preferred boot entry</para>
 
         <para>An EFI variable is set to allow this setting to persist.</para>
 
-        <xi:include href="version-info.xml" xpointer="v239"/></listitem>
+        <xi:include href="version-info.xml" xpointer="v260"/></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><keycap>D</keycap></term>
+        <listitem><para>Make selected entry the default boot entry</para>
+
+        <para>An EFI variable is set to allow this setting to persist.</para>
+
+        <xi:include href="version-info.xml" xpointer="v260"/></listitem>
       </varlistentry>
 
       <varlistentry>
       </varlistentry>
 
       <varlistentry>
+        <term><varname>LoaderEntryPreferred</varname></term>
         <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. Can be set in the OS and the boot
         loader. <varname>LoaderEntryOneShot</varname> sets the default entry for the next boot only, while
         <varname>LoaderEntryDefault</varname> sets it persistently for all future boots.
+        <varname>LoaderEntryPreferred</varname> is like <varname>LoaderEntryDefault</varname> but additionally
+        takes into account boot assessment and skips boot entries with a tries-left counter equal to zero.
         <citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
         <option>set-default</option> and <option>set-oneshot</option> commands make use of these variables.
         The boot loader modifies <varname>LoaderEntryDefault</varname> on request, when the
index d7fa50fe0f8c3b5cb2c938c62f4451b40ca9e6a1..cdf36b95203057f1a91d188caed845f8bf322cfe 100644 (file)
@@ -133,7 +133,9 @@ typedef struct {
         uint64_t timeout_sec_config;
         uint64_t timeout_sec_efivar;
         char16_t *entry_default_config;
+        char16_t *entry_preferred_config;
         char16_t *entry_default_efivar;
+        char16_t *entry_preferred_efivar;
         char16_t *entry_oneshot;
         char16_t *entry_saved;
         char16_t *entry_sysfail;
@@ -150,6 +152,8 @@ typedef struct {
         bool force_menu;
         bool use_saved_entry;
         bool use_saved_entry_efivar;
+        bool use_saved_entry_preferred;
+        bool use_saved_entry_preferred_efivar;
         bool beep;
         bool sysfail_occurred;
         int64_t console_mode;
@@ -328,8 +332,12 @@ static void print_status(Config *config, char16_t *loaded_image_path) {
 
         if (config->entry_default_config)
                 printf("              default (config): %ls\n", config->entry_default_config);
+        if (config->entry_preferred_config)
+                printf("            preferred (config): %ls\n", config->entry_preferred_config);
         if (config->entry_default_efivar)
                 printf("             default (EFI var): %ls\n", config->entry_default_efivar);
+        if (config->entry_preferred_efivar)
+                printf("           preferred (EFI var): %ls\n", config->entry_preferred_efivar);
         if (config->entry_oneshot)
                 printf("            default (one-shot): %ls\n", config->entry_oneshot);
         if (config->entry_sysfail)
@@ -760,7 +768,25 @@ static bool menu_run(
                         action = ACTION_QUIT;
                         break;
 
+                /* Set/unset the preferred entry */
                 case KEYPRESS(0, 0, 'd'):
+                        if (config->idx_default_efivar != idx_highlight) {
+                                free(config->entry_preferred_efivar);
+                                config->entry_preferred_efivar = xstrdup16(config->entries[idx_highlight]->id);
+                                config->idx_default_efivar = idx_highlight;
+                                status = xstrdup16(u"Preferred boot entry selected.");
+                        } else {
+                                config->entry_preferred_efivar = mfree(config->entry_preferred_efivar);
+                                config->idx_default_efivar = IDX_INVALID;
+                                status = xstrdup16(u"Preferred boot entry cleared.");
+                        }
+                        config->entry_default_efivar = mfree(config->entry_default_efivar);
+                        config->use_saved_entry_efivar = false;
+                        config->use_saved_entry_preferred_efivar = false;
+                        refresh = true;
+                        break;
+
+                /* Set/unset the default entry */
                 case KEYPRESS(0, 0, 'D'):
                         if (config->idx_default_efivar != idx_highlight) {
                                 free(config->entry_default_efivar);
@@ -772,7 +798,9 @@ static bool menu_run(
                                 config->idx_default_efivar = IDX_INVALID;
                                 status = xstrdup16(u"Default boot entry cleared.");
                         }
+                        config->entry_preferred_efivar = mfree(config->entry_preferred_efivar);
                         config->use_saved_entry_efivar = false;
+                        config->use_saved_entry_preferred_efivar = false;
                         refresh = true;
                         break;
 
@@ -919,8 +947,17 @@ static bool menu_run(
 
         /* Update EFI vars after we left the menu to reduce NVRAM writes. */
 
-        if (default_efivar_saved != config->idx_default_efivar)
-                efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntryDefault", config->entry_default_efivar, EFI_VARIABLE_NON_VOLATILE);
+        if (default_efivar_saved != config->idx_default_efivar) {
+                if (config->entry_preferred_efivar)
+                        efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntryPreferred", config->entry_preferred_efivar, EFI_VARIABLE_NON_VOLATILE);
+                else
+                        efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderEntryPreferred", EFI_VARIABLE_NON_VOLATILE);
+
+                if (config->entry_default_efivar)
+                        efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntryDefault", config->entry_default_efivar, EFI_VARIABLE_NON_VOLATILE);
+                else
+                        efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderEntryDefault", EFI_VARIABLE_NON_VOLATILE);
+        }
 
         if (console_mode_efivar_saved != config->console_mode_efivar) {
                 if (config->console_mode_efivar == CONSOLE_MODE_KEEP)
@@ -1071,6 +1108,14 @@ static void config_defaults_load_from_file(Config *config, char *content) {
                         free(config->entry_default_config);
                         config->entry_default_config = xstr8_to_16(value);
 
+                } else if (streq8(key, "preferred")) {
+                        if (value[0] == '@' && !strcaseeq8(value, "@saved")) {
+                                log_warning("Unsupported special entry identifier, ignoring: %s", value);
+                                continue;
+                        }
+                        free(config->entry_preferred_config);
+                        config->entry_preferred_config = xstr8_to_16(value);
+
                 } else if (streq8(key, "editor")) {
                         if (!parse_boolean(value, &config->editor))
                                 log_warning("Error parsing 'editor' config option, ignoring: %s", value);
@@ -1571,18 +1616,24 @@ 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"LoaderEntryPreferred", &config->entry_preferred_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_preferred_config);
+        strtolower16(config->entry_preferred_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");
-        if (config->use_saved_entry || config->use_saved_entry_efivar)
+        config->use_saved_entry_preferred = streq16(config->entry_preferred_config, u"@saved");
+        config->use_saved_entry_preferred_efivar = streq16(config->entry_preferred_efivar, u"@saved");
+        if (config->use_saved_entry || config->use_saved_entry_efivar || config->use_saved_entry_preferred || config->use_saved_entry_preferred_efivar)
                 (void) efivar_get_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntryLastBooted", &config->entry_saved);
+
 }
 
 static bool valid_type1_filename(const char16_t *fname) {
@@ -1745,7 +1796,7 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
         return CMP(a->tries_done, b->tries_done);
 }
 
-static size_t config_find_entry(Config *config, const char16_t *pattern) {
+static size_t config_find_entry(Config *config, const char16_t *pattern, const bool check_assessment) {
         assert(config);
 
         /* We expect pattern and entry IDs to be already case folded. */
@@ -1754,7 +1805,7 @@ static size_t config_find_entry(Config *config, const char16_t *pattern) {
                 return IDX_INVALID;
 
         for (size_t i = 0; i < config->n_entries; i++)
-                if (efi_fnmatch(pattern, config->entries[i]->id))
+                if (efi_fnmatch(pattern, config->entries[i]->id) && (!check_assessment || config->entries[i]->tries_left != 0))
                         return i;
 
         return IDX_INVALID;
@@ -1785,20 +1836,47 @@ static void config_select_default_entry(Config *config) {
         assert(config);
 
         if (config->sysfail_occurred) {
-                i = config_find_entry(config, config->entry_sysfail);
+                i = config_find_entry(config, config->entry_sysfail, /* check_assessment= */ false);
                 if (i != IDX_INVALID) {
                         config->idx_default = i;
                         return;
                 }
         }
 
-        i = config_find_entry(config, config->entry_oneshot);
+        i = config_find_entry(config, config->entry_oneshot, /* check_assessment= */ false);
         if (i != IDX_INVALID) {
                 config->idx_default = i;
                 return;
         }
 
-        i = config_find_entry(config, config->use_saved_entry_efivar ? config->entry_saved : config->entry_default_efivar);
+        /* Try to match the preferred entry pattern */
+
+        i = config_find_entry(config, config->use_saved_entry_preferred_efivar ? config->entry_saved : config->entry_preferred_efivar, /* check_assessment= */ true);
+        if (i != IDX_INVALID) {
+                config->idx_default = i;
+                config->idx_default_efivar = i;
+                return;
+        }
+
+        i = config_find_entry(config, config->entry_preferred_config, /* check_assessment= */ true);
+        if (i != IDX_INVALID) {
+                config->idx_default = i;
+                return;
+        }
+
+        if (config->use_saved_entry_preferred)
+                /* No need to do the same thing twice. */
+                i = config->use_saved_entry_preferred_efivar ? IDX_INVALID : config_find_entry(config, config->entry_saved, /* check_assessment= */ true);
+        else
+                i = config_find_entry(config, config->entry_preferred_config, /* check_assessment= */ true);
+        if (i != IDX_INVALID) {
+                config->idx_default = i;
+                return;
+        }
+
+        /* Try to match the default pattern */
+
+        i = config_find_entry(config, config->use_saved_entry_efivar ? config->entry_saved : config->entry_default_efivar, /* check_assessment= */ false);
         if (i != IDX_INVALID) {
                 config->idx_default = i;
                 config->idx_default_efivar = i;
@@ -1807,9 +1885,9 @@ static void config_select_default_entry(Config *config) {
 
         if (config->use_saved_entry)
                 /* No need to do the same thing twice. */
-                i = config->use_saved_entry_efivar ? IDX_INVALID : config_find_entry(config, config->entry_saved);
+                i = config->use_saved_entry_efivar ? IDX_INVALID : config_find_entry(config, config->entry_saved, /* check_assessment= */ false);
         else
-                i = config_find_entry(config, config->entry_default_config);
+                i = config_find_entry(config, config->entry_default_config, /* check_assessment= */ false);
         if (i != IDX_INVALID) {
                 config->idx_default = i;
                 return;
@@ -2811,6 +2889,8 @@ static void config_free(Config *config) {
         free(config->entries);
         free(config->entry_default_config);
         free(config->entry_default_efivar);
+        free(config->entry_preferred_config);
+        free(config->entry_preferred_efivar);
         free(config->entry_oneshot);
         free(config->entry_saved);
         free(config->entry_sysfail);
@@ -2932,6 +3012,7 @@ static void export_loader_variables(
                 EFI_LOADER_FEATURE_CONFIG_TIMEOUT |
                 EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT |
                 EFI_LOADER_FEATURE_ENTRY_DEFAULT |
+                EFI_LOADER_FEATURE_ENTRY_PREFERRED |
                 EFI_LOADER_FEATURE_ENTRY_ONESHOT |
                 EFI_LOADER_FEATURE_BOOT_COUNTING |
                 EFI_LOADER_FEATURE_XBOOTLDR |
index 50ab7b602c4e265e3ab01c94ca91b160a2685e34..1a8d5ffb30cb270b27cd1b6dca273f8b989ee16f 100644 (file)
@@ -1861,6 +1861,7 @@ static int remove_loader_variables(void) {
                        EFI_LOADER_VARIABLE_STR("LoaderConfigConsoleMode"),
                        EFI_LOADER_VARIABLE_STR("LoaderConfigTimeout"),
                        EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot"),
+                       EFI_LOADER_VARIABLE_STR("LoaderEntryPreferred"),
                        EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"),
                        EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"),
                        EFI_LOADER_VARIABLE_STR("LoaderEntryLastBooted"),
index 82770dd6c6c34eaff63e03deb791fbebbd4aaab0..81eb354aea93085b3549f4121f9c6f14448fc1e0 100644 (file)
@@ -171,6 +171,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-preferred")) {
+                variable = EFI_LOADER_VARIABLE_STR("LoaderEntryPreferred");
+                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;
index 67b590b76f1c2658b133f7c6052f6bf4854e0408..1b471816cf4f9c853d4c91853e20b32105abca5a 100644 (file)
@@ -408,7 +408,8 @@ 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, *sysfail_entry = NULL, *sysfail_reason = NULL;
+                        *current_entry = NULL, *oneshot_entry = NULL, *preferred_entry = NULL, *default_entry = NULL, *sysfail_entry = NULL,
+                        *sysfail_reason = NULL;
                 uint64_t loader_features = 0, stub_features = 0;
                 int have;
 
@@ -422,6 +423,7 @@ int verb_status(int argc, char *argv[], void *userdata) {
                 (void) efi_stub_get_features(&stub_features);
                 (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), &current_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("LoaderEntryPreferred"), &preferred_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);
@@ -509,6 +511,8 @@ int verb_status(int argc, char *argv[], void *userdata) {
 
                         if (current_entry)
                                 printf(" Current Entry: %s\n", current_entry);
+                        if (preferred_entry)
+                                printf(" Preferred Entry: %s\n", preferred_entry);
                         if (default_entry)
                                 printf(" Default Entry: %s\n", default_entry);
                         if (oneshot_entry && !streq_ptr(oneshot_entry, default_entry))
index e586c7f0dabc7c11f51da0b390d49f51b77e3127..7e59fd016814706912b76d2f313f4d8810ad7c00 100644 (file)
@@ -702,6 +702,7 @@ static int bootctl_main(int argc, char *argv[]) {
                 { "unlink",              2,        2,        0,            verb_unlink              },
                 { "cleanup",             VERB_ANY, 1,        0,            verb_cleanup             },
                 { "set-default",         2,        2,        0,            verb_set_efivar          },
+                { "set-preferred",       2,        2,        0,            verb_set_efivar          },
                 { "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          },
index df94a736e1ca6494c16c61729f333d45c75019b0..15be52119a0a2dd2d141e5e5717256e770e1e881 100644 (file)
@@ -28,6 +28,7 @@
 #define EFI_LOADER_FEATURE_TYPE1_UKI               (UINT64_C(1) << 16)
 #define EFI_LOADER_FEATURE_TYPE1_UKI_URL           (UINT64_C(1) << 17)
 #define EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS   (UINT64_C(1) << 18)
+#define EFI_LOADER_FEATURE_ENTRY_PREFERRED         (UINT64_C(1) << 19)
 
 /* Features of the stub, i.e. systemd-stub */
 #define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION     (UINT64_C(1) << 0)
index b6b5d6e50b2f617a7a93923e011416d1a7f97243..89dfae70c962866289143d6bcfe37697ee8c7bcf 100644 (file)
@@ -462,9 +462,11 @@ int boot_config_load_type1(
 void boot_config_free(BootConfig *config) {
         assert(config);
 
+        free(config->preferred_pattern);
         free(config->default_pattern);
 
         free(config->entry_oneshot);
+        free(config->entry_preferred);
         free(config->entry_default);
         free(config->entry_selected);
         free(config->entry_sysfail);
@@ -515,6 +517,8 @@ int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
                         continue;
                 }
 
+                if (streq(field, "preferred"))
+                        r = free_and_strdup(&config->preferred_pattern, p);
                 if (streq(field, "default"))
                         r = free_and_strdup(&config->default_pattern, p);
                 else if (STR_IN_SET(field, "timeout", "editor", "auto-entries", "auto-firmware",
@@ -1390,6 +1394,15 @@ static int boot_entries_select_default(const BootConfig *config) {
                 }
         }
 
+        if (config->entry_preferred) {
+                i = boot_config_find(config, config->entry_preferred);
+                if (i >= 0) {
+                        log_debug("Found default: id \"%s\" is matched by LoaderEntryPreferred",
+                                  config->entries[i].id);
+                        return i;
+                }
+        }
+
         if (config->entry_default) {
                 i = boot_config_find(config, config->entry_default);
                 if (i >= 0) {
@@ -1399,6 +1412,15 @@ static int boot_entries_select_default(const BootConfig *config) {
                 }
         }
 
+        if (config->preferred_pattern) {
+                i = boot_config_find(config, config->preferred_pattern);
+                if (i >= 0) {
+                        log_debug("Found preferred: id \"%s\" is matched by pattern \"%s\"",
+                                  config->entries[i].id, config->preferred_pattern);
+                        return i;
+                }
+        }
+
         if (config->default_pattern) {
                 i = boot_config_find(config, config->default_pattern);
                 if (i >= 0) {
@@ -1438,6 +1460,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 \"LoaderEntryOneShot\", ignoring: %m");
 
+        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryPreferred"), &config->entry_preferred);
+        if (r == -ENOMEM)
+                return log_oom();
+        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
+                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryPreferred\", ignoring: %m");
+
         r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &config->entry_default);
         if (r == -ENOMEM)
                 return log_oom();
index 3bd516c75dfdc87df3614b03d532028970e39a3d..f325dcae251540cfeea30eaa6e49199b5e4c9bdf 100644 (file)
@@ -73,8 +73,10 @@ typedef struct BootConfig {
         int loader_conf_status;  /* 0 → before loading, 1 → loaded, negative → error. */
 
         char *default_pattern;
+        char *preferred_pattern;
 
         char *entry_oneshot;
+        char *entry_preferred;
         char *entry_default;
         char *entry_selected;
         char *entry_sysfail;