]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-boot: Add support to boot last selected entry 21070/head
authorJan Janssen <medhefgo@web.de>
Thu, 28 Oct 2021 11:00:13 +0000 (13:00 +0200)
committerJan Janssen <medhefgo@web.de>
Fri, 29 Oct 2021 11:57:24 +0000 (13:57 +0200)
Fixes: #18994
man/bootctl.xml
man/loader.conf.xml
src/boot/bootctl.c
src/boot/efi/boot.c

index f6445ec8f351d91d3099af7793135a7d94557f4c..a0be688321dffef2245d97dbbdc025454fbf68b2 100644 (file)
         see <ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink> 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.
-        When an empty string ("") is specified as an ID, then the corresponding EFI variable will be unset.
+        loader entry for all future boots, but may be used for other operations too.</para>
+
+        <para>If set to <option>@saved</option> the chosen entry will be saved as an EFI variable
+        on every boot and automatically selected the next time the boot loader starts.</para>
+
+        <para>When an empty string ("") is specified as an ID, then the corresponding EFI variable will be unset.
         </para></listitem>
       </varlistentry>
 
index 3a954a3ce9420cc49801136082b62e04ed2bfb3d..5343751ece154f86eb1459f3a1d3dc56abd21590 100644 (file)
@@ -60,6 +60,9 @@
         selected entry will be stored as an EFI variable, overriding this option.
         </para>
 
+        <para>If set to <literal>@saved</literal> the chosen entry will be saved as an EFI variable
+        on every boot and automatically selected the next time the boot loader starts.</para>
+
         <table>
           <title>Automatically detected entries will use the following names:</title>
 
index 449eed0bed1f8fef7a45007103b78618d28dffc7..6243c6e2f580feaf8446b311b73992cd3b11d50c 100644 (file)
@@ -1834,7 +1834,7 @@ 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 (arg1[0] == '@')
+        } else if (arg1[0] == '@' && !streq(arg1, "@saved"))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported special entry identifier: %s", arg1);
         else {
                 encoded = utf8_to_utf16(arg1, strlen(arg1));
index f1b5c91c1b5f944e226dcedafdb8fe542b0e4175..db0c22d405b9feb6ee0bc703c5a8dc2b714bd1ad 100644 (file)
@@ -70,11 +70,14 @@ typedef struct {
         CHAR16 *entry_default_config;
         CHAR16 *entry_default_efivar;
         CHAR16 *entry_oneshot;
+        CHAR16 *entry_saved;
         CHAR16 *options_edit;
         BOOLEAN editor;
         BOOLEAN auto_entries;
         BOOLEAN auto_firmware;
         BOOLEAN force_menu;
+        BOOLEAN use_saved_entry;
+        BOOLEAN use_saved_entry_efivar;
         INT64 console_mode;
         INT64 console_mode_efivar;
         RandomSeedMode random_seed_mode;
@@ -489,6 +492,7 @@ static void print_status(Config *config, CHAR16 *loaded_image_path) {
         ps_string(L"               default: %s\n", config->entry_default_config);
         ps_string(L"     default (EFI var): %s\n", config->entry_default_efivar);
         ps_string(L"    default (one-shot): %s\n", config->entry_oneshot);
+        ps_string(L"           saved entry: %s\n", config->entry_saved);
           ps_bool(L"                editor: %s\n", config->editor);
           ps_bool(L"          auto-entries: %s\n", config->auto_entries);
           ps_bool(L"         auto-firmware: %s\n", config->auto_firmware);
@@ -845,6 +849,7 @@ static BOOLEAN menu_run(
                                 config->idx_default_efivar = -1;
                                 status = StrDuplicate(L"Default boot entry cleared.");
                         }
+                        config->use_saved_entry_efivar = FALSE;
                         refresh = TRUE;
                         break;
 
@@ -1112,6 +1117,10 @@ static void config_defaults_load_from_file(Config *config, CHAR8 *content) {
                 }
 
                 if (strcmpa((CHAR8 *)"default", key) == 0) {
+                        if (value[0] == '@' && strcmpa((CHAR8 *)"@saved", value) != 0) {
+                                log_error_stall(L"Unsupported special entry identifier: %a", value);
+                                continue;
+                        }
                         FreePool(config->entry_default_config);
                         config->entry_default_config = stra_to_str(value);
                         continue;
@@ -1546,6 +1555,11 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
                 (void) efivar_set(LOADER_GUID, L"LoaderEntryOneShot", NULL, EFI_VARIABLE_NON_VOLATILE);
 
         (void) efivar_get(LOADER_GUID, L"LoaderEntryDefault", &config->entry_default_efivar);
+
+        config->use_saved_entry = streq_ptr(config->entry_default_config, L"@saved");
+        config->use_saved_entry_efivar = streq_ptr(config->entry_default_efivar, L"@saved");
+        if (config->use_saved_entry || config->use_saved_entry_efivar)
+                (void) efivar_get(LOADER_GUID, L"LoaderEntryLastBooted", &config->entry_saved);
 }
 
 static void config_load_entries(
@@ -1655,14 +1669,18 @@ static void config_default_entry_select(Config *config) {
                 return;
         }
 
-        i = config_entry_find(config, config->entry_default_efivar);
+        i = config_entry_find(config, config->use_saved_entry_efivar ? config->entry_saved : config->entry_default_efivar);
         if (i >= 0) {
                 config->idx_default = i;
                 config->idx_default_efivar = i;
                 return;
         }
 
-        i = config_entry_find(config, config->entry_default_config);
+        if (config->use_saved_entry)
+                /* No need to do the same thing twice. */
+                i = config->use_saved_entry_efivar ? -1 : config_entry_find(config, config->entry_saved);
+        else
+                i = config_entry_find(config, config->entry_default_config);
         if (i >= 0) {
                 config->idx_default = i;
                 return;
@@ -2237,6 +2255,29 @@ static void config_write_entries_to_variable(Config *config) {
         (void) efivar_set_raw(LOADER_GUID, L"LoaderEntries", buffer, sz, 0);
 }
 
+static void save_selected_entry(const Config *config, const ConfigEntry *entry) {
+        assert(config);
+        assert(entry);
+        assert(!entry->call);
+
+        /* Always export the selected boot entry to the system in a volatile var. */
+        (void) efivar_set(LOADER_GUID, L"LoaderEntrySelected", entry->id, 0);
+
+        /* Do not save or delete if this was a oneshot boot. */
+        if (streq_ptr(config->entry_oneshot, entry->id))
+                return;
+
+        if (config->use_saved_entry_efivar || (!config->entry_default_efivar && config->use_saved_entry)) {
+                /* Avoid unnecessary NVRAM writes. */
+                if (streq_ptr(config->entry_saved, entry->id))
+                        return;
+
+                (void) efivar_set(LOADER_GUID, L"LoaderEntryLastBooted", entry->id, EFI_VARIABLE_NON_VOLATILE);
+        } else
+                /* Delete the non-volatile var if not needed. */
+                (void) efivar_set(LOADER_GUID, L"LoaderEntryLastBooted", NULL, EFI_VARIABLE_NON_VOLATILE);
+}
+
 static void export_variables(
                 EFI_LOADED_IMAGE *loaded_image,
                 const CHAR16 *loaded_image_path,
@@ -2415,9 +2456,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
                 }
 
                 config_entry_bump_counters(entry, root_dir);
-
-                /* Export the selected boot entry to the system */
-                (void) efivar_set(LOADER_GUID, L"LoaderEntrySelected", entry->id, 0);
+                save_selected_entry(&config, entry);
 
                 /* Optionally, read a random seed off the ESP and pass it to the OS */
                 (void) process_random_seed(root_dir, config.random_seed_mode);