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:
commit
3663f754f1
@ -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>
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user