diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index 3b726e63a4..e537426970 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -256,8 +256,12 @@ LoaderConfigTimeout - The menu time-out. Read by the boot loader. (Also, modified by it when the - t/T keys are used, see above.) + LoaderConfigTimeoutOneShot + The menu time-out in seconds. Read by the boot loader. LoaderConfigTimeout + is maintained persistently, while LoaderConfigTimeoutOneShot is a one-time override which is + read once (in which case it takes precedence over LoaderConfigTimeout) and then + removed. LoaderConfigTimeout may be manipulated with the + t/T keys, see above.) @@ -297,6 +301,14 @@ loader. + + LoaderFeatures + + A set of flags indicating the features the boot loader supports. Set by the boot loader. Use + bootctl1 to view this + data. + + LoaderFirmwareInfo LoaderFirmwareType diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 4ff7594a9a..d3a26fd7a0 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -62,6 +62,7 @@ typedef struct { BOOLEAN editor; BOOLEAN auto_entries; BOOLEAN auto_firmware; + BOOLEAN force_menu; UINTN console_mode; enum console_mode_change_type console_mode_change; } Config; @@ -385,10 +386,10 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) { Print(L"\n--- press key ---\n\n"); console_key_read(&key, TRUE); - Print(L"timeout: %d\n", config->timeout_sec); + Print(L"timeout: %u\n", config->timeout_sec); if (config->timeout_sec_efivar >= 0) Print(L"timeout (EFI var): %d\n", config->timeout_sec_efivar); - Print(L"timeout (config): %d\n", config->timeout_sec_config); + Print(L"timeout (config): %u\n", config->timeout_sec_config); if (config->entry_default_pattern) Print(L"default pattern: '%s'\n", config->entry_default_pattern); Print(L"editor: %s\n", yes_no(config->editor)); @@ -403,7 +404,8 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) { Print(L"\n"); if (efivar_get_int(L"LoaderConfigTimeout", &i) == EFI_SUCCESS) - Print(L"LoaderConfigTimeout: %d\n", i); + Print(L"LoaderConfigTimeout: %u\n", i); + if (config->entry_oneshot) Print(L"LoaderEntryOneShot: %s\n", config->entry_oneshot); if (efivar_get(L"LoaderDevicePartUUID", &partstr) == EFI_SUCCESS) @@ -1402,9 +1404,11 @@ static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) { UINTN sec; EFI_STATUS err; - config->editor = TRUE; - config->auto_entries = TRUE; - config->auto_firmware = TRUE; + *config = (Config) { + .editor = TRUE, + .auto_entries = TRUE, + .auto_firmware = TRUE, + }; err = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content, NULL); if (!EFI_ERROR(err)) @@ -1412,10 +1416,19 @@ static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) { err = efivar_get_int(L"LoaderConfigTimeout", &sec); if (!EFI_ERROR(err)) { - config->timeout_sec_efivar = sec; + config->timeout_sec_efivar = sec > INTN_MAX ? INTN_MAX : sec; config->timeout_sec = sec; } else config->timeout_sec_efivar = -1; + + err = efivar_get_int(L"LoaderConfigTimeoutOneShot", &sec); + if (!EFI_ERROR(err)) { + /* Unset variable now, after all it's "one shot". */ + (void) efivar_set(L"LoaderConfigTimeoutOneShot", NULL, TRUE); + + config->timeout_sec = sec; + config->force_menu = TRUE; /* force the menu when this is set */ + } } static VOID config_load_entries( @@ -2061,6 +2074,14 @@ static VOID config_write_entries_to_variable(Config *config) { } EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { + static const UINT64 loader_features = + (1ULL << 0) | /* I honour the LoaderConfigTimeout variable */ + (1ULL << 1) | /* I honour the LoaderConfigTimeoutOneShot variable */ + (1ULL << 2) | /* I honour the LoaderEntryDefault variable */ + (1ULL << 3) | /* I honour the LoaderEntryOneShot variable */ + (1ULL << 4) | /* I support boot counting */ + 0; + _cleanup_freepool_ CHAR16 *infostr = NULL, *typestr = NULL; CHAR8 *b; UINTN size; @@ -2084,6 +2105,8 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { typestr = PoolPrint(L"UEFI %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); efivar_set(L"LoaderFirmwareType", typestr, FALSE); + (void) efivar_set_raw(&loader_guid, L"LoaderFeatures", &loader_features, sizeof(loader_features), FALSE); + err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(err)) { @@ -2116,7 +2139,6 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { loaded_image_path = DevicePathToStr(loaded_image->FilePath); efivar_set(L"LoaderImageIdentifier", loaded_image_path, FALSE); - ZeroMem(&config, sizeof(Config)); config_load_defaults(&config, root_dir); /* scan /EFI/Linux/ directory */ @@ -2141,7 +2163,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { UINT64 osind = (UINT64)*b; if (osind & EFI_OS_INDICATIONS_BOOT_TO_FW_UI) - config_entry_add_call(&config, L"auto-reboot-into-firmware-ui", L"Reboot Into Firmware Interface", reboot_into_firmware); + config_entry_add_call(&config, L"auto-reboot-to-firmware-setup", L"Reboot Into Firmware Interface", reboot_into_firmware); FreePool(b); } @@ -2166,7 +2188,9 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { } /* select entry or show menu when key is pressed or timeout is set */ - if (config.timeout_sec == 0) { + if (config.force_menu || config.timeout_sec > 0) + menu = TRUE; + else { UINT64 key; err = console_key_read(&key, FALSE); @@ -2180,8 +2204,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { else menu = TRUE; } - } else - menu = TRUE; + } for (;;) { ConfigEntry *entry; @@ -2192,12 +2215,12 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { uefi_call_wrapper(BS->SetWatchdogTimer, 4, 0, 0x10000, 0, NULL); if (!menu_run(&config, &entry, loaded_image_path)) break; + } - /* run special entry like "reboot" */ - if (entry->call) { - entry->call(); - continue; - } + /* run special entry like "reboot" */ + if (entry->call) { + entry->call(); + continue; } config_entry_bump_counters(entry, root_dir); diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index b70777292e..f1f1674c59 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -59,7 +59,10 @@ UINT64 time_usec(VOID) { return 1000UL * 1000UL * ticks / freq; } -EFI_STATUS parse_boolean(CHAR8 *v, BOOLEAN *b) { +EFI_STATUS parse_boolean(const CHAR8 *v, BOOLEAN *b) { + if (!v) + return EFI_INVALID_PARAMETER; + if (strcmpa(v, (CHAR8 *)"1") == 0 || strcmpa(v, (CHAR8 *)"yes") == 0 || strcmpa(v, (CHAR8 *)"y") == 0 || @@ -79,46 +82,61 @@ EFI_STATUS parse_boolean(CHAR8 *v, BOOLEAN *b) { return EFI_INVALID_PARAMETER; } -EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, VOID *buf, UINTN size, BOOLEAN persistent) { +EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const CHAR16 *name, const VOID *buf, UINTN size, BOOLEAN persistent) { UINT32 flags; flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; if (persistent) flags |= EFI_VARIABLE_NON_VOLATILE; - return uefi_call_wrapper(RT->SetVariable, 5, name, (EFI_GUID *)vendor, flags, size, buf); + return uefi_call_wrapper(RT->SetVariable, 5, (CHAR16*) name, (EFI_GUID *)vendor, flags, size, (VOID*) buf); } -EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent) { +EFI_STATUS efivar_set(const CHAR16 *name, const CHAR16 *value, BOOLEAN persistent) { return efivar_set_raw(&loader_guid, name, value, value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, persistent); } EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent) { CHAR16 str[32]; - SPrint(str, 32, L"%d", i); + SPrint(str, 32, L"%u", i); return efivar_set(name, str, persistent); } -EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value) { +EFI_STATUS efivar_get(const CHAR16 *name, CHAR16 **value) { _cleanup_freepool_ CHAR8 *buf = NULL; + EFI_STATUS err; CHAR16 *val; UINTN size; - EFI_STATUS err; err = efivar_get_raw(&loader_guid, name, &buf, &size); if (EFI_ERROR(err)) return err; - val = StrDuplicate((CHAR16 *)buf); + /* Make sure there are no incomplete characters in the buffer */ + if ((size % 2) != 0) + return EFI_INVALID_PARAMETER; + + /* Return buffer directly if it happens to be NUL terminated already */ + if (size >= 2 && buf[size-2] == 0 && buf[size-1] == 0) { + *value = (CHAR16*) buf; + buf = NULL; + return EFI_SUCCESS; + } + + /* Make sure a terminating NUL is available at the end */ + val = AllocatePool(size + 2); if (!val) return EFI_OUT_OF_RESOURCES; + CopyMem(val, buf, size); + val[size/2] = 0; /* NUL terminate */ + *value = val; return EFI_SUCCESS; } -EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i) { +EFI_STATUS efivar_get_int(const CHAR16 *name, UINTN *i) { _cleanup_freepool_ CHAR16 *val = NULL; EFI_STATUS err; @@ -129,7 +147,7 @@ EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i) { return err; } -EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size) { +EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const CHAR16 *name, CHAR8 **buffer, UINTN *size) { _cleanup_freepool_ CHAR8 *buf = NULL; UINTN l; EFI_STATUS err; @@ -139,7 +157,7 @@ EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, if (!buf) return EFI_OUT_OF_RESOURCES; - err = uefi_call_wrapper(RT->GetVariable, 5, name, (EFI_GUID *)vendor, NULL, &l, buf); + err = uefi_call_wrapper(RT->GetVariable, 5, (CHAR16*) name, (EFI_GUID *)vendor, NULL, &l, buf); if (!EFI_ERROR(err)) { *buffer = buf; buf = NULL; @@ -287,12 +305,12 @@ CHAR8 *strchra(CHAR8 *s, CHAR8 c) { return NULL; } -EFI_STATUS file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content, UINTN *content_size) { +EFI_STATUS file_read(EFI_FILE_HANDLE dir, const CHAR16 *name, UINTN off, UINTN size, CHAR8 **content, UINTN *content_size) { EFI_FILE_HANDLE handle; _cleanup_freepool_ CHAR8 *buf = NULL; EFI_STATUS err; - err = uefi_call_wrapper(dir->Open, 5, dir, &handle, name, EFI_FILE_MODE_READ, 0ULL); + err = uefi_call_wrapper(dir->Open, 5, dir, &handle, (CHAR16*) name, EFI_FILE_MODE_READ, 0ULL); if (EFI_ERROR(err)) return err; diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index daa386b769..f7e0e49c06 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -11,26 +11,26 @@ static inline const CHAR16 *yes_no(BOOLEAN b) { return b ? L"yes" : L"no"; } -EFI_STATUS parse_boolean(CHAR8 *v, BOOLEAN *b); +EFI_STATUS parse_boolean(const CHAR8 *v, BOOLEAN *b); UINT64 ticks_read(void); UINT64 ticks_freq(void); UINT64 time_usec(void); -EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent); -EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, VOID *buf, UINTN size, BOOLEAN persistent); +EFI_STATUS efivar_set(const CHAR16 *name, const CHAR16 *value, BOOLEAN persistent); +EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const CHAR16 *name, const VOID *buf, UINTN size, BOOLEAN persistent); EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent); VOID efivar_set_time_usec(CHAR16 *name, UINT64 usec); -EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value); -EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size); -EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i); +EFI_STATUS efivar_get(const CHAR16 *name, CHAR16 **value); +EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const CHAR16 *name, CHAR8 **buffer, UINTN *size); +EFI_STATUS efivar_get_int(const CHAR16 *name, UINTN *i); CHAR8 *strchra(CHAR8 *s, CHAR8 c); CHAR16 *stra_to_path(CHAR8 *stra); CHAR16 *stra_to_str(CHAR8 *stra); -EFI_STATUS file_read(EFI_FILE_HANDLE dir, CHAR16 *name, UINTN off, UINTN size, CHAR8 **content, UINTN *content_size); +EFI_STATUS file_read(EFI_FILE_HANDLE dir, const CHAR16 *name, UINTN off, UINTN size, CHAR8 **content, UINTN *content_size); static inline void FreePoolp(void *p) { void *q = *(void**) p; @@ -52,3 +52,6 @@ static inline void FileHandleClosep(EFI_FILE_HANDLE *handle) { } const EFI_GUID loader_guid; + +#define UINTN_MAX (~(UINTN)0) +#define INTN_MAX ((INTN)(UINTN_MAX>>1))