1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2024-12-23 17:34:00 +03:00

Merge pull request #21070 from medhefgo/boot-save

sd-boot: Add support to boot last select entry
This commit is contained in:
Lennart Poettering 2021-11-04 17:37:12 +01:00 committed by GitHub
commit 3663f754f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 73 deletions

View File

@ -99,8 +99,8 @@
<term><option>set-default</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-oneshot</option> <replaceable>ID</replaceable></term>
<listitem><para>Sets the default boot loader entry. Takes a single boot loader entry ID string as <listitem><para>Sets the default boot loader entry. Takes a single boot loader entry ID string or a glob
argument. The <option>set-oneshot</option> command will set the default entry only for the next boot, 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.</para></listitem> the <option>set-default</option> will set it persistently for all future boots.</para></listitem>
<listitem><para>Optionally, the boot loader entry ID may be specified as one of: <option>@default</option>, <listitem><para>Optionally, the boot loader entry ID may be specified as one of: <option>@default</option>,
@ -111,8 +111,12 @@
see <ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink> for details. 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 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 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. loader entry for all future boots, but may be used for other operations too.</para>
When an empty string ("") is specified as an ID, then the corresponding EFI variable will be unset.
<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> </para></listitem>
</varlistentry> </varlistentry>

View File

@ -60,6 +60,9 @@
selected entry will be stored as an EFI variable, overriding this option. selected entry will be stored as an EFI variable, overriding this option.
</para> </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> <table>
<title>Automatically detected entries will use the following names:</title> <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) if (r < 0)
return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryDefault': %m"); 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); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported special entry identifier: %s", arg1);
else { else {
encoded = utf8_to_utf16(arg1, strlen(arg1)); encoded = utf8_to_utf16(arg1, strlen(arg1));

View File

@ -67,13 +67,17 @@ typedef struct {
UINT32 timeout_sec; /* Actual timeout used (efi_main() override > efivar > config). */ UINT32 timeout_sec; /* Actual timeout used (efi_main() override > efivar > config). */
UINT32 timeout_sec_config; UINT32 timeout_sec_config;
UINT32 timeout_sec_efivar; UINT32 timeout_sec_efivar;
CHAR16 *entry_default_pattern; CHAR16 *entry_default_config;
CHAR16 *entry_default_efivar;
CHAR16 *entry_oneshot; CHAR16 *entry_oneshot;
CHAR16 *entry_saved;
CHAR16 *options_edit; CHAR16 *options_edit;
BOOLEAN editor; BOOLEAN editor;
BOOLEAN auto_entries; BOOLEAN auto_entries;
BOOLEAN auto_firmware; BOOLEAN auto_firmware;
BOOLEAN force_menu; BOOLEAN force_menu;
BOOLEAN use_saved_entry;
BOOLEAN use_saved_entry_efivar;
INT64 console_mode; INT64 console_mode;
INT64 console_mode_efivar; INT64 console_mode_efivar;
RandomSeedMode random_seed_mode; RandomSeedMode random_seed_mode;
@ -435,7 +439,7 @@ static void print_status(Config *config, CHAR16 *loaded_image_path) {
UINT64 key; UINT64 key;
UINTN x_max, y_max; UINTN x_max, y_max;
SecureBootMode secure; SecureBootMode secure;
_cleanup_freepool_ CHAR16 *device_part_uuid = NULL, *default_efivar = NULL; _cleanup_freepool_ CHAR16 *device_part_uuid = NULL;
assert(config); assert(config);
assert(loaded_image_path); assert(loaded_image_path);
@ -445,7 +449,6 @@ static void print_status(Config *config, CHAR16 *loaded_image_path) {
secure = secure_boot_mode(); secure = secure_boot_mode();
(void) efivar_get(LOADER_GUID, L"LoaderDevicePartUUID", &device_part_uuid); (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. */ /* 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); 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 (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" editor: %s\n", config->editor);
ps_bool(L" auto-entries: %s\n", config->auto_entries); ps_bool(L" auto-entries: %s\n", config->auto_entries);
ps_bool(L" auto-firmware: %s\n", config->auto_firmware); ps_bool(L" auto-firmware: %s\n", config->auto_firmware);
@ -586,6 +590,7 @@ static BOOLEAN menu_run(
INT16 idx; INT16 idx;
BOOLEAN exit = FALSE, run = TRUE, firmware_setup = FALSE; BOOLEAN exit = FALSE, run = TRUE, firmware_setup = FALSE;
INT64 console_mode_initial = ST->ConOut->Mode->Mode, console_mode_efivar_saved = config->console_mode_efivar; 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); graphics_mode(FALSE);
ST->ConIn->Reset(ST->ConIn, 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'):
case KEYPRESS(0, 0, 'D'): case KEYPRESS(0, 0, 'D'):
if (config->idx_default_efivar != (INTN)idx_highlight) { if (config->idx_default_efivar != (INTN)idx_highlight) {
/* store the selected entry in a persistent EFI variable */ FreePool(config->entry_default_efivar);
efivar_set( config->entry_default_efivar = StrDuplicate(config->entries[idx_highlight]->id);
LOADER_GUID, if (!config->entry_default_efivar) {
L"LoaderEntryDefault", log_oom();
config->entries[idx_highlight]->id, return FALSE;
EFI_VARIABLE_NON_VOLATILE); }
config->idx_default_efivar = idx_highlight; config->idx_default_efivar = idx_highlight;
status = StrDuplicate(L"Default boot entry selected."); status = StrDuplicate(L"Default boot entry selected.");
} else { } else {
/* clear the default entry EFI variable */ config->entry_default_efivar = mfree(config->entry_default_efivar);
efivar_set(LOADER_GUID, L"LoaderEntryDefault", NULL, EFI_VARIABLE_NON_VOLATILE);
config->idx_default_efivar = -1; config->idx_default_efivar = -1;
status = StrDuplicate(L"Default boot entry cleared."); status = StrDuplicate(L"Default boot entry cleared.");
} }
config->use_saved_entry_efivar = FALSE;
refresh = TRUE; refresh = TRUE;
break; break;
@ -954,6 +959,9 @@ static BOOLEAN menu_run(
/* Update EFI vars after we left the menu to reduce NVRAM writes. */ /* 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 (console_mode_efivar_saved != config->console_mode_efivar) {
if (config->console_mode_efivar == CONSOLE_MODE_KEEP) if (config->console_mode_efivar == CONSOLE_MODE_KEEP)
efivar_set(LOADER_GUID, L"LoaderConfigConsoleMode", NULL, EFI_VARIABLE_NON_VOLATILE); 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) { if (strcmpa((CHAR8 *)"default", key) == 0) {
FreePool(config->entry_default_pattern); if (value[0] == '@' && strcmpa((CHAR8 *)"@saved", value) != 0) {
config->entry_default_pattern = stra_to_str(value); log_error_stall(L"Unsupported special entry identifier: %a", value);
StrLwr(config->entry_default_pattern); continue;
}
FreePool(config->entry_default_config);
config->entry_default_config = stra_to_str(value);
continue; 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); err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigConsoleMode", &value);
if (!EFI_ERROR(err)) if (!EFI_ERROR(err))
config->console_mode_efivar = value; 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( 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); 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(config);
assert(id);
if (!needle)
return -1;
for (UINTN i = 0; i < config->entry_count; i++) 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 (INTN) i;
return -1; return -1;
} }
static void config_default_entry_select(Config *config) { static void config_default_entry_select(Config *config) {
_cleanup_freepool_ CHAR16 *entry_default = NULL;
EFI_STATUS err;
INTN i; INTN i;
assert(config); assert(config);
/* i = config_entry_find(config, config->entry_oneshot);
* The EFI variable to specify a boot entry for the next, and only the if (i >= 0) {
* next reboot. The variable is always cleared directly after it is read. config->idx_default = i;
*/
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)
return; return;
}
/* i = config_entry_find(config, config->use_saved_entry_efivar ? config->entry_saved : config->entry_default_efivar);
* Match the pattern from the end of the list to the start, find last if (i >= 0) {
* entry (largest number) matching the given pattern. config->idx_default = i;
*/ config->idx_default_efivar = i;
if (config->entry_default_pattern) { return;
i = config->entry_count; }
while (i--) {
if (MetaiMatch(config->entries[i]->id, config->entry_default_pattern)) { if (config->use_saved_entry)
config->idx_default = i; /* No need to do the same thing twice. */
return; 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 */ /* select the last suitable entry */
@ -2224,7 +2223,7 @@ static void config_free(Config *config) {
for (UINTN i = 0; i < config->entry_count; i++) for (UINTN i = 0; i < config->entry_count; i++)
config_entry_free(config->entries[i]); config_entry_free(config->entries[i]);
FreePool(config->entries); FreePool(config->entries);
FreePool(config->entry_default_pattern); FreePool(config->entry_default_config);
FreePool(config->options_edit); FreePool(config->options_edit);
FreePool(config->entry_oneshot); 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); (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( static void export_variables(
EFI_LOADED_IMAGE *loaded_image, EFI_LOADED_IMAGE *loaded_image,
const CHAR16 *loaded_image_path, 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); config_entry_bump_counters(entry, root_dir);
save_selected_entry(&config, entry);
/* Export the selected boot entry to the system */
(void) efivar_set(LOADER_GUID, L"LoaderEntrySelected", entry->id, 0);
/* Optionally, read a random seed off the ESP and pass it to the OS */ /* Optionally, read a random seed off the ESP and pass it to the OS */
(void) process_random_seed(root_dir, config.random_seed_mode); (void) process_random_seed(root_dir, config.random_seed_mode);