From 95086da39f262aa61c1a5590ca8efd5984fbf6bd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 25 Feb 2025 11:55:19 +0100 Subject: [PATCH 1/9] boot: split out line editor let's make this beast of boot.c a bit more digestable --- src/boot/boot.c | 249 +----------------------------------------- src/boot/line-edit.c | 254 +++++++++++++++++++++++++++++++++++++++++++ src/boot/line-edit.h | 6 + src/boot/meson.build | 1 + 4 files changed, 262 insertions(+), 248 deletions(-) create mode 100644 src/boot/line-edit.c create mode 100644 src/boot/line-edit.h diff --git a/src/boot/boot.c b/src/boot/boot.c index 6e55a659dd..7431f6d1d1 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -11,6 +11,7 @@ #include "export-vars.h" #include "graphics.h" #include "initrd.h" +#include "line-edit.h" #include "linux.h" #include "measure.h" #include "memory-util-fundamental.h" @@ -150,254 +151,6 @@ enum { IDX_INVALID, }; -static void cursor_left(size_t *cursor, size_t *first) { - assert(cursor); - assert(first); - - if ((*cursor) > 0) - (*cursor)--; - else if ((*first) > 0) - (*first)--; -} - -static void cursor_right(size_t *cursor, size_t *first, size_t x_max, size_t len) { - assert(cursor); - assert(first); - - if ((*cursor)+1 < x_max) - (*cursor)++; - else if ((*first) + (*cursor) < len) - (*first)++; -} - -static bool line_edit(char16_t **line_in, size_t x_max, size_t y_pos) { - _cleanup_free_ char16_t *line = NULL, *print = NULL; - size_t size, len, first = 0, cursor = 0, clear = 0; - - /* Edit the line and return true if it should be executed, false if not. */ - - assert(line_in); - - len = strlen16(*line_in); - size = len + 1024; - line = xnew(char16_t, size); - print = xnew(char16_t, x_max + 1); - strcpy16(line, strempty(*line_in)); - - for (;;) { - EFI_STATUS err; - uint64_t key; - size_t j, cursor_color = EFI_TEXT_ATTR_SWAP(COLOR_EDIT); - - j = MIN(len - first, x_max); - memcpy(print, line + first, j * sizeof(char16_t)); - while (clear > 0 && j < x_max) { - clear--; - print[j++] = ' '; - } - print[j] = '\0'; - - /* See comment at edit_line() call site for why we start at 1. */ - print_at(1, y_pos, COLOR_EDIT, print); - - if (!print[cursor]) - print[cursor] = ' '; - print[cursor+1] = '\0'; - do { - print_at(cursor + 1, y_pos, cursor_color, print + cursor); - cursor_color = EFI_TEXT_ATTR_SWAP(cursor_color); - - err = console_key_read(&key, 750 * 1000); - if (!IN_SET(err, EFI_SUCCESS, EFI_TIMEOUT, EFI_NOT_READY)) - return false; - - print_at(cursor + 1, y_pos, COLOR_EDIT, print + cursor); - } while (err != EFI_SUCCESS); - - switch (key) { - case KEYPRESS(0, SCAN_ESC, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')): - return false; - - case KEYPRESS(0, SCAN_HOME, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')): - /* beginning-of-line */ - cursor = 0; - first = 0; - continue; - - case KEYPRESS(0, SCAN_END, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')): - /* end-of-line */ - cursor = len - first; - if (cursor+1 >= x_max) { - cursor = x_max-1; - first = len - (x_max-1); - } - continue; - - case KEYPRESS(0, SCAN_DOWN, 0): - case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'): - case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0): - /* forward-word */ - while (line[first + cursor] == ' ') - cursor_right(&cursor, &first, x_max, len); - while (line[first + cursor] && line[first + cursor] != ' ') - cursor_right(&cursor, &first, x_max, len); - continue; - - case KEYPRESS(0, SCAN_UP, 0): - case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'): - case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0): - /* backward-word */ - if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { - cursor_left(&cursor, &first); - while ((first + cursor) > 0 && line[first + cursor] == ' ') - cursor_left(&cursor, &first); - } - while ((first + cursor) > 0 && line[first + cursor-1] != ' ') - cursor_left(&cursor, &first); - continue; - - case KEYPRESS(0, SCAN_RIGHT, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')): - /* forward-char */ - if (first + cursor == len) - continue; - cursor_right(&cursor, &first, x_max, len); - continue; - - case KEYPRESS(0, SCAN_LEFT, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')): - /* backward-char */ - cursor_left(&cursor, &first); - continue; - - case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_DELETE, 0): - case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'): - /* kill-word */ - clear = 0; - - size_t k; - for (k = first + cursor; k < len && line[k] == ' '; k++) - clear++; - for (; k < len && line[k] != ' '; k++) - clear++; - - for (size_t i = first + cursor; i + clear < len; i++) - line[i] = line[i + clear]; - len -= clear; - line[len] = '\0'; - continue; - - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')): - case KEYPRESS(EFI_ALT_PRESSED, 0, '\b'): - /* backward-kill-word */ - clear = 0; - if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { - cursor_left(&cursor, &first); - clear++; - while ((first + cursor) > 0 && line[first + cursor] == ' ') { - cursor_left(&cursor, &first); - clear++; - } - } - while ((first + cursor) > 0 && line[first + cursor-1] != ' ') { - cursor_left(&cursor, &first); - clear++; - } - - for (size_t i = first + cursor; i + clear < len; i++) - line[i] = line[i + clear]; - len -= clear; - line[len] = '\0'; - continue; - - case KEYPRESS(0, SCAN_DELETE, 0): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')): - if (len == 0) - continue; - if (first + cursor == len) - continue; - for (size_t i = first + cursor; i < len; i++) - line[i] = line[i+1]; - clear = 1; - len--; - continue; - - case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'): - case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')): - /* kill-line */ - line[first + cursor] = '\0'; - clear = len - (first + cursor); - len = first + cursor; - continue; - - case KEYPRESS(0, 0, '\n'): - case KEYPRESS(0, 0, '\r'): - case KEYPRESS(0, SCAN_F3, 0): /* EZpad Mini 4s firmware sends malformed events */ - case KEYPRESS(0, SCAN_F3, '\r'): /* Teclast X98+ II firmware sends malformed events */ - if (!streq16(line, *line_in)) { - free(*line_in); - *line_in = TAKE_PTR(line); - } - return true; - - case KEYPRESS(0, 0, '\b'): - if (len == 0) - continue; - if (first == 0 && cursor == 0) - continue; - for (size_t i = first + cursor-1; i < len; i++) - line[i] = line[i+1]; - clear = 1; - len--; - if (cursor > 0) - cursor--; - if (cursor > 0 || first == 0) - continue; - /* show full line if it fits */ - if (len < x_max) { - cursor = first; - first = 0; - continue; - } - /* jump left to see what we delete */ - if (first > 10) { - first -= 10; - cursor = 10; - } else { - cursor = first; - first = 0; - } - continue; - - case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'): - case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff): - if (len+1 == size) - continue; - for (size_t i = len; i > first + cursor; i--) - line[i] = line[i-1]; - line[first + cursor] = KEYCHAR(key); - len++; - line[len] = '\0'; - if (cursor+1 < x_max) - cursor++; - else if (first + cursor < len) - first++; - continue; - } - } -} static size_t entry_lookup_key(Config *config, size_t start, char16_t key) { assert(config); diff --git a/src/boot/line-edit.c b/src/boot/line-edit.c new file mode 100644 index 0000000000..ca9dc067a5 --- /dev/null +++ b/src/boot/line-edit.c @@ -0,0 +1,254 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "console.h" +#include "line-edit.h" +#include "util.h" + +static void cursor_left(size_t *cursor, size_t *first) { + assert(cursor); + assert(first); + + if ((*cursor) > 0) + (*cursor)--; + else if ((*first) > 0) + (*first)--; +} + +static void cursor_right(size_t *cursor, size_t *first, size_t x_max, size_t len) { + assert(cursor); + assert(first); + + if ((*cursor)+1 < x_max) + (*cursor)++; + else if ((*first) + (*cursor) < len) + (*first)++; +} + +bool line_edit(char16_t **line_in, size_t x_max, size_t y_pos) { + _cleanup_free_ char16_t *line = NULL, *print = NULL; + size_t size, len, first = 0, cursor = 0, clear = 0; + + /* Edit the line and return true if it should be executed, false if not. */ + + assert(line_in); + + len = strlen16(*line_in); + size = len + 1024; + line = xnew(char16_t, size); + print = xnew(char16_t, x_max + 1); + strcpy16(line, strempty(*line_in)); + + for (;;) { + EFI_STATUS err; + uint64_t key; + size_t j, cursor_color = EFI_TEXT_ATTR_SWAP(COLOR_EDIT); + + j = MIN(len - first, x_max); + memcpy(print, line + first, j * sizeof(char16_t)); + while (clear > 0 && j < x_max) { + clear--; + print[j++] = ' '; + } + print[j] = '\0'; + + /* See comment at edit_line() call site for why we start at 1. */ + print_at(1, y_pos, COLOR_EDIT, print); + + if (!print[cursor]) + print[cursor] = ' '; + print[cursor+1] = '\0'; + do { + print_at(cursor + 1, y_pos, cursor_color, print + cursor); + cursor_color = EFI_TEXT_ATTR_SWAP(cursor_color); + + err = console_key_read(&key, 750 * 1000); + if (!IN_SET(err, EFI_SUCCESS, EFI_TIMEOUT, EFI_NOT_READY)) + return false; + + print_at(cursor + 1, y_pos, COLOR_EDIT, print + cursor); + } while (err != EFI_SUCCESS); + + switch (key) { + case KEYPRESS(0, SCAN_ESC, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')): + return false; + + case KEYPRESS(0, SCAN_HOME, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')): + /* beginning-of-line */ + cursor = 0; + first = 0; + continue; + + case KEYPRESS(0, SCAN_END, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')): + /* end-of-line */ + cursor = len - first; + if (cursor+1 >= x_max) { + cursor = x_max-1; + first = len - (x_max-1); + } + continue; + + case KEYPRESS(0, SCAN_DOWN, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'): + case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0): + /* forward-word */ + while (line[first + cursor] == ' ') + cursor_right(&cursor, &first, x_max, len); + while (line[first + cursor] && line[first + cursor] != ' ') + cursor_right(&cursor, &first, x_max, len); + continue; + + case KEYPRESS(0, SCAN_UP, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'): + case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0): + /* backward-word */ + if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { + cursor_left(&cursor, &first); + while ((first + cursor) > 0 && line[first + cursor] == ' ') + cursor_left(&cursor, &first); + } + while ((first + cursor) > 0 && line[first + cursor-1] != ' ') + cursor_left(&cursor, &first); + continue; + + case KEYPRESS(0, SCAN_RIGHT, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')): + /* forward-char */ + if (first + cursor == len) + continue; + cursor_right(&cursor, &first, x_max, len); + continue; + + case KEYPRESS(0, SCAN_LEFT, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')): + /* backward-char */ + cursor_left(&cursor, &first); + continue; + + case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_DELETE, 0): + case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'): + /* kill-word */ + clear = 0; + + size_t k; + for (k = first + cursor; k < len && line[k] == ' '; k++) + clear++; + for (; k < len && line[k] != ' '; k++) + clear++; + + for (size_t i = first + cursor; i + clear < len; i++) + line[i] = line[i + clear]; + len -= clear; + line[len] = '\0'; + continue; + + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')): + case KEYPRESS(EFI_ALT_PRESSED, 0, '\b'): + /* backward-kill-word */ + clear = 0; + if ((first + cursor) > 0 && line[first + cursor-1] == ' ') { + cursor_left(&cursor, &first); + clear++; + while ((first + cursor) > 0 && line[first + cursor] == ' ') { + cursor_left(&cursor, &first); + clear++; + } + } + while ((first + cursor) > 0 && line[first + cursor-1] != ' ') { + cursor_left(&cursor, &first); + clear++; + } + + for (size_t i = first + cursor; i + clear < len; i++) + line[i] = line[i + clear]; + len -= clear; + line[len] = '\0'; + continue; + + case KEYPRESS(0, SCAN_DELETE, 0): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')): + if (len == 0) + continue; + if (first + cursor == len) + continue; + for (size_t i = first + cursor; i < len; i++) + line[i] = line[i+1]; + clear = 1; + len--; + continue; + + case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'): + case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')): + /* kill-line */ + line[first + cursor] = '\0'; + clear = len - (first + cursor); + len = first + cursor; + continue; + + case KEYPRESS(0, 0, '\n'): + case KEYPRESS(0, 0, '\r'): + case KEYPRESS(0, SCAN_F3, 0): /* EZpad Mini 4s firmware sends malformed events */ + case KEYPRESS(0, SCAN_F3, '\r'): /* Teclast X98+ II firmware sends malformed events */ + if (!streq16(line, *line_in)) { + free(*line_in); + *line_in = TAKE_PTR(line); + } + return true; + + case KEYPRESS(0, 0, '\b'): + if (len == 0) + continue; + if (first == 0 && cursor == 0) + continue; + for (size_t i = first + cursor-1; i < len; i++) + line[i] = line[i+1]; + clear = 1; + len--; + if (cursor > 0) + cursor--; + if (cursor > 0 || first == 0) + continue; + /* show full line if it fits */ + if (len < x_max) { + cursor = first; + first = 0; + continue; + } + /* jump left to see what we delete */ + if (first > 10) { + first -= 10; + cursor = 10; + } else { + cursor = first; + first = 0; + } + continue; + + case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'): + case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff): + if (len+1 == size) + continue; + for (size_t i = len; i > first + cursor; i--) + line[i] = line[i-1]; + line[first + cursor] = KEYCHAR(key); + len++; + line[len] = '\0'; + if (cursor+1 < x_max) + cursor++; + else if (first + cursor < len) + first++; + continue; + } + } +} diff --git a/src/boot/line-edit.h b/src/boot/line-edit.h new file mode 100644 index 0000000000..523108d42e --- /dev/null +++ b/src/boot/line-edit.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +bool line_edit(char16_t **line_in, size_t x_max, size_t y_pos); diff --git a/src/boot/meson.build b/src/boot/meson.build index 69c8e40ad3..f88371b6b1 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -307,6 +307,7 @@ libefi_sources = files( systemd_boot_sources = files( 'boot.c', + 'line-edit.c', ) stub_sources = files( From 4801e164014299fab4d23aeb29aa35a9a4b261d1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 25 Feb 2025 12:12:56 +0100 Subject: [PATCH 2/9] boot: make .call() method of BootEntry more complete Let's pass some context information to the .call() methods so that it's sufficient to implement the handlers for all entry types with them. (This commit doesn't port the various entry types over though, that happens in later commits.) --- src/boot/boot.c | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 7431f6d1d1..1308c83648 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -70,7 +70,7 @@ typedef enum LoaderType { /* Which loader types shall be considered for automatic selection */ #define LOADER_TYPE_MAY_AUTO_SELECT(t) IN_SET(t, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_UKI_URL, LOADER_TYPE2_UKI) -typedef struct { +typedef struct BootEntry { char16_t *id; /* The unique identifier for this entry (typically the filename of the file defining the entry, possibly suffixed with a profile id) */ char16_t *id_without_profile; /* same, but without any profile id suffixed */ char16_t *title_show; /* The string to actually display (this is made unique before showing) */ @@ -87,7 +87,7 @@ typedef struct { bool options_implied; /* If true, these options are implied if we invoke the PE binary without any parameters (as in: UKI). If false we must specify these options explicitly. */ char16_t **initrd; char16_t key; - EFI_STATUS (*call)(void); + EFI_STATUS (*call)(const struct BootEntry *entry, EFI_FILE *root_dir, EFI_HANDLE parent_image); int tries_done; int tries_left; char16_t *path; @@ -419,24 +419,24 @@ static EFI_STATUS set_reboot_into_firmware(void) { return EFI_SUCCESS; } -_noreturn_ static EFI_STATUS poweroff_system(void) { +_noreturn_ static EFI_STATUS call_poweroff_system(const BootEntry *entry, EFI_FILE *root_dir, EFI_HANDLE parent_image) { RT->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL); assert_not_reached(); } -_noreturn_ static EFI_STATUS reboot_system(void) { +_noreturn_ static EFI_STATUS call_reboot_system(const BootEntry *entry, EFI_FILE *root_dir, EFI_HANDLE parent_image) { RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL); assert_not_reached(); } -static EFI_STATUS reboot_into_firmware(void) { +static EFI_STATUS call_reboot_into_firmware(const BootEntry *entry, EFI_FILE *root_dir, EFI_HANDLE parent_image) { EFI_STATUS err; err = set_reboot_into_firmware(); if (err != EFI_SUCCESS) return err; - return reboot_system(); + return call_reboot_system(entry, root_dir, parent_image); } static bool menu_run( @@ -929,10 +929,10 @@ static bool menu_run( case ACTION_CONTINUE: assert_not_reached(); case ACTION_POWEROFF: - poweroff_system(); + (void) call_poweroff_system(/* entry= */ NULL, /* root_dir= */ NULL, /* parent_image= */ NULL); case ACTION_REBOOT: case ACTION_FIRMWARE_SETUP: - reboot_system(); + (void) call_reboot_system(/* entry= */ NULL, /* root_dir= */ NULL, /* parent_image= */ NULL); case ACTION_RUN: case ACTION_QUIT: break; @@ -1894,11 +1894,13 @@ static void config_add_entry_osx(Config *config) { } #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) -static EFI_STATUS boot_windows_bitlocker(void) { +static EFI_STATUS call_boot_windows_bitlocker(const BootEntry *entry, EFI_FILE *root_dir, EFI_HANDLE parent_image) { _cleanup_free_ EFI_HANDLE *handles = NULL; size_t n_handles; EFI_STATUS err; + assert(entry); + // FIXME: Experimental for now. Should be generalized, and become a per-entry option that can be // enabled independently of BitLocker, and without a BootXXXX entry pre-existing. @@ -2000,7 +2002,7 @@ static void config_add_entry_windows(Config *config, EFI_HANDLE *device, EFI_FIL u"\\EFI\\Microsoft\\Boot\\bootmgfw.efi"); if (config->reboot_for_bitlocker) - e->call = boot_windows_bitlocker; + e->call = call_boot_windows_bitlocker; #endif } @@ -2458,8 +2460,9 @@ static EFI_STATUS expand_path( } static EFI_STATUS image_start( - EFI_HANDLE parent_image, - const BootEntry *entry) { + const BootEntry *entry, + EFI_FILE *root, + EFI_HANDLE parent_image) { _cleanup_(devicetree_cleanup) struct devicetree_state dtstate = {}; _cleanup_(unload_imagep) EFI_HANDLE image = NULL; @@ -2469,7 +2472,7 @@ static EFI_STATUS image_start( /* If this loader entry has a special way to boot, try that first. */ if (entry->call) - (void) entry->call(); + (void) entry->call(entry, root, parent_image); _cleanup_file_close_ EFI_FILE *image_root = NULL; _cleanup_free_ EFI_DEVICE_PATH *path = NULL; @@ -2805,7 +2808,7 @@ static void config_load_all_entries( *entry = (BootEntry) { .id = xstrdup16(u"auto-reboot-to-firmware-setup"), .title = xstrdup16(u"Reboot Into Firmware Interface"), - .call = reboot_into_firmware, + .call = call_reboot_into_firmware, .tries_done = -1, .tries_left = -1, }; @@ -2817,7 +2820,7 @@ static void config_load_all_entries( *entry = (BootEntry) { .id = xstrdup16(u"auto-poweroff"), .title = xstrdup16(u"Power Off The System"), - .call = poweroff_system, + .call = call_poweroff_system, .tries_done = -1, .tries_left = -1, }; @@ -2829,7 +2832,7 @@ static void config_load_all_entries( *entry = (BootEntry) { .id = xstrdup16(u"auto-reboot"), .title = xstrdup16(u"Reboot The System"), - .call = reboot_system, + .call = call_reboot_system, .tries_done = -1, .tries_left = -1, }; @@ -2949,7 +2952,7 @@ static EFI_STATUS run(EFI_HANDLE image) { /* Run special entry like "reboot" now. Those that have a loader * will be handled by image_start() instead. */ if (entry->call && !entry->loader) { - entry->call(); + entry->call(entry, root_dir, image); continue; } @@ -2959,7 +2962,7 @@ static EFI_STATUS run(EFI_HANDLE image) { /* Optionally, read a random seed off the ESP and pass it to the OS */ (void) process_random_seed(root_dir); - err = image_start(image, entry); + err = image_start(entry, root_dir, image); if (err != EFI_SUCCESS) return err; From 729f3c21c51cd4e5b91b52d26d8ddb34c5732729 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 25 Feb 2025 12:16:10 +0100 Subject: [PATCH 3/9] boot: only do boot counting management for some menu entries Let's do boot counting only for some menu entry types, and carefully list which types those are. --- src/boot/boot.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/boot/boot.c b/src/boot/boot.c index 1308c83648..410021ebee 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -70,6 +70,9 @@ typedef enum LoaderType { /* Which loader types shall be considered for automatic selection */ #define LOADER_TYPE_MAY_AUTO_SELECT(t) IN_SET(t, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_UKI_URL, LOADER_TYPE2_UKI) +/* Whether to do boot attempt counting logic (only works if userspace can actually find the selected option later) */ +#define LOADER_TYPE_BUMP_COUNTERS(t) IN_SET(t, LOADER_LINUX, LOADER_UKI, LOADER_TYPE2_UKI) + typedef struct BootEntry { char16_t *id; /* The unique identifier for this entry (typically the filename of the file defining the entry, possibly suffixed with a profile id) */ char16_t *id_without_profile; /* same, but without any profile id suffixed */ @@ -1152,6 +1155,9 @@ static EFI_STATUS boot_entry_bump_counters(BootEntry *entry) { assert(entry); + if (!LOADER_TYPE_BUMP_COUNTERS(entry->type)) + return EFI_SUCCESS; + if (entry->tries_left < 0) return EFI_SUCCESS; From ae5e7f17d0013d68c940c69d81e41f00af5488e1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 25 Feb 2025 12:19:48 +0100 Subject: [PATCH 4/9] boot: only do random seed management for some entry types Similar to the previous commits, let's carefully condition random seed management (which might be slow, and simply pointless for various entry types) on the entry type. --- src/boot/boot.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 410021ebee..62be8ec0a3 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -73,6 +73,9 @@ typedef enum LoaderType { /* Whether to do boot attempt counting logic (only works if userspace can actually find the selected option later) */ #define LOADER_TYPE_BUMP_COUNTERS(t) IN_SET(t, LOADER_LINUX, LOADER_UKI, LOADER_TYPE2_UKI) +/* Whether to do random seed management (only we invoke Linux) */ +#define LOADER_TYPE_PROCESS_RANDOM_SEED(t) IN_SET(t, LOADER_LINUX, LOADER_UKI, LOADER_TYPE2_UKI) + typedef struct BootEntry { char16_t *id; /* The unique identifier for this entry (typically the filename of the file defining the entry, possibly suffixed with a profile id) */ char16_t *id_without_profile; /* same, but without any profile id suffixed */ @@ -2966,7 +2969,8 @@ static EFI_STATUS run(EFI_HANDLE image) { 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); + if (LOADER_TYPE_PROCESS_RANDOM_SEED(entry->type)) + (void) process_random_seed(root_dir); err = image_start(entry, root_dir, image); if (err != EFI_SUCCESS) From d870ae47b767183c1312ad7e3196696cf38e3b9e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 25 Feb 2025 12:34:49 +0100 Subject: [PATCH 5/9] boot: only save entry for some entry types Much like the previous one, let's also condition the entry selection saving on entry types. --- src/boot/boot.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 62be8ec0a3..1493b31c8d 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -76,6 +76,9 @@ typedef enum LoaderType { /* Whether to do random seed management (only we invoke Linux) */ #define LOADER_TYPE_PROCESS_RANDOM_SEED(t) IN_SET(t, LOADER_LINUX, LOADER_UKI, LOADER_TYPE2_UKI) +/* Whether to persistently save the selected entry in an EFI variable, if that's requested. */ +#define LOADER_TYPE_SAVE_ENTRY(t) IN_SET(t, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_UKI_URL, LOADER_TYPE2_UKI) + typedef struct BootEntry { char16_t *id; /* The unique identifier for this entry (typically the filename of the file defining the entry, possibly suffixed with a profile id) */ char16_t *id_without_profile; /* same, but without any profile id suffixed */ @@ -2675,7 +2678,9 @@ static void config_write_entries_to_variable(Config *config) { static void save_selected_entry(const Config *config, const BootEntry *entry) { assert(config); assert(entry); - assert(entry->loader || !entry->call); + + if (!LOADER_TYPE_SAVE_ENTRY(entry->type)) + return; /* Always export the selected boot entry to the system in a volatile var. */ (void) efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderEntrySelected", entry->id, 0); From fb6cf4bbb75baee8a6988d899de2c6b3e3805e31 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 25 Feb 2025 12:35:25 +0100 Subject: [PATCH 6/9] boot: make regular entries less special Let's try to always invoke entries via the call() method of BootEntry objects, to stick to a single uniform way to do so. --- src/boot/boot.c | 64 ++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 1493b31c8d..ec32f14e97 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -1211,6 +1211,8 @@ static EFI_STATUS boot_entry_bump_counters(BootEntry *entry) { return EFI_SUCCESS; } +static EFI_STATUS call_image_start(const BootEntry *entry, EFI_FILE *root_dir, EFI_HANDLE parent_image); + static void boot_entry_add_type1( Config *config, EFI_HANDLE *device, @@ -1236,6 +1238,7 @@ static void boot_entry_add_type1( *entry = (BootEntry) { .tries_done = -1, .tries_left = -1, + .call = call_image_start, }; while ((line = line_get_key_value(content, " \t", &pos, &key, &value))) @@ -1865,6 +1868,7 @@ static BootEntry* config_add_entry_loader_auto( .key = key, .tries_done = -1, .tries_left = -1, + .call = call_image_start, }; config_add_entry(config, entry); @@ -1896,7 +1900,7 @@ static void config_add_entry_osx(Config *config) { config, handles[i], root, - NULL, + /* loaded_image_path= */ NULL, u"auto-osx", 'a', u"macOS", @@ -1990,7 +1994,7 @@ static EFI_STATUS call_boot_windows_bitlocker(const BootEntry *entry, EFI_FILE * } #endif -static void config_add_entry_windows(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir) { +static void config_add_entry_windows(Config *config, EFI_HANDLE *device, EFI_FILE *root) { #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) _cleanup_free_ char *bcd = NULL; char16_t *title = NULL; @@ -1999,19 +2003,25 @@ static void config_add_entry_windows(Config *config, EFI_HANDLE *device, EFI_FIL assert(config); assert(device); - assert(root_dir); + assert(root); if (!config->auto_entries) return; /* Try to find a better title. */ - err = file_read(root_dir, u"\\EFI\\Microsoft\\Boot\\BCD", 0, 100*1024, &bcd, &len); + err = file_read(root, u"\\EFI\\Microsoft\\Boot\\BCD", 0, 100*1024, &bcd, &len); if (err == EFI_SUCCESS) title = get_bcd_title((uint8_t *) bcd, len); - BootEntry *e = config_add_entry_loader_auto(config, device, root_dir, NULL, - u"auto-windows", 'w', title ?: u"Windows Boot Manager", - u"\\EFI\\Microsoft\\Boot\\bootmgfw.efi"); + BootEntry *e = config_add_entry_loader_auto( + config, + device, + root, + NULL, + u"auto-windows", + 'w', + title ?: u"Windows Boot Manager", + u"\\EFI\\Microsoft\\Boot\\bootmgfw.efi"); if (config->reboot_for_bitlocker) e->call = call_boot_windows_bitlocker; @@ -2216,6 +2226,7 @@ static void boot_entry_add_type2( .tries_done = -1, .tries_left = -1, .profile = profile, + .call = call_image_start, }; config_add_entry(config, entry); @@ -2471,9 +2482,9 @@ static EFI_STATUS expand_path( return EFI_NOT_FOUND; } -static EFI_STATUS image_start( +static EFI_STATUS call_image_start( const BootEntry *entry, - EFI_FILE *root, + EFI_FILE *root_dir, EFI_HANDLE parent_image) { _cleanup_(devicetree_cleanup) struct devicetree_state dtstate = {}; @@ -2482,10 +2493,6 @@ static EFI_STATUS image_start( assert(entry); - /* If this loader entry has a special way to boot, try that first. */ - if (entry->call) - (void) entry->call(entry, root, parent_image); - _cleanup_file_close_ EFI_FILE *image_root = NULL; _cleanup_free_ EFI_DEVICE_PATH *path = NULL; bool boot_policy; @@ -2812,10 +2819,24 @@ static void config_load_all_entries( /* If we find some well-known loaders, add them to the end of the list */ config_add_entry_osx(config); config_add_entry_windows(config, loaded_image->DeviceHandle, root_dir); - config_add_entry_loader_auto(config, loaded_image->DeviceHandle, root_dir, NULL, - u"auto-efi-shell", 's', u"EFI Shell", u"\\shell" EFI_MACHINE_TYPE_NAME ".efi"); - config_add_entry_loader_auto(config, loaded_image->DeviceHandle, root_dir, loaded_image_path, - u"auto-efi-default", '\0', u"EFI Default Loader", NULL); + config_add_entry_loader_auto( + config, + loaded_image->DeviceHandle, + root_dir, + /* loaded_image_path= */ NULL, + u"auto-efi-shell", + 's', + u"EFI Shell", + u"\\shell" EFI_MACHINE_TYPE_NAME ".efi"); + config_add_entry_loader_auto( + config, + loaded_image->DeviceHandle, + root_dir, + loaded_image_path, + u"auto-efi-default", + '\0', + u"EFI Default Loader", + /* loader= */ NULL); if (config->auto_firmware && FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) { BootEntry *entry = xnew(BootEntry, 1); @@ -2963,13 +2984,6 @@ static EFI_STATUS run(EFI_HANDLE image) { continue; } - /* Run special entry like "reboot" now. Those that have a loader - * will be handled by image_start() instead. */ - if (entry->call && !entry->loader) { - entry->call(entry, root_dir, image); - continue; - } - (void) boot_entry_bump_counters(entry); save_selected_entry(&config, entry); @@ -2977,7 +2991,7 @@ static EFI_STATUS run(EFI_HANDLE image) { if (LOADER_TYPE_PROCESS_RANDOM_SEED(entry->type)) (void) process_random_seed(root_dir); - err = image_start(entry, root_dir, image); + err = ASSERT_PTR(entry->call)(entry, root_dir, image); if (err != EFI_SUCCESS) return err; From fb09163a01374069ac317e2101e9fc5910a84df0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 25 Feb 2025 12:36:17 +0100 Subject: [PATCH 7/9] boot: move check if secure boot enrollment applies to a single place Let's check if sb enrollment applies at a single place: inside the discovery function, and let's not bother with additional checks later. --- src/boot/boot.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index ec32f14e97..090f7237cd 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -2711,6 +2711,9 @@ static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) EFI_STATUS err; _cleanup_file_close_ EFI_FILE *keys_basedir = NULL; + if (config->secure_boot_enroll == ENROLL_OFF) + return EFI_SUCCESS; + if (!IN_SET(secure_boot_mode(), SECURE_BOOT_SETUP, SECURE_BOOT_AUDIT)) return EFI_SUCCESS; @@ -2874,12 +2877,10 @@ static void config_load_all_entries( config_add_entry(config, entry); } - /* Find secure boot signing keys and autoload them if configured. - * Otherwise, create menu entries so that the user can load them manually. - * If the secure-boot-enroll variable is set to no (the default), we do not - * even search for keys on the ESP */ - if (config->secure_boot_enroll != ENROLL_OFF) - secure_boot_discover_keys(config, root_dir); + /* Find secure boot signing keys and autoload them if configured. Otherwise, create menu entries so + * that the user can load them manually. If the secure-boot-enroll variable is set to no (the + * default), we do not even search for keys on the ESP */ + (void) secure_boot_discover_keys(config, root_dir); if (config->n_entries == 0) return; @@ -2977,7 +2978,7 @@ static EFI_STATUS run(EFI_HANDLE image) { } /* if auto enrollment is activated, we try to load keys for the given entry. */ - if (entry->type == LOADER_SECURE_BOOT_KEYS && config.secure_boot_enroll != ENROLL_OFF) { + if (entry->type == LOADER_SECURE_BOOT_KEYS) { err = secure_boot_enroll_at(root_dir, entry->path, /*force=*/ true); if (err != EFI_SUCCESS) return err; From 612bc71210244cd632344f86c8fb685867b92c30 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 25 Feb 2025 12:40:43 +0100 Subject: [PATCH 8/9] boot: make secure boot enrollment less special This is the last entry type that has special handling: with this in place we now always invoke entry handlers the very same way. via the .call() method of the BootEntry structure. --- src/boot/boot.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index 090f7237cd..b24e6f7a26 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -2707,6 +2707,12 @@ static void save_selected_entry(const Config *config, const BootEntry *entry) { (void) efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderEntryLastBooted", EFI_VARIABLE_NON_VOLATILE); } +static EFI_STATUS call_secure_boot_enroll(const BootEntry *entry, EFI_FILE *root_dir, EFI_HANDLE parent_image) { + assert(entry); + + return secure_boot_enroll_at(root_dir, entry->path, /* force= */ true); +} + static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) { EFI_STATUS err; _cleanup_file_close_ EFI_FILE *keys_basedir = NULL; @@ -2747,6 +2753,7 @@ static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) .type = LOADER_SECURE_BOOT_KEYS, .tries_done = -1, .tries_left = -1, + .call = call_secure_boot_enroll, }; config_add_entry(config, entry); @@ -2977,14 +2984,6 @@ static EFI_STATUS run(EFI_HANDLE image) { return EFI_SUCCESS; } - /* if auto enrollment is activated, we try to load keys for the given entry. */ - if (entry->type == LOADER_SECURE_BOOT_KEYS) { - err = secure_boot_enroll_at(root_dir, entry->path, /*force=*/ true); - if (err != EFI_SUCCESS) - return err; - continue; - } - (void) boot_entry_bump_counters(entry); save_selected_entry(&config, entry); From 395ac1c44821c1903c55f1c11cb50716196505d0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 25 Feb 2025 12:43:11 +0100 Subject: [PATCH 9/9] boot: split out call that adds reboot/poweroff/firmware entries --- src/boot/boot.c | 77 +++++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/src/boot/boot.c b/src/boot/boot.c index b24e6f7a26..54561e4af4 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -2799,6 +2799,47 @@ static void export_loader_variables( (void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", loader_features, 0); } +static void config_add_system_entries(Config *config) { + assert(config); + + if (config->auto_firmware && FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) { + BootEntry *entry = xnew(BootEntry, 1); + *entry = (BootEntry) { + .id = xstrdup16(u"auto-reboot-to-firmware-setup"), + .title = xstrdup16(u"Reboot Into Firmware Interface"), + .call = call_reboot_into_firmware, + .tries_done = -1, + .tries_left = -1, + }; + config_add_entry(config, entry); + } + + if (config->auto_poweroff) { + BootEntry *entry = xnew(BootEntry, 1); + *entry = (BootEntry) { + .id = xstrdup16(u"auto-poweroff"), + .title = xstrdup16(u"Power Off The System"), + .call = call_poweroff_system, + .tries_done = -1, + .tries_left = -1, + }; + config_add_entry(config, entry); + } + + if (config->auto_reboot) { + BootEntry *entry = xnew(BootEntry, 1); + *entry = (BootEntry) { + .id = xstrdup16(u"auto-reboot"), + .title = xstrdup16(u"Reboot The System"), + .call = call_reboot_system, + .tries_done = -1, + .tries_left = -1, + }; + config_add_entry(config, entry); + } + +} + static void config_load_all_entries( Config *config, EFI_LOADED_IMAGE_PROTOCOL *loaded_image, @@ -2848,41 +2889,7 @@ static void config_load_all_entries( u"EFI Default Loader", /* loader= */ NULL); - if (config->auto_firmware && FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) { - BootEntry *entry = xnew(BootEntry, 1); - *entry = (BootEntry) { - .id = xstrdup16(u"auto-reboot-to-firmware-setup"), - .title = xstrdup16(u"Reboot Into Firmware Interface"), - .call = call_reboot_into_firmware, - .tries_done = -1, - .tries_left = -1, - }; - config_add_entry(config, entry); - } - - if (config->auto_poweroff) { - BootEntry *entry = xnew(BootEntry, 1); - *entry = (BootEntry) { - .id = xstrdup16(u"auto-poweroff"), - .title = xstrdup16(u"Power Off The System"), - .call = call_poweroff_system, - .tries_done = -1, - .tries_left = -1, - }; - config_add_entry(config, entry); - } - - if (config->auto_reboot) { - BootEntry *entry = xnew(BootEntry, 1); - *entry = (BootEntry) { - .id = xstrdup16(u"auto-reboot"), - .title = xstrdup16(u"Reboot The System"), - .call = call_reboot_system, - .tries_done = -1, - .tries_left = -1, - }; - config_add_entry(config, entry); - } + config_add_system_entries(config); /* Find secure boot signing keys and autoload them if configured. Otherwise, create menu entries so * that the user can load them manually. If the secure-boot-enroll variable is set to no (the