From d10ba83a1b67f6a0b758a5e0f8cea0e34222ef7f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jun 2018 18:47:33 +0200 Subject: [PATCH 01/16] mkosi: update the boot loader from our freshly built one --- mkosi.build | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mkosi.build b/mkosi.build index b7b8c50a2b..114e617c85 100755 --- a/mkosi.build +++ b/mkosi.build @@ -90,3 +90,8 @@ cat > "$DESTDIR"/etc/issue < Date: Thu, 21 Jun 2018 18:49:52 +0200 Subject: [PATCH 02/16] efi: add poor man's offsetof() implementation --- src/boot/efi/util.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 8a3e0520fc..3e6e610289 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -5,6 +5,7 @@ #include #define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) +#define OFFSETOF(x,y) ((UINTN) &(((x*)0)->y)) static inline const CHAR16 *yes_no(BOOLEAN b) { return b ? L"yes" : L"no"; From 3aaa95e0a09ef1d838cc8dd8b89e7caaeba415e6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jun 2018 18:50:07 +0200 Subject: [PATCH 03/16] efi: add cleanup handler for closing file descriptors --- src/boot/efi/util.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 3e6e610289..6d1a52f618 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -38,3 +38,10 @@ static inline void FreePoolp(void *p) { #define _cleanup_(x) __attribute__((cleanup(x))) #define _cleanup_freepool_ _cleanup_(FreePoolp) + +static inline void FileHandleClosep(EFI_FILE_HANDLE *handle) { + if (!*handle) + return; + + uefi_call_wrapper((*handle)->Close, 1, *handle); +} From 2880e665ab3c72ce029cc31289c61ae72fbe667a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jun 2018 18:48:21 +0200 Subject: [PATCH 04/16] efi: explicity check for NULL in FreePoolp() Firmware implementations are generally pretty bad, hence let's better add an explicit check for NULL before invokin FreePool(), in particular is it doesn't appear to be documented whether FreePool() is supposed to be happy with NULL. --- src/boot/efi/util.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 6d1a52f618..9921e8bc0a 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -33,7 +33,12 @@ 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); static inline void FreePoolp(void *p) { - FreePool(*(void**) p); + void *q = *(void**) p; + + if (!q) + return; + + FreePool(q); } #define _cleanup_(x) __attribute__((cleanup(x))) From 1336bb9864108b60296151f745cf264170ed6910 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jun 2018 18:25:19 +0200 Subject: [PATCH 05/16] sd-boot: drop initialization of 'line' which we override in the next line anyway --- src/boot/efi/boot.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 48c9db7c15..0c4ff40afe 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -968,7 +968,6 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) { UINTN pos = 0; CHAR8 *key, *value; - line = content; while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { if (strcmpa((CHAR8 *)"timeout", key) == 0) { _cleanup_freepool_ CHAR16 *s = NULL; @@ -1040,7 +1039,6 @@ static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR1 entry = AllocateZeroPool(sizeof(ConfigEntry)); - line = content; while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { if (strcmpa((CHAR8 *)"title", key) == 0) { FreePool(entry->title); @@ -1560,7 +1558,6 @@ static VOID config_entry_add_linux(Config *config, EFI_LOADED_IMAGE *loaded_imag continue; /* read properties from the embedded os-release file */ - line = content; while ((line = line_get_key_value(content, (CHAR8 *)"=", &pos, &key, &value))) { if (strcmpa((CHAR8 *)"PRETTY_NAME", key) == 0) { FreePool(os_name); From 580fe4df59cc7f3c6945fbecc39a1c38edecd629 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jun 2018 18:45:58 +0200 Subject: [PATCH 06/16] sd-boot: break overly long function argument lists following our usual coding style --- src/boot/efi/boot.c | 84 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 0c4ff40afe..045565b958 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -68,14 +68,24 @@ static VOID cursor_left(UINTN *cursor, UINTN *first) { (*first)--; } -static VOID cursor_right(UINTN *cursor, UINTN *first, UINTN x_max, UINTN len) { +static VOID cursor_right( + UINTN *cursor, + UINTN *first, + UINTN x_max, + UINTN len) { + if ((*cursor)+1 < x_max) (*cursor)++; else if ((*first) + (*cursor) < len) (*first)++; } -static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN y_pos) { +static BOOLEAN line_edit( + CHAR16 *line_in, + CHAR16 **line_out, + UINTN x_max, + UINTN y_pos) { + _cleanup_freepool_ CHAR16 *line = NULL, *print = NULL; UINTN size, len, first, cursor, clear; BOOLEAN exit, enter; @@ -442,7 +452,11 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) { uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); } -static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *loaded_image_path) { +static BOOLEAN menu_run( + Config *config, + ConfigEntry **chosen_entry, + CHAR16 *loaded_image_path) { + EFI_STATUS err; UINTN visible_max; UINTN idx_highlight; @@ -901,7 +915,13 @@ static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2) { return StrCmp(os1, os2); } -static CHAR8 *line_get_key_value(CHAR8 *content, CHAR8 *sep, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) { +static CHAR8 *line_get_key_value( + CHAR8 *content, + CHAR8 *sep, + UINTN *pos, + CHAR8 **key_ret, + CHAR8 **value_ret) { + CHAR8 *line; UINTN linelen; CHAR8 *value; @@ -1029,7 +1049,13 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) { } } -static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR16 *file, CHAR8 *content, CHAR16 *loaded_image_path) { +static VOID config_entry_add_from_file( + Config *config, + EFI_HANDLE *device, + CHAR16 *file, + CHAR8 *content, + CHAR16 *loaded_image_path) { + ConfigEntry *entry; CHAR8 *line; UINTN pos = 0; @@ -1175,7 +1201,12 @@ static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) { config->timeout_sec_efivar = -1; } -static VOID config_load_entries(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path) { +static VOID config_load_entries( + Config *config, + EFI_HANDLE *device, + EFI_FILE *root_dir, + CHAR16 *loaded_image_path) { + EFI_FILE_HANDLE entries_dir; EFI_STATUS err; @@ -1401,7 +1432,11 @@ static VOID config_title_generate(Config *config) { } } -static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS (*call)(VOID)) { +static BOOLEAN config_entry_add_call( + Config *config, + CHAR16 *title, + EFI_STATUS (*call)(VOID)) { + ConfigEntry *entry; entry = AllocateZeroPool(sizeof(ConfigEntry)); @@ -1412,8 +1447,15 @@ static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS ( return TRUE; } -static ConfigEntry *config_entry_add_loader(Config *config, EFI_HANDLE *device, - enum loader_type type,CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { +static ConfigEntry *config_entry_add_loader( + Config *config, + EFI_HANDLE *device, + enum loader_type type, + CHAR16 *file, + CHAR16 key, + CHAR16 *title, + CHAR16 *loader) { + ConfigEntry *entry; entry = AllocateZeroPool(sizeof(ConfigEntry)); @@ -1429,8 +1471,16 @@ static ConfigEntry *config_entry_add_loader(Config *config, EFI_HANDLE *device, return entry; } -static BOOLEAN config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path, - CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) { +static BOOLEAN config_entry_add_loader_auto( + Config *config, + EFI_HANDLE *device, + EFI_FILE *root_dir, + CHAR16 *loaded_image_path, + CHAR16 *file, + CHAR16 key, + CHAR16 *title, + CHAR16 *loader) { + EFI_FILE_HANDLE handle; ConfigEntry *entry; EFI_STATUS err; @@ -1502,7 +1552,11 @@ static VOID config_entry_add_osx(Config *config) { } } -static VOID config_entry_add_linux(Config *config, EFI_LOADED_IMAGE *loaded_image, EFI_FILE *root_dir) { +static VOID config_entry_add_linux( + Config *config, + EFI_LOADED_IMAGE *loaded_image, + EFI_FILE *root_dir) { + EFI_FILE_HANDLE linux_dir; EFI_STATUS err; ConfigEntry *entry; @@ -1619,7 +1673,11 @@ static VOID config_entry_add_linux(Config *config, EFI_LOADED_IMAGE *loaded_imag uefi_call_wrapper(linux_dir->Close, 1, linux_dir); } -static EFI_STATUS image_start(EFI_HANDLE parent_image, const Config *config, const ConfigEntry *entry) { +static EFI_STATUS image_start( + EFI_HANDLE parent_image, + const Config *config, + const ConfigEntry *entry) { + EFI_HANDLE image; _cleanup_freepool_ EFI_DEVICE_PATH *path = NULL; CHAR16 *options; From 081cc95feceec71f08b804570460706719f21abb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jun 2018 18:49:10 +0200 Subject: [PATCH 07/16] sd-boot: rename ConfigEntry field 'file' to 'id' The field derives from a file name only in very specific cases, for many cases it's a fixed string (for example, all "auto-" items are like this). Also, even when it derives from a file name, it is processed a bit, as suffixes are removed and the string is converted to lower case. hence, let's name this field "id" instead, because that's what it is used for: as general identification token. --- src/boot/efi/boot.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 045565b958..82b8939740 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -28,7 +28,7 @@ enum loader_type { }; typedef struct { - CHAR16 *file; + CHAR16 *id; /* The identifier for this entry (note that this id is not necessarily unique though!) */ CHAR16 *title_show; CHAR16 *title; CHAR16 *version; @@ -417,8 +417,8 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) { entry = config->entries[i]; Print(L"config entry: %d/%d\n", i+1, config->entry_count); - if (entry->file) - Print(L"file '%s'\n", entry->file); + if (entry->id) + Print(L"id '%s'\n", entry->id); Print(L"title show '%s'\n", entry->title_show); if (entry->title) Print(L"title '%s'\n", entry->title); @@ -727,7 +727,7 @@ static BOOLEAN menu_run( case KEYPRESS(0, 0, 'd'): if (config->idx_default_efivar != (INTN)idx_highlight) { /* store the selected entry in a persistent EFI variable */ - efivar_set(L"LoaderEntryDefault", config->entries[idx_highlight]->file, TRUE); + efivar_set(L"LoaderEntryDefault", config->entries[idx_highlight]->id, TRUE); config->idx_default_efivar = idx_highlight; status = StrDuplicate(L"Default boot entry selected."); } else { @@ -1170,12 +1170,12 @@ static VOID config_entry_add_from_file( } entry->device = device; - entry->file = StrDuplicate(file); - len = StrLen(entry->file); + entry->id = StrDuplicate(file); + len = StrLen(entry->id); /* remove ".conf" */ if (len > 5) - entry->file[len - 5] = '\0'; - StrLwr(entry->file); + entry->id[len - 5] = '\0'; + StrLwr(entry->id); config_add_entry(config, entry); } @@ -1257,7 +1257,7 @@ static VOID config_sort_entries(Config *config) { for (k = 0; k < config->entry_count - i; k++) { ConfigEntry *entry; - if (str_verscmp(config->entries[k]->file, config->entries[k+1]->file) <= 0) + if (str_verscmp(config->entries[k]->id, config->entries[k+1]->id) <= 0) continue; entry = config->entries[k]; config->entries[k] = config->entries[k+1]; @@ -1283,7 +1283,7 @@ static VOID config_default_entry_select(Config *config) { BOOLEAN found = FALSE; for (i = 0; i < config->entry_count; i++) - if (StrCmp(config->entries[i]->file, entry_oneshot) == 0) { + if (StrCmp(config->entries[i]->id, entry_oneshot) == 0) { config->idx_default = i; found = TRUE; break; @@ -1304,7 +1304,7 @@ static VOID config_default_entry_select(Config *config) { err = efivar_get(L"LoaderEntryDefault", &entry_default); if (!EFI_ERROR(err)) { for (i = 0; i < config->entry_count; i++) - if (StrCmp(config->entries[i]->file, entry_default) == 0) { + if (StrCmp(config->entries[i]->id, entry_default) == 0) { config->idx_default = i; config->idx_default_efivar = i; return; @@ -1324,7 +1324,7 @@ static VOID config_default_entry_select(Config *config) { while (i--) { if (config->entries[i]->no_autoselect) continue; - if (MetaiMatch(config->entries[i]->file, config->entry_default_pattern)) { + if (MetaiMatch(config->entries[i]->id, config->entry_default_pattern)) { config->idx_default = i; return; } @@ -1375,7 +1375,7 @@ static VOID config_title_generate(Config *config) { FreePool(config->entries[i]->title_show); title = config->entries[i]->title; if (!title) - title = config->entries[i]->file; + title = config->entries[i]->id; config->entries[i]->title_show = StrDuplicate(title); } @@ -1425,7 +1425,7 @@ static VOID config_title_generate(Config *config) { if (!config->entries[i]->non_unique) continue; - s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, config->entries[i]->file); + s = PoolPrint(L"%s (%s)", config->entries[i]->title_show, config->entries[i]->id); FreePool(config->entries[i]->title_show); config->entries[i]->title_show = s; config->entries[i]->non_unique = FALSE; @@ -1451,7 +1451,7 @@ static ConfigEntry *config_entry_add_loader( Config *config, EFI_HANDLE *device, enum loader_type type, - CHAR16 *file, + CHAR16 *id, CHAR16 key, CHAR16 *title, CHAR16 *loader) { @@ -1463,8 +1463,8 @@ static ConfigEntry *config_entry_add_loader( entry->title = StrDuplicate(title); entry->device = device; entry->loader = StrDuplicate(loader); - entry->file = StrDuplicate(file); - StrLwr(entry->file); + entry->id = StrDuplicate(id); + StrLwr(entry->id); entry->key = key; config_add_entry(config, entry); @@ -1476,7 +1476,7 @@ static BOOLEAN config_entry_add_loader_auto( EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path, - CHAR16 *file, + CHAR16 *id, CHAR16 key, CHAR16 *title, CHAR16 *loader) { @@ -1514,7 +1514,7 @@ static BOOLEAN config_entry_add_loader_auto( return FALSE; uefi_call_wrapper(handle->Close, 1, handle); - entry = config_entry_add_loader(config, device, LOADER_UNDEFINED, file, key, title, loader); + entry = config_entry_add_loader(config, device, LOADER_UNDEFINED, id, key, title, loader); if (!entry) return FALSE; @@ -1907,7 +1907,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { } /* export the selected boot entry to the system */ - efivar_set(L"LoaderEntrySelected", entry->file, FALSE); + efivar_set(L"LoaderEntrySelected", entry->id, FALSE); uefi_call_wrapper(BS->SetWatchdogTimer, 4, 5 * 60, 0x10000, 0, NULL); err = image_start(image, &config, entry); From 42cf81c26fad4b3ef4611d2887230707835c4c70 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jun 2018 23:20:47 +0200 Subject: [PATCH 08/16] sd-boot: properly free all config entry fields --- src/boot/efi/boot.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 82b8939740..cb041363cd 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -851,11 +851,17 @@ static VOID config_add_entry(Config *config, ConfigEntry *entry) { } static VOID config_entry_free(ConfigEntry *entry) { + if (!entry) + return; + + FreePool(entry->id); FreePool(entry->title_show); FreePool(entry->title); + FreePool(entry->version); FreePool(entry->machine_id); FreePool(entry->loader); FreePool(entry->options); + FreePool(entry); } static BOOLEAN is_digit(CHAR16 c) { @@ -1151,7 +1157,6 @@ static VOID config_entry_add_from_file( if (entry->type == LOADER_UNDEFINED) { config_entry_free(entry); - FreePool(entry); return; } From 2214cfbf50887553bac07e9772bfc87252b74b82 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jun 2018 23:32:21 +0200 Subject: [PATCH 09/16] sd-boot: simplify memory management in processing of unified kernel image a bit --- src/boot/efi/boot.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index cb041363cd..582d34f983 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -1644,15 +1644,16 @@ static VOID config_entry_add_linux( } if (os_name && os_id && (os_version || os_build)) { - CHAR16 *conf; - CHAR16 *path; + _cleanup_freepool_ CHAR16 *conf = NULL, *path = NULL; conf = PoolPrint(L"%s-%s", os_id, os_version ? : os_build); path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName); + entry = config_entry_add_loader(config, loaded_image->DeviceHandle, LOADER_LINUX, conf, 'l', os_name, path); FreePool(content); content = NULL; + /* read the embedded cmdline file */ err = file_read(linux_dir, f->FileName, offs[1], szs[1], &content, NULL); if (!EFI_ERROR(err)) { @@ -1663,9 +1664,6 @@ static VOID config_entry_add_linux( entry->options = stra_to_str(content); } - - FreePool(conf); - FreePool(path); } FreePool(os_name); From 05907f25a5ce49d5fdf1a89d86269146a846db4f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jun 2018 11:25:46 +0200 Subject: [PATCH 10/16] sd-boot: remove left-over lgpl blurb --- src/boot/efi/stub.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 5c97b54691..d11e555748 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -1,9 +1,4 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ -/* This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2.1 of the License, or - * (at your option) any later version. - */ #include #include From 64e7e27cd4683b0d12c018bf20c51d6b1f7ed95b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 21 Jun 2018 18:51:14 +0200 Subject: [PATCH 11/16] sd-boot: coding style fix, don't rely on C's downgrade-to-bool feature for numerical values --- src/boot/efi/boot.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 582d34f983..b1111062e5 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -890,7 +890,7 @@ static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2) { INTN order; order = c_order(*s1) - c_order(*s2); - if (order) + if (order != 0) return order; s1++; s2++; @@ -914,7 +914,7 @@ static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2) { if (is_digit(*s2)) return -1; - if (first) + if (first != 0) return first; } From f538cc65487afa62a1add23d729a90cd3138f9f1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Jun 2018 18:25:01 +0200 Subject: [PATCH 12/16] sd-boot: add boot counting mechanism --- src/boot/efi/boot.c | 293 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 276 insertions(+), 17 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index b1111062e5..909dad69b1 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -24,7 +24,7 @@ static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE; enum loader_type { LOADER_UNDEFINED, LOADER_EFI, - LOADER_LINUX + LOADER_LINUX, }; typedef struct { @@ -41,6 +41,11 @@ typedef struct { EFI_STATUS (*call)(VOID); BOOLEAN no_autoselect; BOOLEAN non_unique; + UINTN tries_done; + UINTN tries_left; + CHAR16 *path; + CHAR16 *current_name; + CHAR16 *next_name; } ConfigEntry; typedef struct { @@ -445,6 +450,17 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) { if (entry->call) Print(L"internal call yes\n"); + if (entry->tries_left != (UINTN) -1) + Print(L"counting boots yes\n" + "tries done %u\n" + "tries left %u\n" + "current path %s\\%s\n" + "next path %s\\%s\n", + entry->tries_done, + entry->tries_left, + entry->path, entry->current_name, + entry->path, entry->next_name); + Print(L"\n--- press key ---\n\n"); console_key_read(&key, TRUE); } @@ -861,6 +877,9 @@ static VOID config_entry_free(ConfigEntry *entry) { FreePool(entry->machine_id); FreePool(entry->loader); FreePool(entry->options); + FreePool(entry->path); + FreePool(entry->current_name); + FreePool(entry->next_name); FreePool(entry); } @@ -1055,9 +1074,195 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) { } } +static VOID config_entry_parse_tries( + ConfigEntry *entry, + CHAR16 *path, + CHAR16 *file, + CHAR16 *suffix) { + + UINTN left = (UINTN) -1, done = (UINTN) -1, factor = 1, i, next_left, next_done; + _cleanup_freepool_ CHAR16 *prefix = NULL; + + /* + * Parses a suffix of two counters (one going down, one going up) in the form "+LEFT-DONE" from the end of the + * filename (but before the .efi/.conf suffix), where the "-DONE" part is optional and may be left out (in + * which case that counter as assumed to be zero, i.e. the missing part is synonymous to "-0"). + * + * Names we grok, and the series they result in: + * + * foobar+3.efi → foobar+2-1.efi → foobar+1-2.efi → foobar+0-3.efi → STOP! + * foobar+4-0.efi → foobar+3-1.efi → foobar+2-2.efi → foobar+1-3.efi → foobar+0-4.efi → STOP! + */ + + i = StrLen(file); + + /* Chop off any suffix such as ".conf" or ".efi" */ + if (suffix) { + UINTN suffix_length; + + suffix_length = StrLen(suffix); + if (i < suffix_length) + return; + + i -= suffix_length; + } + + /* Go backwards through the string and parse everything we encounter */ + for (;;) { + if (i == 0) + return; + + i--; + + switch (file[i]) { + + case '+': + if (left == (UINTN) -1) /* didn't read at least one digit for 'left'? */ + return; + + if (done == (UINTN) -1) /* no 'done' counter? If so, it's equivalent to 0 */ + done = 0; + + goto good; + + case '-': + if (left == (UINTN) -1) /* didn't parse any digit yet? */ + return; + + if (done != (UINTN) -1) /* already encountered a dash earlier? */ + return; + + /* So we encountered a dash. This means this counter is of the form +LEFT-DONE. Let's assign + * what we already parsed to 'done', and start fresh for the 'left' part. */ + + done = left; + left = (UINTN) -1; + factor = 1; + break; + + case '0'...'9': { + UINTN new_factor; + + if (left == (UINTN) -1) + left = file[i] - '0'; + else { + UINTN new_left, digit; + + digit = file[i] - '0'; + if (digit > (UINTN) -1 / factor) /* overflow check */ + return; + + new_left = left + digit * factor; + if (new_left < left) /* overflow check */ + return; + + if (new_left == (UINTN) -1) /* don't allow us to be confused */ + return; + } + + new_factor = factor * 10; + if (new_factor < factor) /* overflow chck */ + return; + + factor = new_factor; + break; + } + + default: + return; + } + } + +good: + entry->tries_left = left; + entry->tries_done = done; + + entry->path = StrDuplicate(path); + entry->current_name = StrDuplicate(file); + + next_left = left <= 0 ? 0 : left - 1; + next_done = done >= (UINTN) -2 ? (UINTN) -2 : done + 1; + + prefix = StrDuplicate(file); + prefix[i] = 0; + + entry->next_name = PoolPrint(L"%s+%u-%u%s", prefix, next_left, next_done, suffix ?: L""); +} + +static VOID config_entry_bump_counters( + ConfigEntry *entry, + EFI_FILE_HANDLE root_dir) { + + _cleanup_freepool_ CHAR16* old_path = NULL, *new_path = NULL; + _cleanup_(FileHandleClosep) EFI_FILE_HANDLE handle = NULL; + static EFI_GUID EfiFileInfoGuid = EFI_FILE_INFO_ID; + _cleanup_freepool_ EFI_FILE_INFO *file_info = NULL; + UINTN file_info_size, a, b; + EFI_STATUS r; + + if (entry->tries_left == (UINTN) -1) + return; + + if (!entry->path || !entry->current_name || !entry->next_name) + return; + + old_path = PoolPrint(L"%s\\%s", entry->path, entry->current_name); + + r = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, old_path, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0ULL); + if (EFI_ERROR(r)) + return; + + a = StrLen(entry->current_name); + b = StrLen(entry->next_name); + + file_info_size = OFFSETOF(EFI_FILE_INFO, FileName) + (a > b ? a : b) + 1; + + for (;;) { + file_info = AllocatePool(file_info_size); + + r = uefi_call_wrapper(handle->GetInfo, 4, handle, &EfiFileInfoGuid, &file_info_size, file_info); + if (!EFI_ERROR(r)) + break; + + if (r != EFI_BUFFER_TOO_SMALL || file_info_size * 2 < file_info_size) { + Print(L"\nFailed to get file info for '%s': %r\n", old_path, r); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return; + } + + file_info_size *= 2; + FreePool(file_info); + } + + /* And rename the file */ + StrCpy(file_info->FileName, entry->next_name); + r = uefi_call_wrapper(handle->SetInfo, 4, handle, &EfiFileInfoGuid, file_info_size, file_info); + if (EFI_ERROR(r)) { + Print(L"\nFailed to rename '%s' to '%s', ignoring: %r\n", old_path, entry->next_name, r); + uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); + return; + } + + /* Flush everything to disk, just in case… */ + (void) uefi_call_wrapper(handle->Flush, 1, handle); + + /* Let's tell the OS that we renamed this file, so that it knows what to rename to the counter-less name on + * success */ + new_path = PoolPrint(L"%s\\%s", entry->path, entry->next_name); + efivar_set(L"LoaderBootCountPath", new_path, FALSE); + + /* If the file we just renamed is the loader path, then let's update that. */ + if (StrCmp(entry->loader, old_path) == 0) { + FreePool(entry->loader); + entry->loader = new_path; + new_path = NULL; + } +} + static VOID config_entry_add_from_file( Config *config, EFI_HANDLE *device, + CHAR16 *path, CHAR16 *file, CHAR8 *content, CHAR16 *loaded_image_path) { @@ -1069,7 +1274,12 @@ static VOID config_entry_add_from_file( UINTN len; _cleanup_freepool_ CHAR16 *initrd = NULL; - entry = AllocateZeroPool(sizeof(ConfigEntry)); + entry = AllocatePool(sizeof(ConfigEntry)); + + *entry = (ConfigEntry) { + .tries_done = (UINTN) -1, + .tries_left = (UINTN) -1, + }; while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) { if (strcmpa((CHAR8 *)"title", key) == 0) { @@ -1183,6 +1393,8 @@ static VOID config_entry_add_from_file( StrLwr(entry->id); config_add_entry(config, entry); + + config_entry_parse_tries(entry, path, file, L".conf"); } static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) { @@ -1245,12 +1457,44 @@ static VOID config_load_entries( err = file_read(entries_dir, f->FileName, 0, 0, &content, NULL); if (!EFI_ERROR(err)) - config_entry_add_from_file(config, device, f->FileName, content, loaded_image_path); + config_entry_add_from_file(config, device, L"\\loader\\entries", f->FileName, content, loaded_image_path); } uefi_call_wrapper(entries_dir->Close, 1, entries_dir); } } +static INTN config_entry_compare(ConfigEntry *a, ConfigEntry *b) { + INTN r; + + /* Order entries that have no tries left to the end of the list */ + if (a->tries_left != 0 && b->tries_left == 0) + return -1; + if (a->tries_left == 0 && b->tries_left != 0) + return 1; + + r = str_verscmp(a->id, b->id); + if (r != 0) + return r; + + if (a->tries_left == (UINTN) -1 || + b->tries_left == (UINTN) -1) + return 0; + + /* If both items have boot counting, and otherwise are identical, put the entry with more tries left first */ + if (a->tries_left > b->tries_left) + return -1; + if (a->tries_left < b->tries_left) + return 1; + + /* If they have the same number of tries left, then let the one win which was tried fewer times so far */ + if (a->tries_done < b->tries_done) + return -1; + if (a->tries_done > b->tries_done) + return 1; + + return 0; +} + static VOID config_sort_entries(Config *config) { UINTN i; @@ -1262,8 +1506,9 @@ static VOID config_sort_entries(Config *config) { for (k = 0; k < config->entry_count - i; k++) { ConfigEntry *entry; - if (str_verscmp(config->entries[k]->id, config->entries[k+1]->id) <= 0) + if (config_entry_compare(config->entries[k], config->entries[k+1]) <= 0) continue; + entry = config->entries[k]; config->entries[k] = config->entries[k+1]; config->entries[k+1] = entry; @@ -1444,10 +1689,15 @@ static BOOLEAN config_entry_add_call( ConfigEntry *entry; - entry = AllocateZeroPool(sizeof(ConfigEntry)); - entry->title = StrDuplicate(title); - entry->call = call; - entry->no_autoselect = TRUE; + entry = AllocatePool(sizeof(ConfigEntry)); + *entry = (ConfigEntry) { + .title = StrDuplicate(title), + .call = call, + .no_autoselect = TRUE, + .tries_done = (UINTN) -1, + .tries_left = (UINTN) -1, + }; + config_add_entry(config, entry); return TRUE; } @@ -1463,16 +1713,21 @@ static ConfigEntry *config_entry_add_loader( ConfigEntry *entry; - entry = AllocateZeroPool(sizeof(ConfigEntry)); - entry->type = type; - entry->title = StrDuplicate(title); - entry->device = device; - entry->loader = StrDuplicate(loader); - entry->id = StrDuplicate(id); - StrLwr(entry->id); - entry->key = key; - config_add_entry(config, entry); + entry = AllocatePool(sizeof(ConfigEntry)); + *entry = (ConfigEntry) { + .type = type, + .title = StrDuplicate(title), + .device = device, + .loader = StrDuplicate(loader), + .id = StrDuplicate(id), + .key = key, + .tries_done = (UINTN) -1, + .tries_left = (UINTN) -1, + }; + StrLwr(entry->id); + + config_add_entry(config, entry); return entry; } @@ -1664,6 +1919,8 @@ static VOID config_entry_add_linux( entry->options = stra_to_str(content); } + + config_entry_parse_tries(entry, L"\\EFI\\Linux", f->FileName, L".efi"); } FreePool(os_name); @@ -1909,6 +2166,8 @@ 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 */ efivar_set(L"LoaderEntrySelected", entry->id, FALSE); From f82ecab0a9c4c1ef5f43bdee98f128c3a0ee611c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jun 2018 18:18:20 +0200 Subject: [PATCH 13/16] efi: make efivar_set_raw() buffer argument VOID* No need to define a type if it is just some arbitrary buffer for us anyway. --- src/boot/efi/boot.c | 2 +- src/boot/efi/util.c | 4 ++-- src/boot/efi/util.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 909dad69b1..02c46c44c2 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2007,7 +2007,7 @@ static EFI_STATUS reboot_into_firmware(VOID) { if (!EFI_ERROR(err)) osind |= (UINT64)*b; - err = efivar_set_raw(&global_guid, L"OsIndications", (CHAR8 *)&osind, sizeof(UINT64), TRUE); + err = efivar_set_raw(&global_guid, L"OsIndications", &osind, sizeof(UINT64), TRUE); if (EFI_ERROR(err)) return err; diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index c75e970b03..e286b14bcc 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -79,7 +79,7 @@ EFI_STATUS parse_boolean(CHAR8 *v, BOOLEAN *b) { return EFI_INVALID_PARAMETER; } -EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent) { +EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, VOID *buf, UINTN size, BOOLEAN persistent) { UINT32 flags; flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; @@ -90,7 +90,7 @@ EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINT } EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent) { - return efivar_set_raw(&loader_guid, name, (CHAR8 *)value, value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, 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) { diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 9921e8bc0a..ae06444014 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -18,7 +18,7 @@ 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, CHAR8 *buf, UINTN size, BOOLEAN persistent); +EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, 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); From 0e2bc7327428b410feb0e7d7a23ca07f0e810222 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jun 2018 18:19:09 +0200 Subject: [PATCH 14/16] sd-boot: write the IDs of all discovered entries to an EFI variable This is primarily useful for debugging, but can be useful for other purposes too. For example userspace could check whether "auto-windows" is included in the list, before triggering a boot-into-windows operation. --- src/boot/efi/boot.c | 25 +++++++++++++++++++++++++ src/boot/efi/util.c | 2 +- src/boot/efi/util.h | 2 ++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 02c46c44c2..7fff880414 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2028,6 +2028,29 @@ static VOID config_free(Config *config) { FreePool(config->entry_oneshot); } +static VOID config_write_entries_to_variable(Config *config) { + _cleanup_freepool_ CHAR16 *buffer = NULL; + UINTN i, sz = 0; + CHAR16 *p; + + for (i = 0; i < config->entry_count; i++) + sz += StrLen(config->entries[i]->id) + 1; + + p = buffer = AllocatePool(sz * sizeof(CHAR16)); + + for (i = 0; i < config->entry_count; i++) { + UINTN l; + + l = StrLen(config->entries[i]->id) + 1; + CopyMem(p, config->entries[i]->id, l * sizeof(CHAR16)); + + p += l; + } + + /* Store the full list of discovered entries. */ + (void) efivar_set_raw(&loader_guid, L"LoaderEntries", buffer, (UINT8*) p - (UINT8*) buffer, FALSE); +} + EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { _cleanup_freepool_ CHAR16 *infostr = NULL, *typestr = NULL; CHAR8 *b; @@ -2119,6 +2142,8 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { goto out; } + config_write_entries_to_variable(&config); + config_title_generate(&config); /* select entry by configured pattern or EFI LoaderDefaultEntry= variable */ diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index e286b14bcc..fd4be681a5 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -10,7 +10,7 @@ * the (ESP)\loader\entries\-.conf convention and the * associated EFI variables. */ -static const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} }; +const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} }; #ifdef __x86_64__ UINT64 ticks_read(VOID) { diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index ae06444014..40ede66d4a 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -50,3 +50,5 @@ static inline void FileHandleClosep(EFI_FILE_HANDLE *handle) { uefi_call_wrapper((*handle)->Close, 1, *handle); } + +const EFI_GUID loader_guid; From 7f1ef125265ace0b3d4b229284555000e945f93c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jun 2018 18:36:40 +0200 Subject: [PATCH 15/16] sd-boot: also set an ID for the reboot-into-firmware entry --- src/boot/efi/boot.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 7fff880414..ce8f7535b6 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -1684,6 +1684,7 @@ static VOID config_title_generate(Config *config) { static BOOLEAN config_entry_add_call( Config *config, + CHAR16 *id, CHAR16 *title, EFI_STATUS (*call)(VOID)) { @@ -1691,6 +1692,7 @@ static BOOLEAN config_entry_add_call( entry = AllocatePool(sizeof(ConfigEntry)); *entry = (ConfigEntry) { + .id = StrDuplicate(id), .title = StrDuplicate(title), .call = call, .no_autoselect = TRUE, @@ -2132,7 +2134,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"Reboot Into Firmware Interface", reboot_into_firmware); + config_entry_add_call(&config, L"auto-reboot-into-firmware-ui", L"Reboot Into Firmware Interface", reboot_into_firmware); FreePool(b); } From 535610b5615f8f7b82b715f1163111696295ada6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jun 2018 18:44:55 +0200 Subject: [PATCH 16/16] sd-boot: factor out searching for loader entry --- src/boot/efi/boot.c | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index ce8f7535b6..4ff7594a9a 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -1519,10 +1519,20 @@ static VOID config_sort_entries(Config *config) { } } +static INTN config_entry_find(Config *config, CHAR16 *id) { + UINTN i; + + for (i = 0; i < config->entry_count; i++) + if (StrCmp(config->entries[i]->id, id) == 0) + return (INTN) i; + + return -1; +} + static VOID config_default_entry_select(Config *config) { _cleanup_freepool_ CHAR16 *entry_oneshot = NULL, *entry_default = NULL; EFI_STATUS err; - UINTN i; + INTN i; /* * The EFI variable to specify a boot entry for the next, and only the @@ -1530,19 +1540,15 @@ static VOID config_default_entry_select(Config *config) { */ err = efivar_get(L"LoaderEntryOneShot", &entry_oneshot); if (!EFI_ERROR(err)) { - BOOLEAN found = FALSE; - - for (i = 0; i < config->entry_count; i++) - if (StrCmp(config->entries[i]->id, entry_oneshot) == 0) { - config->idx_default = i; - found = TRUE; - break; - } config->entry_oneshot = StrDuplicate(entry_oneshot); efivar_set(L"LoaderEntryOneShot", NULL, TRUE); - if (found) + + i = config_entry_find(config, entry_oneshot); + if (i >= 0) { + config->idx_default = i; return; + } } /* @@ -1553,12 +1559,13 @@ static VOID config_default_entry_select(Config *config) { */ err = efivar_get(L"LoaderEntryDefault", &entry_default); if (!EFI_ERROR(err)) { - for (i = 0; i < config->entry_count; i++) - if (StrCmp(config->entries[i]->id, entry_default) == 0) { - config->idx_default = i; - config->idx_default_efivar = i; - return; - } + + i = config_entry_find(config, entry_default); + if (i >= 0) { + config->idx_default = i; + config->idx_default_efivar = i; + return; + } } config->idx_default_efivar = -1;