sd-boot: Add support to boot last selected entry

Fixes: #18994
This commit is contained in:
Jan Janssen 2021-10-28 13:00:13 +02:00
parent 0c674ce5f2
commit ee4fd9cbd4
4 changed files with 54 additions and 8 deletions

View file

@ -111,8 +111,12 @@
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>

View 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>

View 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));

View 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);