diff --git a/man/bootctl.xml b/man/bootctl.xml
index 73cacc6107..a0be688321 100644
--- a/man/bootctl.xml
+++ b/man/bootctl.xml
@@ -99,8 +99,8 @@
IDID
- Sets the default boot loader entry. Takes a single boot loader entry ID string as
- argument. The command will set the default entry only for the next boot,
+ 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.Optionally, the boot loader entry ID may be specified as one of: ,
@@ -111,8 +111,12 @@
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.
- 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.
+
+ If set to the chosen entry will be saved as an EFI variable
+ on every boot and automatically selected the next time the boot loader starts.
+
+ When an empty string ("") is specified as an ID, then the corresponding EFI variable will be unset.
diff --git a/man/loader.conf.xml b/man/loader.conf.xml
index 3a954a3ce9..5343751ece 100644
--- a/man/loader.conf.xml
+++ b/man/loader.conf.xml
@@ -60,6 +60,9 @@
selected entry will be stored as an EFI variable, overriding this option.
+ If set to @saved the chosen entry will be saved as an EFI variable
+ on every boot and automatically selected the next time the boot loader starts.
+
Automatically detected entries will use the following names:
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c
index 449eed0bed..6243c6e2f5 100644
--- a/src/boot/bootctl.c
+++ b/src/boot/bootctl.c
@@ -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));
diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c
index 729e199fcc..db0c22d405 100644
--- a/src/boot/efi/boot.c
+++ b/src/boot/efi/boot.c
@@ -67,13 +67,17 @@ typedef struct {
UINT32 timeout_sec; /* Actual timeout used (efi_main() override > efivar > config). */
UINT32 timeout_sec_config;
UINT32 timeout_sec_efivar;
- CHAR16 *entry_default_pattern;
+ 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;
@@ -435,7 +439,7 @@ static void print_status(Config *config, CHAR16 *loaded_image_path) {
UINT64 key;
UINTN x_max, y_max;
SecureBootMode secure;
- _cleanup_freepool_ CHAR16 *device_part_uuid = NULL, *default_efivar = NULL;
+ _cleanup_freepool_ CHAR16 *device_part_uuid = NULL;
assert(config);
assert(loaded_image_path);
@@ -445,7 +449,6 @@ static void print_status(Config *config, CHAR16 *loaded_image_path) {
secure = secure_boot_mode();
(void) efivar_get(LOADER_GUID, L"LoaderDevicePartUUID", &device_part_uuid);
- (void) efivar_get(LOADER_GUID, L"LoaderEntryDefault", &default_efivar);
/* We employ some unusual indentation here for readability. */
@@ -486,9 +489,10 @@ static void print_status(Config *config, CHAR16 *loaded_image_path) {
Print(L" timeout (EFI var): %lu s\n", config->timeout_sec_efivar);
}
- ps_string(L" default: %s\n", config->entry_default_pattern);
+ 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" default (EFI var): %s\n", default_efivar);
+ 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);
@@ -586,6 +590,7 @@ static BOOLEAN menu_run(
INT16 idx;
BOOLEAN exit = FALSE, run = TRUE, firmware_setup = FALSE;
INT64 console_mode_initial = ST->ConOut->Mode->Mode, console_mode_efivar_saved = config->console_mode_efivar;
+ INTN default_efivar_saved = config->idx_default_efivar;
graphics_mode(FALSE);
ST->ConIn->Reset(ST->ConIn, FALSE);
@@ -831,20 +836,20 @@ static BOOLEAN menu_run(
case KEYPRESS(0, 0, 'd'):
case KEYPRESS(0, 0, 'D'):
if (config->idx_default_efivar != (INTN)idx_highlight) {
- /* store the selected entry in a persistent EFI variable */
- efivar_set(
- LOADER_GUID,
- L"LoaderEntryDefault",
- config->entries[idx_highlight]->id,
- EFI_VARIABLE_NON_VOLATILE);
+ FreePool(config->entry_default_efivar);
+ config->entry_default_efivar = StrDuplicate(config->entries[idx_highlight]->id);
+ if (!config->entry_default_efivar) {
+ log_oom();
+ return FALSE;
+ }
config->idx_default_efivar = idx_highlight;
status = StrDuplicate(L"Default boot entry selected.");
} else {
- /* clear the default entry EFI variable */
- efivar_set(LOADER_GUID, L"LoaderEntryDefault", NULL, EFI_VARIABLE_NON_VOLATILE);
+ config->entry_default_efivar = mfree(config->entry_default_efivar);
config->idx_default_efivar = -1;
status = StrDuplicate(L"Default boot entry cleared.");
}
+ config->use_saved_entry_efivar = FALSE;
refresh = TRUE;
break;
@@ -954,6 +959,9 @@ static BOOLEAN menu_run(
/* Update EFI vars after we left the menu to reduce NVRAM writes. */
+ if (default_efivar_saved != config->idx_default_efivar)
+ efivar_set(LOADER_GUID, L"LoaderEntryDefault", config->entry_default_efivar, EFI_VARIABLE_NON_VOLATILE);
+
if (console_mode_efivar_saved != config->console_mode_efivar) {
if (config->console_mode_efivar == CONSOLE_MODE_KEEP)
efivar_set(LOADER_GUID, L"LoaderConfigConsoleMode", NULL, EFI_VARIABLE_NON_VOLATILE);
@@ -1109,9 +1117,12 @@ static void config_defaults_load_from_file(Config *config, CHAR8 *content) {
}
if (strcmpa((CHAR8 *)"default", key) == 0) {
- FreePool(config->entry_default_pattern);
- config->entry_default_pattern = stra_to_str(value);
- StrLwr(config->entry_default_pattern);
+ 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;
}
@@ -1537,6 +1548,18 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigConsoleMode", &value);
if (!EFI_ERROR(err))
config->console_mode_efivar = value;
+
+ err = efivar_get(LOADER_GUID, L"LoaderEntryOneShot", &config->entry_oneshot);
+ if (!EFI_ERROR(err))
+ /* Unset variable now, after all it's "one shot". */
+ (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(
@@ -1622,69 +1645,45 @@ static void config_sort_entries(Config *config) {
sort_pointer_array((void**) config->entries, config->entry_count, (compare_pointer_func_t) config_entry_compare);
}
-static INTN config_entry_find(Config *config, CHAR16 *id) {
+static INTN config_entry_find(Config *config, CHAR16 *needle) {
assert(config);
- assert(id);
+
+ if (!needle)
+ return -1;
for (UINTN i = 0; i < config->entry_count; i++)
- if (StrCmp(config->entries[i]->id, id) == 0)
+ if (MetaiMatch(config->entries[i]->id, needle))
return (INTN) i;
return -1;
}
static void config_default_entry_select(Config *config) {
- _cleanup_freepool_ CHAR16 *entry_default = NULL;
- EFI_STATUS err;
INTN i;
assert(config);
- /*
- * The EFI variable to specify a boot entry for the next, and only the
- * next reboot. The variable is always cleared directly after it is read.
- */
- err = efivar_get(LOADER_GUID, L"LoaderEntryOneShot", &config->entry_oneshot);
- if (!EFI_ERROR(err)) {
- efivar_set(LOADER_GUID, L"LoaderEntryOneShot", NULL, EFI_VARIABLE_NON_VOLATILE);
-
- i = config_entry_find(config, config->entry_oneshot);
- if (i >= 0) {
- config->idx_default = i;
- return;
- }
- }
-
- /*
- * The EFI variable to select the default boot entry overrides the
- * configured pattern. The variable can be set and cleared by pressing
- * the 'd' key in the loader selection menu.
- */
- err = efivar_get(LOADER_GUID, L"LoaderEntryDefault", &entry_default);
- if (!EFI_ERROR(err)) {
- i = config_entry_find(config, entry_default);
- if (i >= 0) {
- config->idx_default = i;
- config->idx_default_efivar = i;
- return;
- }
- }
-
- if (config->entry_count == 0)
+ i = config_entry_find(config, config->entry_oneshot);
+ if (i >= 0) {
+ config->idx_default = i;
return;
+ }
- /*
- * Match the pattern from the end of the list to the start, find last
- * entry (largest number) matching the given pattern.
- */
- if (config->entry_default_pattern) {
- i = config->entry_count;
- while (i--) {
- if (MetaiMatch(config->entries[i]->id, config->entry_default_pattern)) {
- config->idx_default = i;
- return;
- }
- }
+ 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;
+ }
+
+ 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;
}
/* select the last suitable entry */
@@ -2224,7 +2223,7 @@ static void config_free(Config *config) {
for (UINTN i = 0; i < config->entry_count; i++)
config_entry_free(config->entries[i]);
FreePool(config->entries);
- FreePool(config->entry_default_pattern);
+ FreePool(config->entry_default_config);
FreePool(config->options_edit);
FreePool(config->entry_oneshot);
}
@@ -2256,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,
@@ -2434,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);