]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/boot.c
sd-boot+bootctl: invert order of entries w/o sort-key
[thirdparty/systemd.git] / src / boot / efi / boot.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <efi.h>
4 #include <efigpt.h>
5 #include <efilib.h>
6
7 #include "bcd.h"
8 #include "bootspec-fundamental.h"
9 #include "console.h"
10 #include "devicetree.h"
11 #include "disk.h"
12 #include "drivers.h"
13 #include "efivars-fundamental.h"
14 #include "graphics.h"
15 #include "linux.h"
16 #include "measure.h"
17 #include "pe.h"
18 #include "random-seed.h"
19 #include "secure-boot.h"
20 #include "shim.h"
21 #include "ticks.h"
22 #include "util.h"
23 #include "xbootldr.h"
24
25 #ifndef GNU_EFI_USE_MS_ABI
26 /* We do not use uefi_call_wrapper() in systemd-boot. As such, we rely on the
27 * compiler to do the calling convention conversion for us. This is check is
28 * to make sure the -DGNU_EFI_USE_MS_ABI was passed to the comiler. */
29 #error systemd-boot requires compilation with GNU_EFI_USE_MS_ABI defined.
30 #endif
31
32 #define TEXT_ATTR_SWAP(c) EFI_TEXT_ATTR(((c) & 0b11110000) >> 4, (c) & 0b1111)
33
34 /* Magic string for recognizing our own binaries */
35 _used_ _section_(".sdmagic") static const char magic[] =
36 "#### LoaderInfo: systemd-boot " GIT_VERSION " ####";
37
38 /* Makes systemd-boot available from \EFI\Linux\ for testing purposes. */
39 _used_ _section_(".osrel") static const char osrel[] =
40 "ID=systemd-boot\n"
41 "VERSION=\"" GIT_VERSION "\"\n"
42 "NAME=\"systemd-boot " GIT_VERSION "\"\n";
43
44 enum loader_type {
45 LOADER_UNDEFINED,
46 LOADER_AUTO,
47 LOADER_EFI,
48 LOADER_LINUX, /* Boot loader spec type #1 entries */
49 LOADER_UNIFIED_LINUX, /* Boot loader spec type #2 entries */
50 };
51
52 typedef struct {
53 CHAR16 *id; /* The unique identifier for this entry (typically the filename of the file defining the entry) */
54 CHAR16 *title_show; /* The string to actually display (this is made unique before showing) */
55 CHAR16 *title; /* The raw (human readable) title string of the entry (not necessarily unique) */
56 CHAR16 *sort_key; /* The string to use as primary sory key, usually ID= from os-release, possibly suffixed */
57 CHAR16 *version; /* The raw (human readable) version string of the entry */
58 CHAR16 *machine_id;
59 EFI_HANDLE *device;
60 enum loader_type type;
61 CHAR16 *loader;
62 CHAR16 *devicetree;
63 CHAR16 *options;
64 CHAR16 key;
65 EFI_STATUS (*call)(void);
66 UINTN tries_done;
67 UINTN tries_left;
68 CHAR16 *path;
69 CHAR16 *current_name;
70 CHAR16 *next_name;
71 } ConfigEntry;
72
73 typedef struct {
74 ConfigEntry **entries;
75 UINTN entry_count;
76 UINTN idx_default;
77 UINTN idx_default_efivar;
78 UINT32 timeout_sec; /* Actual timeout used (efi_main() override > efivar > config). */
79 UINT32 timeout_sec_config;
80 UINT32 timeout_sec_efivar;
81 CHAR16 *entry_default_config;
82 CHAR16 *entry_default_efivar;
83 CHAR16 *entry_oneshot;
84 CHAR16 *entry_saved;
85 CHAR16 *options_edit;
86 BOOLEAN editor;
87 BOOLEAN auto_entries;
88 BOOLEAN auto_firmware;
89 BOOLEAN reboot_for_bitlocker;
90 BOOLEAN force_menu;
91 BOOLEAN use_saved_entry;
92 BOOLEAN use_saved_entry_efivar;
93 BOOLEAN beep;
94 INT64 console_mode;
95 INT64 console_mode_efivar;
96 RandomSeedMode random_seed_mode;
97 } Config;
98
99 /* These values have been chosen so that the transitions the user sees could
100 * employ unsigned over-/underflow like this:
101 * efivar unset ↔ force menu ↔ no timeout/skip menu ↔ 1 s ↔ 2 s ↔ … */
102 enum {
103 TIMEOUT_MIN = 1,
104 TIMEOUT_MAX = UINT32_MAX - 2U,
105 TIMEOUT_UNSET = UINT32_MAX - 1U,
106 TIMEOUT_MENU_FORCE = UINT32_MAX,
107 TIMEOUT_MENU_HIDDEN = 0,
108 TIMEOUT_TYPE_MAX = UINT32_MAX,
109 };
110
111 enum {
112 IDX_MAX = INT16_MAX,
113 IDX_INVALID,
114 };
115
116 static void cursor_left(UINTN *cursor, UINTN *first) {
117 assert(cursor);
118 assert(first);
119
120 if ((*cursor) > 0)
121 (*cursor)--;
122 else if ((*first) > 0)
123 (*first)--;
124 }
125
126 static void cursor_right(
127 UINTN *cursor,
128 UINTN *first,
129 UINTN x_max,
130 UINTN len) {
131
132 assert(cursor);
133 assert(first);
134
135 if ((*cursor)+1 < x_max)
136 (*cursor)++;
137 else if ((*first) + (*cursor) < len)
138 (*first)++;
139 }
140
141 static BOOLEAN line_edit(
142 const CHAR16 *line_in,
143 CHAR16 **line_out,
144 UINTN x_max,
145 UINTN y_pos) {
146
147 _cleanup_freepool_ CHAR16 *line = NULL, *print = NULL;
148 UINTN size, len, first = 0, cursor = 0, clear = 0;
149
150 assert(line_out);
151
152 if (!line_in)
153 line_in = L"";
154
155 len = StrLen(line_in);
156 size = len + 1024;
157 line = xnew(CHAR16, size);
158 print = xnew(CHAR16, x_max + 1);
159 StrCpy(line, line_in);
160
161 for (;;) {
162 EFI_STATUS err;
163 UINT64 key;
164 UINTN j;
165 UINTN cursor_color = TEXT_ATTR_SWAP(COLOR_EDIT);
166
167 j = MIN(len - first, x_max);
168 CopyMem(print, line + first, j * sizeof(CHAR16));
169 while (clear > 0 && j < x_max) {
170 clear--;
171 print[j++] = ' ';
172 }
173 print[j] = '\0';
174
175 /* See comment at edit_line() call site for why we start at 1. */
176 print_at(1, y_pos, COLOR_EDIT, print);
177
178 if (!print[cursor])
179 print[cursor] = ' ';
180 print[cursor+1] = '\0';
181 do {
182 print_at(cursor + 1, y_pos, cursor_color, print + cursor);
183 cursor_color = TEXT_ATTR_SWAP(cursor_color);
184
185 err = console_key_read(&key, 750 * 1000);
186 if (!IN_SET(err, EFI_SUCCESS, EFI_TIMEOUT, EFI_NOT_READY))
187 return FALSE;
188
189 print_at(cursor + 1, y_pos, COLOR_EDIT, print + cursor);
190 } while (EFI_ERROR(err));
191
192 switch (key) {
193 case KEYPRESS(0, SCAN_ESC, 0):
194 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'):
195 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'):
196 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')):
197 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')):
198 return FALSE;
199
200 case KEYPRESS(0, SCAN_HOME, 0):
201 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'):
202 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')):
203 /* beginning-of-line */
204 cursor = 0;
205 first = 0;
206 continue;
207
208 case KEYPRESS(0, SCAN_END, 0):
209 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'):
210 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')):
211 /* end-of-line */
212 cursor = len - first;
213 if (cursor+1 >= x_max) {
214 cursor = x_max-1;
215 first = len - (x_max-1);
216 }
217 continue;
218
219 case KEYPRESS(0, SCAN_DOWN, 0):
220 case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'):
221 case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0):
222 /* forward-word */
223 while (line[first + cursor] == ' ')
224 cursor_right(&cursor, &first, x_max, len);
225 while (line[first + cursor] && line[first + cursor] != ' ')
226 cursor_right(&cursor, &first, x_max, len);
227 continue;
228
229 case KEYPRESS(0, SCAN_UP, 0):
230 case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'):
231 case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0):
232 /* backward-word */
233 if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
234 cursor_left(&cursor, &first);
235 while ((first + cursor) > 0 && line[first + cursor] == ' ')
236 cursor_left(&cursor, &first);
237 }
238 while ((first + cursor) > 0 && line[first + cursor-1] != ' ')
239 cursor_left(&cursor, &first);
240 continue;
241
242 case KEYPRESS(0, SCAN_RIGHT, 0):
243 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'):
244 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')):
245 /* forward-char */
246 if (first + cursor == len)
247 continue;
248 cursor_right(&cursor, &first, x_max, len);
249 continue;
250
251 case KEYPRESS(0, SCAN_LEFT, 0):
252 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'):
253 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')):
254 /* backward-char */
255 cursor_left(&cursor, &first);
256 continue;
257
258 case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'):
259 /* kill-word */
260 clear = 0;
261
262 UINTN k;
263 for (k = first + cursor; k < len && line[k] == ' '; k++)
264 clear++;
265 for (; k < len && line[k] != ' '; k++)
266 clear++;
267
268 for (UINTN i = first + cursor; i + clear < len; i++)
269 line[i] = line[i + clear];
270 len -= clear;
271 line[len] = '\0';
272 continue;
273
274 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'):
275 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')):
276 case KEYPRESS(EFI_ALT_PRESSED, 0, CHAR_BACKSPACE):
277 /* backward-kill-word */
278 clear = 0;
279 if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
280 cursor_left(&cursor, &first);
281 clear++;
282 while ((first + cursor) > 0 && line[first + cursor] == ' ') {
283 cursor_left(&cursor, &first);
284 clear++;
285 }
286 }
287 while ((first + cursor) > 0 && line[first + cursor-1] != ' ') {
288 cursor_left(&cursor, &first);
289 clear++;
290 }
291
292 for (UINTN i = first + cursor; i + clear < len; i++)
293 line[i] = line[i + clear];
294 len -= clear;
295 line[len] = '\0';
296 continue;
297
298 case KEYPRESS(0, SCAN_DELETE, 0):
299 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'):
300 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')):
301 if (len == 0)
302 continue;
303 if (first + cursor == len)
304 continue;
305 for (UINTN i = first + cursor; i < len; i++)
306 line[i] = line[i+1];
307 clear = 1;
308 len--;
309 continue;
310
311 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'):
312 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')):
313 /* kill-line */
314 line[first + cursor] = '\0';
315 clear = len - (first + cursor);
316 len = first + cursor;
317 continue;
318
319 case KEYPRESS(0, 0, CHAR_LINEFEED):
320 case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN):
321 case KEYPRESS(0, CHAR_CARRIAGE_RETURN, 0): /* EZpad Mini 4s firmware sends malformed events */
322 case KEYPRESS(0, CHAR_CARRIAGE_RETURN, CHAR_CARRIAGE_RETURN): /* Teclast X98+ II firmware sends malformed events */
323 if (StrCmp(line, line_in) != 0)
324 *line_out = TAKE_PTR(line);
325 return TRUE;
326
327 case KEYPRESS(0, 0, CHAR_BACKSPACE):
328 if (len == 0)
329 continue;
330 if (first == 0 && cursor == 0)
331 continue;
332 for (UINTN i = first + cursor-1; i < len; i++)
333 line[i] = line[i+1];
334 clear = 1;
335 len--;
336 if (cursor > 0)
337 cursor--;
338 if (cursor > 0 || first == 0)
339 continue;
340 /* show full line if it fits */
341 if (len < x_max) {
342 cursor = first;
343 first = 0;
344 continue;
345 }
346 /* jump left to see what we delete */
347 if (first > 10) {
348 first -= 10;
349 cursor = 10;
350 } else {
351 cursor = first;
352 first = 0;
353 }
354 continue;
355
356 case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
357 case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
358 if (len+1 == size)
359 continue;
360 for (UINTN i = len; i > first + cursor; i--)
361 line[i] = line[i-1];
362 line[first + cursor] = KEYCHAR(key);
363 len++;
364 line[len] = '\0';
365 if (cursor+1 < x_max)
366 cursor++;
367 else if (first + cursor < len)
368 first++;
369 continue;
370 }
371 }
372 }
373
374 static UINTN entry_lookup_key(Config *config, UINTN start, CHAR16 key) {
375 assert(config);
376
377 if (key == 0)
378 return IDX_INVALID;
379
380 /* select entry by number key */
381 if (key >= '1' && key <= '9') {
382 UINTN i = key - '0';
383 if (i > config->entry_count)
384 i = config->entry_count;
385 return i-1;
386 }
387
388 /* find matching key in config entries */
389 for (UINTN i = start; i < config->entry_count; i++)
390 if (config->entries[i]->key == key)
391 return i;
392
393 for (UINTN i = 0; i < start; i++)
394 if (config->entries[i]->key == key)
395 return i;
396
397 return IDX_INVALID;
398 }
399
400 static CHAR16 *update_timeout_efivar(UINT32 *t, BOOLEAN inc) {
401 assert(t);
402
403 switch (*t) {
404 case TIMEOUT_MAX:
405 *t = inc ? TIMEOUT_MAX : (*t - 1);
406 break;
407 case TIMEOUT_UNSET:
408 *t = inc ? TIMEOUT_MENU_FORCE : TIMEOUT_UNSET;
409 break;
410 case TIMEOUT_MENU_FORCE:
411 *t = inc ? TIMEOUT_MENU_HIDDEN : TIMEOUT_UNSET;
412 break;
413 case TIMEOUT_MENU_HIDDEN:
414 *t = inc ? TIMEOUT_MIN : TIMEOUT_MENU_FORCE;
415 break;
416 default:
417 *t += inc ? 1 : -1;
418 }
419
420 switch (*t) {
421 case TIMEOUT_UNSET:
422 return xstrdup(L"Menu timeout defined by configuration file.");
423 case TIMEOUT_MENU_FORCE:
424 return xstrdup(L"Timeout disabled, menu will always be shown.");
425 case TIMEOUT_MENU_HIDDEN:
426 return xstrdup(L"Menu disabled. Hold down key at bootup to show menu.");
427 default:
428 return xpool_print(L"Menu timeout set to %u s.", *t);
429 }
430 }
431
432 static void ps_string(const CHAR16 *fmt, const void *value) {
433 assert(fmt);
434 if (value)
435 Print(fmt, value);
436 }
437
438 static void ps_bool(const CHAR16 *fmt, BOOLEAN value) {
439 assert(fmt);
440 Print(fmt, yes_no(value));
441 }
442
443 static BOOLEAN ps_continue(void) {
444 UINT64 key;
445 EFI_STATUS err;
446
447 Print(L"\n--- Press any key to continue, ESC or q to quit. ---\n\n");
448 err = console_key_read(&key, UINT64_MAX);
449 return !EFI_ERROR(err) && !IN_SET(key, KEYPRESS(0, SCAN_ESC, 0), KEYPRESS(0, 0, 'q'), KEYPRESS(0, 0, 'Q'));
450 }
451
452 static void print_status(Config *config, CHAR16 *loaded_image_path) {
453 UINTN x_max, y_max;
454 UINT32 screen_width = 0, screen_height = 0;
455 SecureBootMode secure;
456 _cleanup_freepool_ CHAR16 *device_part_uuid = NULL;
457
458 assert(config);
459 assert(loaded_image_path);
460
461 clear_screen(COLOR_NORMAL);
462 console_query_mode(&x_max, &y_max);
463 query_screen_resolution(&screen_width, &screen_height);
464
465 secure = secure_boot_mode();
466 (void) efivar_get(LOADER_GUID, L"LoaderDevicePartUUID", &device_part_uuid);
467
468 /* We employ some unusual indentation here for readability. */
469
470 ps_string(L" systemd-boot version: %a\n", GIT_VERSION);
471 ps_string(L" loaded image: %s\n", loaded_image_path);
472 ps_string(L" loader partition UUID: %s\n", device_part_uuid);
473 ps_string(L" architecture: %a\n", EFI_MACHINE_TYPE_NAME);
474 Print(L" UEFI specification: %u.%02u\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
475 ps_string(L" firmware vendor: %s\n", ST->FirmwareVendor);
476 Print(L" firmware version: %u.%02u\n", ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
477 Print(L" OS indications: %lu\n", get_os_indications_supported());
478 Print(L" secure boot: %s (%s)\n", yes_no(IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED)), secure_boot_mode_to_string(secure));
479 ps_bool(L" shim: %s\n", shim_loaded());
480 ps_bool(L" TPM: %s\n", tpm_present());
481 Print(L" console mode: %d/%d (%lux%lu @%ux%u)\n", ST->ConOut->Mode->Mode, ST->ConOut->Mode->MaxMode - 1LL, x_max, y_max, screen_width, screen_height);
482
483 if (!ps_continue())
484 return;
485
486 switch (config->timeout_sec_config) {
487 case TIMEOUT_UNSET:
488 break;
489 case TIMEOUT_MENU_FORCE:
490 Print(L" timeout: menu-force\n"); break;
491 case TIMEOUT_MENU_HIDDEN:
492 Print(L" timeout: menu-hidden\n"); break;
493 default:
494 Print(L" timeout: %lu s\n", config->timeout_sec_config);
495 }
496
497 switch (config->timeout_sec_efivar) {
498 case TIMEOUT_UNSET:
499 break;
500 case TIMEOUT_MENU_FORCE:
501 Print(L" timeout (EFI var): menu-force\n"); break;
502 case TIMEOUT_MENU_HIDDEN:
503 Print(L" timeout (EFI var): menu-hidden\n"); break;
504 default:
505 Print(L" timeout (EFI var): %lu s\n", config->timeout_sec_efivar);
506 }
507
508 ps_string(L" default: %s\n", config->entry_default_config);
509 ps_string(L" default (EFI var): %s\n", config->entry_default_efivar);
510 ps_string(L" default (one-shot): %s\n", config->entry_oneshot);
511 ps_string(L" saved entry: %s\n", config->entry_saved);
512 ps_bool(L" editor: %s\n", config->editor);
513 ps_bool(L" auto-entries: %s\n", config->auto_entries);
514 ps_bool(L" auto-firmware: %s\n", config->auto_firmware);
515 ps_bool(L" beep: %s\n", config->beep);
516 ps_bool(L" reboot-for-bitlocker: %s\n", config->reboot_for_bitlocker);
517 ps_string(L" random-seed-mode: %s\n", random_seed_modes_table[config->random_seed_mode]);
518
519 switch (config->console_mode) {
520 case CONSOLE_MODE_AUTO:
521 Print(L" console-mode: %s\n", L"auto"); break;
522 case CONSOLE_MODE_KEEP:
523 Print(L" console-mode: %s\n", L"keep"); break;
524 case CONSOLE_MODE_FIRMWARE_MAX:
525 Print(L" console-mode: %s\n", L"max"); break;
526 default:
527 Print(L" console-mode: %ld\n", config->console_mode); break;
528 }
529
530 /* EFI var console mode is always a concrete value or unset. */
531 if (config->console_mode_efivar != CONSOLE_MODE_KEEP)
532 Print(L"console-mode (EFI var): %ld\n", config->console_mode_efivar);
533
534 if (!ps_continue())
535 return;
536
537 for (UINTN i = 0; i < config->entry_count; i++) {
538 ConfigEntry *entry = config->entries[i];
539
540 Print(L" config entry: %lu/%lu\n", i + 1, config->entry_count);
541 ps_string(L" id: %s\n", entry->id);
542 ps_string(L" title: %s\n", entry->title);
543 ps_string(L" title show: %s\n", streq_ptr(entry->title, entry->title_show) ? NULL : entry->title_show);
544 ps_string(L" sort key: %s\n", entry->sort_key);
545 ps_string(L" version: %s\n", entry->version);
546 ps_string(L" machine-id: %s\n", entry->machine_id);
547 if (entry->device)
548 Print(L" device: %D\n", DevicePathFromHandle(entry->device));
549 ps_string(L" loader: %s\n", entry->loader);
550 ps_string(L" devicetree: %s\n", entry->devicetree);
551 ps_string(L" options: %s\n", entry->options);
552 ps_bool(L" internal call: %s\n", !!entry->call);
553
554 ps_bool(L"counting boots: %s\n", entry->tries_left != UINTN_MAX);
555 if (entry->tries_left != UINTN_MAX) {
556 Print(L" tries: %lu done, %lu left\n", entry->tries_done, entry->tries_left);
557 Print(L" current path: %s\\%s\n", entry->path, entry->current_name);
558 Print(L" next path: %s\\%s\n", entry->path, entry->next_name);
559 }
560
561 if (!ps_continue())
562 return;
563 }
564 }
565
566 static EFI_STATUS reboot_into_firmware(void) {
567 UINT64 osind = 0;
568 EFI_STATUS err;
569
570 if (!FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI))
571 return log_error_status_stall(EFI_UNSUPPORTED, L"Reboot to firmware interface not supported.");
572
573 (void) efivar_get_uint64_le(EFI_GLOBAL_GUID, L"OsIndications", &osind);
574 osind |= EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
575
576 err = efivar_set_uint64_le(EFI_GLOBAL_GUID, L"OsIndications", osind, EFI_VARIABLE_NON_VOLATILE);
577 if (EFI_ERROR(err))
578 return log_error_status_stall(err, L"Error setting OsIndications: %r", err);
579
580 err = RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
581 return log_error_status_stall(err, L"Error calling ResetSystem: %r", err);
582 }
583
584 static BOOLEAN menu_run(
585 Config *config,
586 ConfigEntry **chosen_entry,
587 CHAR16 *loaded_image_path) {
588
589 assert(config);
590 assert(chosen_entry);
591 assert(loaded_image_path);
592
593 EFI_STATUS err;
594 UINTN visible_max = 0;
595 UINTN idx_highlight = config->idx_default;
596 UINTN idx_highlight_prev = 0;
597 UINTN idx, idx_first = 0, idx_last = 0;
598 BOOLEAN new_mode = TRUE, clear = TRUE;
599 BOOLEAN refresh = TRUE, highlight = FALSE;
600 UINTN x_start = 0, y_start = 0, y_status = 0;
601 UINTN x_max, y_max;
602 _cleanup_(strv_freep) CHAR16 **lines = NULL;
603 _cleanup_freepool_ CHAR16 *clearline = NULL, *status = NULL;
604 UINT32 timeout_efivar_saved = config->timeout_sec_efivar;
605 UINT32 timeout_remain = config->timeout_sec == TIMEOUT_MENU_FORCE ? 0 : config->timeout_sec;
606 BOOLEAN exit = FALSE, run = TRUE, firmware_setup = FALSE;
607 INT64 console_mode_initial = ST->ConOut->Mode->Mode, console_mode_efivar_saved = config->console_mode_efivar;
608 UINTN default_efivar_saved = config->idx_default_efivar;
609
610 graphics_mode(FALSE);
611 ST->ConIn->Reset(ST->ConIn, FALSE);
612 ST->ConOut->EnableCursor(ST->ConOut, FALSE);
613
614 /* draw a single character to make ClearScreen work on some firmware */
615 Print(L" ");
616
617 err = console_set_mode(config->console_mode_efivar != CONSOLE_MODE_KEEP ?
618 config->console_mode_efivar : config->console_mode);
619 if (EFI_ERROR(err)) {
620 clear_screen(COLOR_NORMAL);
621 log_error_stall(L"Error switching console mode: %r", err);
622 }
623
624 while (!exit) {
625 UINT64 key;
626
627 if (new_mode) {
628 UINTN line_width = 0, entry_padding = 3;
629
630 console_query_mode(&x_max, &y_max);
631
632 /* account for padding+status */
633 visible_max = y_max - 2;
634
635 /* Drawing entries starts at idx_first until idx_last. We want to make
636 * sure that idx_highlight is centered, but not if we are close to the
637 * beginning/end of the entry list. Otherwise we would have a half-empty
638 * screen. */
639 if (config->entry_count <= visible_max || idx_highlight <= visible_max / 2)
640 idx_first = 0;
641 else if (idx_highlight >= config->entry_count - (visible_max / 2))
642 idx_first = config->entry_count - visible_max;
643 else
644 idx_first = idx_highlight - (visible_max / 2);
645 idx_last = idx_first + visible_max - 1;
646
647 /* length of the longest entry */
648 for (UINTN i = 0; i < config->entry_count; i++)
649 line_width = MAX(line_width, StrLen(config->entries[i]->title_show));
650 line_width = MIN(line_width + 2 * entry_padding, x_max);
651
652 /* offsets to center the entries on the screen */
653 x_start = (x_max - (line_width)) / 2;
654 if (config->entry_count < visible_max)
655 y_start = ((visible_max - config->entry_count) / 2) + 1;
656 else
657 y_start = 0;
658
659 /* Put status line after the entry list, but give it some breathing room. */
660 y_status = MIN(y_start + MIN(visible_max, config->entry_count) + 4, y_max - 1);
661
662 lines = strv_free(lines);
663 clearline = mfree(clearline);
664
665 /* menu entries title lines */
666 lines = xnew(CHAR16*, config->entry_count + 1);
667
668 for (UINTN i = 0; i < config->entry_count; i++) {
669 UINTN j, padding;
670
671 lines[i] = xnew(CHAR16, line_width + 1);
672 padding = (line_width - MIN(StrLen(config->entries[i]->title_show), line_width)) / 2;
673
674 for (j = 0; j < padding; j++)
675 lines[i][j] = ' ';
676
677 for (UINTN k = 0; config->entries[i]->title_show[k] != '\0' && j < line_width; j++, k++)
678 lines[i][j] = config->entries[i]->title_show[k];
679
680 for (; j < line_width; j++)
681 lines[i][j] = ' ';
682 lines[i][line_width] = '\0';
683 }
684 lines[config->entry_count] = NULL;
685
686 clearline = xnew(CHAR16, x_max + 1);
687 for (UINTN i = 0; i < x_max; i++)
688 clearline[i] = ' ';
689 clearline[x_max] = 0;
690
691 new_mode = FALSE;
692 clear = TRUE;
693 }
694
695 if (clear) {
696 clear_screen(COLOR_NORMAL);
697 clear = FALSE;
698 refresh = TRUE;
699 }
700
701 if (refresh) {
702 for (UINTN i = idx_first; i <= idx_last && i < config->entry_count; i++) {
703 print_at(x_start, y_start + i - idx_first,
704 (i == idx_highlight) ? COLOR_HIGHLIGHT : COLOR_ENTRY,
705 lines[i]);
706 if (i == config->idx_default_efivar)
707 print_at(x_start, y_start + i - idx_first,
708 (i == idx_highlight) ? COLOR_HIGHLIGHT : COLOR_ENTRY,
709 (CHAR16*) L"=>");
710 }
711 refresh = FALSE;
712 } else if (highlight) {
713 print_at(x_start, y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, lines[idx_highlight_prev]);
714 print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, lines[idx_highlight]);
715 if (idx_highlight_prev == config->idx_default_efivar)
716 print_at(x_start , y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, (CHAR16*) L"=>");
717 if (idx_highlight == config->idx_default_efivar)
718 print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, (CHAR16*) L"=>");
719 highlight = FALSE;
720 }
721
722 if (timeout_remain > 0) {
723 FreePool(status);
724 status = xpool_print(L"Boot in %u s.", timeout_remain);
725 }
726
727 /* print status at last line of screen */
728 if (status) {
729 UINTN len;
730 UINTN x;
731
732 /* center line */
733 len = StrLen(status);
734 if (len < x_max)
735 x = (x_max - len) / 2;
736 else
737 x = 0;
738 print_at(0, y_status, COLOR_NORMAL, clearline + (x_max - x));
739 ST->ConOut->OutputString(ST->ConOut, status);
740 ST->ConOut->OutputString(ST->ConOut, clearline + 1 + x + len);
741 }
742
743 /* Beep several times so that the selected entry can be distinguished. */
744 if (config->beep)
745 beep(idx_highlight + 1);
746
747 err = console_key_read(&key, timeout_remain > 0 ? 1000 * 1000 : UINT64_MAX);
748 if (err == EFI_NOT_READY)
749 /* No input device returned a key, try again. This
750 * normally should not happen. */
751 continue;
752 if (err == EFI_TIMEOUT) {
753 assert(timeout_remain > 0);
754 timeout_remain--;
755 if (timeout_remain == 0) {
756 exit = TRUE;
757 break;
758 }
759
760 /* update status */
761 continue;
762 }
763 if (EFI_ERROR(err)) {
764 exit = TRUE;
765 break;
766 }
767
768 timeout_remain = 0;
769
770 /* clear status after keystroke */
771 if (status) {
772 FreePool(status);
773 status = NULL;
774 print_at(0, y_status, COLOR_NORMAL, clearline + 1);
775 }
776
777 idx_highlight_prev = idx_highlight;
778
779 if (firmware_setup) {
780 firmware_setup = FALSE;
781 if (key == KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN))
782 reboot_into_firmware();
783 continue;
784 }
785
786 switch (key) {
787 case KEYPRESS(0, SCAN_UP, 0):
788 case KEYPRESS(0, 0, 'k'):
789 case KEYPRESS(0, 0, 'K'):
790 if (idx_highlight > 0)
791 idx_highlight--;
792 break;
793
794 case KEYPRESS(0, SCAN_DOWN, 0):
795 case KEYPRESS(0, 0, 'j'):
796 case KEYPRESS(0, 0, 'J'):
797 if (idx_highlight < config->entry_count-1)
798 idx_highlight++;
799 break;
800
801 case KEYPRESS(0, SCAN_HOME, 0):
802 case KEYPRESS(EFI_ALT_PRESSED, 0, '<'):
803 if (idx_highlight > 0) {
804 refresh = TRUE;
805 idx_highlight = 0;
806 }
807 break;
808
809 case KEYPRESS(0, SCAN_END, 0):
810 case KEYPRESS(EFI_ALT_PRESSED, 0, '>'):
811 if (idx_highlight < config->entry_count-1) {
812 refresh = TRUE;
813 idx_highlight = config->entry_count-1;
814 }
815 break;
816
817 case KEYPRESS(0, SCAN_PAGE_UP, 0):
818 if (idx_highlight > visible_max)
819 idx_highlight -= visible_max;
820 else
821 idx_highlight = 0;
822 break;
823
824 case KEYPRESS(0, SCAN_PAGE_DOWN, 0):
825 idx_highlight += visible_max;
826 if (idx_highlight > config->entry_count-1)
827 idx_highlight = config->entry_count-1;
828 break;
829
830 case KEYPRESS(0, 0, CHAR_LINEFEED):
831 case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN):
832 case KEYPRESS(0, CHAR_CARRIAGE_RETURN, 0): /* EZpad Mini 4s firmware sends malformed events */
833 case KEYPRESS(0, CHAR_CARRIAGE_RETURN, CHAR_CARRIAGE_RETURN): /* Teclast X98+ II firmware sends malformed events */
834 case KEYPRESS(0, SCAN_RIGHT, 0):
835 exit = TRUE;
836 break;
837
838 case KEYPRESS(0, SCAN_F1, 0):
839 case KEYPRESS(0, 0, 'h'):
840 case KEYPRESS(0, 0, 'H'):
841 case KEYPRESS(0, 0, '?'):
842 /* This must stay below 80 characters! Q/v/Ctrl+l/f deliberately not advertised. */
843 status = xstrdup(L"(d)efault (t/T)timeout (e)dit (r/R)resolution (p)rint (h)elp");
844 break;
845
846 case KEYPRESS(0, 0, 'Q'):
847 exit = TRUE;
848 run = FALSE;
849 break;
850
851 case KEYPRESS(0, 0, 'd'):
852 case KEYPRESS(0, 0, 'D'):
853 if (config->idx_default_efivar != idx_highlight) {
854 FreePool(config->entry_default_efivar);
855 config->entry_default_efivar = xstrdup(config->entries[idx_highlight]->id);
856 config->idx_default_efivar = idx_highlight;
857 status = xstrdup(L"Default boot entry selected.");
858 } else {
859 config->entry_default_efivar = mfree(config->entry_default_efivar);
860 config->idx_default_efivar = IDX_INVALID;
861 status = xstrdup(L"Default boot entry cleared.");
862 }
863 config->use_saved_entry_efivar = FALSE;
864 refresh = TRUE;
865 break;
866
867 case KEYPRESS(0, 0, '-'):
868 case KEYPRESS(0, 0, 'T'):
869 status = update_timeout_efivar(&config->timeout_sec_efivar, FALSE);
870 break;
871
872 case KEYPRESS(0, 0, '+'):
873 case KEYPRESS(0, 0, 't'):
874 status = update_timeout_efivar(&config->timeout_sec_efivar, TRUE);
875 break;
876
877 case KEYPRESS(0, 0, 'e'):
878 case KEYPRESS(0, 0, 'E'):
879 /* only the options of configured entries can be edited */
880 if (!config->editor || !IN_SET(config->entries[idx_highlight]->type,
881 LOADER_EFI, LOADER_LINUX, LOADER_UNIFIED_LINUX))
882 break;
883
884 /* Unified kernels that are signed as a whole will not accept command line options
885 * when secure boot is enabled unless there is none embedded in the image. Do not try
886 * to pretend we can edit it to only have it be ignored. */
887 if (config->entries[idx_highlight]->type == LOADER_UNIFIED_LINUX &&
888 secure_boot_enabled() &&
889 config->entries[idx_highlight]->options)
890 break;
891
892 /* The edit line may end up on the last line of the screen. And even though we're
893 * not telling the firmware to advance the line, it still does in this one case,
894 * causing a scroll to happen that screws with our beautiful boot loader output.
895 * Since we cannot paint the last character of the edit line, we simply start
896 * at x-offset 1 for symmetry. */
897 print_at(1, y_status, COLOR_EDIT, clearline + 2);
898 exit = line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max - 2, y_status);
899 print_at(1, y_status, COLOR_NORMAL, clearline + 2);
900 break;
901
902 case KEYPRESS(0, 0, 'v'):
903 status = xpool_print(L"systemd-boot " GIT_VERSION " (" EFI_MACHINE_TYPE_NAME "), "
904 L"UEFI Specification %d.%02d, Vendor %s %d.%02d",
905 ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff,
906 ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
907 break;
908
909 case KEYPRESS(0, 0, 'p'):
910 case KEYPRESS(0, 0, 'P'):
911 print_status(config, loaded_image_path);
912 clear = TRUE;
913 break;
914
915 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'l'):
916 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('l')):
917 clear = TRUE;
918 break;
919
920 case KEYPRESS(0, 0, 'r'):
921 err = console_set_mode(CONSOLE_MODE_NEXT);
922 if (EFI_ERROR(err))
923 status = xpool_print(L"Error changing console mode: %r", err);
924 else {
925 config->console_mode_efivar = ST->ConOut->Mode->Mode;
926 status = xpool_print(L"Console mode changed to %ld.", config->console_mode_efivar);
927 }
928 new_mode = TRUE;
929 break;
930
931 case KEYPRESS(0, 0, 'R'):
932 config->console_mode_efivar = CONSOLE_MODE_KEEP;
933 err = console_set_mode(config->console_mode == CONSOLE_MODE_KEEP ?
934 console_mode_initial : config->console_mode);
935 if (EFI_ERROR(err))
936 status = xpool_print(L"Error resetting console mode: %r", err);
937 else
938 status = xpool_print(L"Console mode reset to %s default.",
939 config->console_mode == CONSOLE_MODE_KEEP ? L"firmware" : L"configuration file");
940 new_mode = TRUE;
941 break;
942
943 case KEYPRESS(0, 0, 'f'):
944 case KEYPRESS(0, 0, 'F'):
945 case KEYPRESS(0, SCAN_F2, 0): /* Most vendors. */
946 case KEYPRESS(0, SCAN_F10, 0): /* HP and Lenovo. */
947 case KEYPRESS(0, SCAN_DELETE, 0): /* Same as F2. */
948 case KEYPRESS(0, SCAN_ESC, 0): /* HP. */
949 if (FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) {
950 firmware_setup = TRUE;
951 /* Let's make sure the user really wants to do this. */
952 status = xpool_print(L"Press Enter to reboot into firmware interface.");
953 } else
954 status = xpool_print(L"Reboot into firmware interface not supported.");
955 break;
956
957 default:
958 /* jump with a hotkey directly to a matching entry */
959 idx = entry_lookup_key(config, idx_highlight+1, KEYCHAR(key));
960 if (idx == IDX_INVALID)
961 break;
962 idx_highlight = idx;
963 refresh = TRUE;
964 }
965
966 if (idx_highlight > idx_last) {
967 idx_last = idx_highlight;
968 idx_first = 1 + idx_highlight - visible_max;
969 refresh = TRUE;
970 } else if (idx_highlight < idx_first) {
971 idx_first = idx_highlight;
972 idx_last = idx_highlight + visible_max-1;
973 refresh = TRUE;
974 }
975
976 if (!refresh && idx_highlight != idx_highlight_prev)
977 highlight = TRUE;
978 }
979
980 *chosen_entry = config->entries[idx_highlight];
981
982 /* Update EFI vars after we left the menu to reduce NVRAM writes. */
983
984 if (default_efivar_saved != config->idx_default_efivar)
985 efivar_set(LOADER_GUID, L"LoaderEntryDefault", config->entry_default_efivar, EFI_VARIABLE_NON_VOLATILE);
986
987 if (console_mode_efivar_saved != config->console_mode_efivar) {
988 if (config->console_mode_efivar == CONSOLE_MODE_KEEP)
989 efivar_set(LOADER_GUID, L"LoaderConfigConsoleMode", NULL, EFI_VARIABLE_NON_VOLATILE);
990 else
991 efivar_set_uint_string(LOADER_GUID, L"LoaderConfigConsoleMode",
992 config->console_mode_efivar, EFI_VARIABLE_NON_VOLATILE);
993 }
994
995 if (timeout_efivar_saved != config->timeout_sec_efivar) {
996 if (config->timeout_sec_efivar == TIMEOUT_UNSET)
997 efivar_set(LOADER_GUID, L"LoaderConfigTimeout", NULL, EFI_VARIABLE_NON_VOLATILE);
998 else
999 efivar_set_uint_string(LOADER_GUID, L"LoaderConfigTimeout",
1000 config->timeout_sec_efivar, EFI_VARIABLE_NON_VOLATILE);
1001 }
1002
1003 clear_screen(COLOR_NORMAL);
1004 return run;
1005 }
1006
1007 static void config_add_entry(Config *config, ConfigEntry *entry) {
1008 assert(config);
1009 assert(entry);
1010
1011 /* This is just for paranoia. */
1012 assert(config->entry_count < IDX_MAX);
1013
1014 if ((config->entry_count & 15) == 0) {
1015 UINTN i = config->entry_count + 16;
1016 config->entries = xreallocate_pool(
1017 config->entries,
1018 sizeof(void *) * config->entry_count,
1019 sizeof(void *) * i);
1020 }
1021 config->entries[config->entry_count++] = entry;
1022 }
1023
1024 static void config_entry_free(ConfigEntry *entry) {
1025 if (!entry)
1026 return;
1027
1028 FreePool(entry->id);
1029 FreePool(entry->title_show);
1030 FreePool(entry->title);
1031 FreePool(entry->sort_key);
1032 FreePool(entry->version);
1033 FreePool(entry->machine_id);
1034 FreePool(entry->loader);
1035 FreePool(entry->devicetree);
1036 FreePool(entry->options);
1037 FreePool(entry->path);
1038 FreePool(entry->current_name);
1039 FreePool(entry->next_name);
1040 FreePool(entry);
1041 }
1042
1043 static inline void config_entry_freep(ConfigEntry **entry) {
1044 config_entry_free(*entry);
1045 }
1046
1047 static CHAR8 *line_get_key_value(
1048 CHAR8 *content,
1049 const CHAR8 *sep,
1050 UINTN *pos,
1051 CHAR8 **key_ret,
1052 CHAR8 **value_ret) {
1053
1054 CHAR8 *line, *value;
1055 UINTN linelen;
1056
1057 assert(content);
1058 assert(sep);
1059 assert(pos);
1060 assert(key_ret);
1061 assert(value_ret);
1062
1063 for (;;) {
1064 line = content + *pos;
1065 if (*line == '\0')
1066 return NULL;
1067
1068 linelen = 0;
1069 while (line[linelen] && !strchra((CHAR8 *) "\n\r", line[linelen]))
1070 linelen++;
1071
1072 /* move pos to next line */
1073 *pos += linelen;
1074 if (content[*pos])
1075 (*pos)++;
1076
1077 /* empty line */
1078 if (linelen == 0)
1079 continue;
1080
1081 /* terminate line */
1082 line[linelen] = '\0';
1083
1084 /* remove leading whitespace */
1085 while (strchra((CHAR8 *) " \t", *line)) {
1086 line++;
1087 linelen--;
1088 }
1089
1090 /* remove trailing whitespace */
1091 while (linelen > 0 && strchra((CHAR8 *) " \t", line[linelen - 1]))
1092 linelen--;
1093 line[linelen] = '\0';
1094
1095 if (*line == '#')
1096 continue;
1097
1098 /* split key/value */
1099 value = line;
1100 while (*value && !strchra(sep, *value))
1101 value++;
1102 if (*value == '\0')
1103 continue;
1104 *value = '\0';
1105 value++;
1106 while (*value && strchra(sep, *value))
1107 value++;
1108
1109 /* unquote */
1110 if (value[0] == '"' && line[linelen - 1] == '"') {
1111 value++;
1112 line[linelen - 1] = '\0';
1113 }
1114
1115 *key_ret = line;
1116 *value_ret = value;
1117 return line;
1118 }
1119 }
1120
1121 static void config_defaults_load_from_file(Config *config, CHAR8 *content) {
1122 CHAR8 *line;
1123 UINTN pos = 0;
1124 CHAR8 *key, *value;
1125 EFI_STATUS err;
1126
1127 assert(config);
1128 assert(content);
1129
1130 while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) {
1131 if (strcmpa((CHAR8 *)"timeout", key) == 0) {
1132 if (strcmpa((CHAR8*) "menu-force", value) == 0)
1133 config->timeout_sec_config = TIMEOUT_MENU_FORCE;
1134 else if (strcmpa((CHAR8*) "menu-hidden", value) == 0)
1135 config->timeout_sec_config = TIMEOUT_MENU_HIDDEN;
1136 else {
1137 _cleanup_freepool_ CHAR16 *s = NULL;
1138
1139 s = xstra_to_str(value);
1140 config->timeout_sec_config = MIN(Atoi(s), TIMEOUT_TYPE_MAX);
1141 }
1142 config->timeout_sec = config->timeout_sec_config;
1143 continue;
1144 }
1145
1146 if (strcmpa((CHAR8 *)"default", key) == 0) {
1147 if (value[0] == '@' && strcmpa((CHAR8 *)"@saved", value) != 0) {
1148 log_error_stall(L"Unsupported special entry identifier: %a", value);
1149 continue;
1150 }
1151 FreePool(config->entry_default_config);
1152 config->entry_default_config = xstra_to_str(value);
1153 continue;
1154 }
1155
1156 if (strcmpa((CHAR8 *)"editor", key) == 0) {
1157 err = parse_boolean(value, &config->editor);
1158 if (EFI_ERROR(err))
1159 log_error_stall(L"Error parsing 'editor' config option: %a", value);
1160 continue;
1161 }
1162
1163 if (strcmpa((CHAR8 *)"auto-entries", key) == 0) {
1164 err = parse_boolean(value, &config->auto_entries);
1165 if (EFI_ERROR(err))
1166 log_error_stall(L"Error parsing 'auto-entries' config option: %a", value);
1167 continue;
1168 }
1169
1170 if (strcmpa((CHAR8 *)"auto-firmware", key) == 0) {
1171 err = parse_boolean(value, &config->auto_firmware);
1172 if (EFI_ERROR(err))
1173 log_error_stall(L"Error parsing 'auto-firmware' config option: %a", value);
1174 continue;
1175 }
1176
1177 if (strcmpa((CHAR8 *)"beep", key) == 0) {
1178 err = parse_boolean(value, &config->beep);
1179 if (EFI_ERROR(err))
1180 log_error_stall(L"Error parsing 'beep' config option: %a", value);
1181 }
1182
1183 if (strcmpa((CHAR8 *)"reboot-for-bitlocker", key) == 0) {
1184 err = parse_boolean(value, &config->reboot_for_bitlocker);
1185 if (EFI_ERROR(err))
1186 log_error_stall(L"Error parsing 'reboot-for-bitlocker' config option: %a", value);
1187 continue;
1188 }
1189
1190 if (strcmpa((CHAR8 *)"console-mode", key) == 0) {
1191 if (strcmpa((CHAR8 *)"auto", value) == 0)
1192 config->console_mode = CONSOLE_MODE_AUTO;
1193 else if (strcmpa((CHAR8 *)"max", value) == 0)
1194 config->console_mode = CONSOLE_MODE_FIRMWARE_MAX;
1195 else if (strcmpa((CHAR8 *)"keep", value) == 0)
1196 config->console_mode = CONSOLE_MODE_KEEP;
1197 else {
1198 _cleanup_freepool_ CHAR16 *s = NULL;
1199
1200 s = xstra_to_str(value);
1201 config->console_mode = MIN(Atoi(s), (UINTN)CONSOLE_MODE_RANGE_MAX);
1202 }
1203
1204 continue;
1205 }
1206
1207 if (strcmpa((CHAR8*) "random-seed-mode", key) == 0) {
1208 if (strcmpa((CHAR8*) "off", value) == 0)
1209 config->random_seed_mode = RANDOM_SEED_OFF;
1210 else if (strcmpa((CHAR8*) "with-system-token", value) == 0)
1211 config->random_seed_mode = RANDOM_SEED_WITH_SYSTEM_TOKEN;
1212 else if (strcmpa((CHAR8*) "always", value) == 0)
1213 config->random_seed_mode = RANDOM_SEED_ALWAYS;
1214 else {
1215 BOOLEAN on;
1216
1217 err = parse_boolean(value, &on);
1218 if (EFI_ERROR(err)) {
1219 log_error_stall(L"Error parsing 'random-seed-mode' config option: %a", value);
1220 continue;
1221 }
1222
1223 config->random_seed_mode = on ? RANDOM_SEED_ALWAYS : RANDOM_SEED_OFF;
1224 }
1225 }
1226 }
1227 }
1228
1229 static void config_entry_parse_tries(
1230 ConfigEntry *entry,
1231 const CHAR16 *path,
1232 const CHAR16 *file,
1233 const CHAR16 *suffix) {
1234
1235 UINTN left = UINTN_MAX, done = UINTN_MAX, factor = 1, i, next_left, next_done;
1236 _cleanup_freepool_ CHAR16 *prefix = NULL;
1237
1238 assert(entry);
1239 assert(path);
1240 assert(file);
1241
1242 /*
1243 * Parses a suffix of two counters (one going down, one going up) in the form "+LEFT-DONE" from the end of the
1244 * filename (but before the .efi/.conf suffix), where the "-DONE" part is optional and may be left out (in
1245 * which case that counter as assumed to be zero, i.e. the missing part is synonymous to "-0").
1246 *
1247 * Names we grok, and the series they result in:
1248 *
1249 * foobar+3.efi → foobar+2-1.efi → foobar+1-2.efi → foobar+0-3.efi → STOP!
1250 * foobar+4-0.efi → foobar+3-1.efi → foobar+2-2.efi → foobar+1-3.efi → foobar+0-4.efi → STOP!
1251 */
1252
1253 i = StrLen(file);
1254
1255 /* Chop off any suffix such as ".conf" or ".efi" */
1256 if (suffix) {
1257 UINTN suffix_length;
1258
1259 suffix_length = StrLen(suffix);
1260 if (i < suffix_length)
1261 return;
1262
1263 i -= suffix_length;
1264 }
1265
1266 /* Go backwards through the string and parse everything we encounter */
1267 for (;;) {
1268 if (i == 0)
1269 return;
1270
1271 i--;
1272
1273 switch (file[i]) {
1274
1275 case '+':
1276 if (left == UINTN_MAX) /* didn't read at least one digit for 'left'? */
1277 return;
1278
1279 if (done == UINTN_MAX) /* no 'done' counter? If so, it's equivalent to 0 */
1280 done = 0;
1281
1282 goto good;
1283
1284 case '-':
1285 if (left == UINTN_MAX) /* didn't parse any digit yet? */
1286 return;
1287
1288 if (done != UINTN_MAX) /* already encountered a dash earlier? */
1289 return;
1290
1291 /* So we encountered a dash. This means this counter is of the form +LEFT-DONE. Let's assign
1292 * what we already parsed to 'done', and start fresh for the 'left' part. */
1293
1294 done = left;
1295 left = UINTN_MAX;
1296 factor = 1;
1297 break;
1298
1299 case '0'...'9': {
1300 UINTN new_factor;
1301
1302 if (left == UINTN_MAX)
1303 left = file[i] - '0';
1304 else {
1305 UINTN new_left, digit;
1306
1307 digit = file[i] - '0';
1308 if (digit > UINTN_MAX / factor) /* overflow check */
1309 return;
1310
1311 new_left = left + digit * factor;
1312 if (new_left < left) /* overflow check */
1313 return;
1314
1315 if (new_left == UINTN_MAX) /* don't allow us to be confused */
1316 return;
1317 }
1318
1319 new_factor = factor * 10;
1320 if (new_factor < factor) /* overflow check */
1321 return;
1322
1323 factor = new_factor;
1324 break;
1325 }
1326
1327 default:
1328 return;
1329 }
1330 }
1331
1332 good:
1333 entry->tries_left = left;
1334 entry->tries_done = done;
1335
1336 entry->path = xstrdup(path);
1337 entry->current_name = xstrdup(file);
1338
1339 next_left = left <= 0 ? 0 : left - 1;
1340 next_done = done >= (UINTN) -2 ? (UINTN) -2 : done + 1;
1341
1342 prefix = xstrdup(file);
1343 prefix[i] = 0;
1344
1345 entry->next_name = xpool_print(L"%s+%u-%u%s", prefix, next_left, next_done, suffix ?: L"");
1346 }
1347
1348 static void config_entry_bump_counters(ConfigEntry *entry, EFI_FILE *root_dir) {
1349 _cleanup_freepool_ CHAR16* old_path = NULL, *new_path = NULL;
1350 _cleanup_(file_closep) EFI_FILE *handle = NULL;
1351 _cleanup_freepool_ EFI_FILE_INFO *file_info = NULL;
1352 UINTN file_info_size;
1353 EFI_STATUS err;
1354
1355 assert(entry);
1356 assert(root_dir);
1357
1358 if (entry->tries_left == UINTN_MAX)
1359 return;
1360
1361 if (!entry->path || !entry->current_name || !entry->next_name)
1362 return;
1363
1364 old_path = xpool_print(L"%s\\%s", entry->path, entry->current_name);
1365
1366 err = root_dir->Open(root_dir, &handle, old_path, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0ULL);
1367 if (EFI_ERROR(err))
1368 return;
1369
1370 err = get_file_info_harder(handle, &file_info, &file_info_size);
1371 if (EFI_ERROR(err))
1372 return;
1373
1374 /* And rename the file */
1375 StrCpy(file_info->FileName, entry->next_name);
1376 err = handle->SetInfo(handle, &GenericFileInfo, file_info_size, file_info);
1377 if (EFI_ERROR(err)) {
1378 log_error_stall(L"Failed to rename '%s' to '%s', ignoring: %r", old_path, entry->next_name, err);
1379 return;
1380 }
1381
1382 /* Flush everything to disk, just in case… */
1383 (void) handle->Flush(handle);
1384
1385 /* Let's tell the OS that we renamed this file, so that it knows what to rename to the counter-less name on
1386 * success */
1387 new_path = xpool_print(L"%s\\%s", entry->path, entry->next_name);
1388 efivar_set(LOADER_GUID, L"LoaderBootCountPath", new_path, 0);
1389
1390 /* If the file we just renamed is the loader path, then let's update that. */
1391 if (StrCmp(entry->loader, old_path) == 0) {
1392 FreePool(entry->loader);
1393 entry->loader = TAKE_PTR(new_path);
1394 }
1395 }
1396
1397 static void config_entry_add_from_file(
1398 Config *config,
1399 EFI_HANDLE *device,
1400 EFI_FILE *root_dir,
1401 const CHAR16 *path,
1402 const CHAR16 *file,
1403 CHAR8 *content,
1404 const CHAR16 *loaded_image_path) {
1405
1406 _cleanup_(config_entry_freep) ConfigEntry *entry = NULL;
1407 CHAR8 *line;
1408 UINTN pos = 0;
1409 CHAR8 *key, *value;
1410 EFI_STATUS err;
1411 _cleanup_freepool_ CHAR16 *initrd = NULL;
1412
1413 assert(config);
1414 assert(device);
1415 assert(root_dir);
1416 assert(path);
1417 assert(file);
1418 assert(content);
1419
1420 entry = xnew(ConfigEntry, 1);
1421 *entry = (ConfigEntry) {
1422 .tries_done = UINTN_MAX,
1423 .tries_left = UINTN_MAX,
1424 };
1425
1426 while ((line = line_get_key_value(content, (CHAR8 *)" \t", &pos, &key, &value))) {
1427 if (strcmpa((CHAR8 *)"title", key) == 0) {
1428 FreePool(entry->title);
1429 entry->title = xstra_to_str(value);
1430 continue;
1431 }
1432
1433 if (strcmpa((CHAR8 *)"sort-key", key) == 0) {
1434 FreePool(entry->sort_key);
1435 entry->sort_key = xstra_to_str(value);
1436 continue;
1437 }
1438
1439 if (strcmpa((CHAR8 *)"version", key) == 0) {
1440 FreePool(entry->version);
1441 entry->version = xstra_to_str(value);
1442 continue;
1443 }
1444
1445 if (strcmpa((CHAR8 *)"machine-id", key) == 0) {
1446 FreePool(entry->machine_id);
1447 entry->machine_id = xstra_to_str(value);
1448 continue;
1449 }
1450
1451 if (strcmpa((CHAR8 *)"linux", key) == 0) {
1452 FreePool(entry->loader);
1453 entry->type = LOADER_LINUX;
1454 entry->loader = xstra_to_path(value);
1455 entry->key = 'l';
1456 continue;
1457 }
1458
1459 if (strcmpa((CHAR8 *)"efi", key) == 0) {
1460 entry->type = LOADER_EFI;
1461 FreePool(entry->loader);
1462 entry->loader = xstra_to_path(value);
1463
1464 /* do not add an entry for ourselves */
1465 if (loaded_image_path && StriCmp(entry->loader, loaded_image_path) == 0) {
1466 entry->type = LOADER_UNDEFINED;
1467 break;
1468 }
1469 continue;
1470 }
1471
1472 if (strcmpa((CHAR8 *)"architecture", key) == 0) {
1473 /* do not add an entry for an EFI image of architecture not matching with that of the image */
1474 if (strcmpa((CHAR8 *)EFI_MACHINE_TYPE_NAME, value) != 0) {
1475 entry->type = LOADER_UNDEFINED;
1476 break;
1477 }
1478 continue;
1479 }
1480
1481 if (strcmpa((CHAR8 *)"devicetree", key) == 0) {
1482 FreePool(entry->devicetree);
1483 entry->devicetree = xstra_to_path(value);
1484 continue;
1485 }
1486
1487 if (strcmpa((CHAR8 *)"initrd", key) == 0) {
1488 _cleanup_freepool_ CHAR16 *new = NULL;
1489
1490 new = xstra_to_path(value);
1491 if (initrd) {
1492 CHAR16 *s;
1493
1494 s = xpool_print(L"%s initrd=%s", initrd, new);
1495 FreePool(initrd);
1496 initrd = s;
1497 } else
1498 initrd = xpool_print(L"initrd=%s", new);
1499
1500 continue;
1501 }
1502
1503 if (strcmpa((CHAR8 *)"options", key) == 0) {
1504 _cleanup_freepool_ CHAR16 *new = NULL;
1505
1506 new = xstra_to_str(value);
1507 if (entry->options) {
1508 CHAR16 *s;
1509
1510 s = xpool_print(L"%s %s", entry->options, new);
1511 FreePool(entry->options);
1512 entry->options = s;
1513 } else
1514 entry->options = TAKE_PTR(new);
1515
1516 continue;
1517 }
1518 }
1519
1520 if (entry->type == LOADER_UNDEFINED)
1521 return;
1522
1523 /* check existence */
1524 _cleanup_(file_closep) EFI_FILE *handle = NULL;
1525 err = root_dir->Open(root_dir, &handle, entry->loader, EFI_FILE_MODE_READ, 0ULL);
1526 if (EFI_ERROR(err))
1527 return;
1528
1529 /* add initrd= to options */
1530 if (entry->type == LOADER_LINUX && initrd) {
1531 if (entry->options) {
1532 CHAR16 *s;
1533
1534 s = xpool_print(L"%s %s", initrd, entry->options);
1535 FreePool(entry->options);
1536 entry->options = s;
1537 } else
1538 entry->options = TAKE_PTR(initrd);
1539 }
1540
1541 entry->device = device;
1542 entry->id = xstrdup(file);
1543 StrLwr(entry->id);
1544
1545 config_add_entry(config, entry);
1546
1547 config_entry_parse_tries(entry, path, file, L".conf");
1548 TAKE_PTR(entry);
1549 }
1550
1551 static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
1552 _cleanup_freepool_ CHAR8 *content = NULL;
1553 UINTN value;
1554 EFI_STATUS err;
1555
1556 assert(root_dir);
1557
1558 *config = (Config) {
1559 .editor = TRUE,
1560 .auto_entries = TRUE,
1561 .auto_firmware = TRUE,
1562 .reboot_for_bitlocker = FALSE,
1563 .random_seed_mode = RANDOM_SEED_WITH_SYSTEM_TOKEN,
1564 .idx_default_efivar = IDX_INVALID,
1565 .console_mode = CONSOLE_MODE_KEEP,
1566 .console_mode_efivar = CONSOLE_MODE_KEEP,
1567 .timeout_sec_config = TIMEOUT_UNSET,
1568 .timeout_sec_efivar = TIMEOUT_UNSET,
1569 };
1570
1571 err = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content, NULL);
1572 if (!EFI_ERROR(err))
1573 config_defaults_load_from_file(config, content);
1574
1575 err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigTimeout", &value);
1576 if (!EFI_ERROR(err)) {
1577 config->timeout_sec_efivar = MIN(value, TIMEOUT_TYPE_MAX);
1578 config->timeout_sec = config->timeout_sec_efivar;
1579 }
1580
1581 err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigTimeoutOneShot", &value);
1582 if (!EFI_ERROR(err)) {
1583 /* Unset variable now, after all it's "one shot". */
1584 (void) efivar_set(LOADER_GUID, L"LoaderConfigTimeoutOneShot", NULL, EFI_VARIABLE_NON_VOLATILE);
1585
1586 config->timeout_sec = MIN(value, TIMEOUT_TYPE_MAX);
1587 config->force_menu = TRUE; /* force the menu when this is set */
1588 }
1589
1590 err = efivar_get_uint_string(LOADER_GUID, L"LoaderConfigConsoleMode", &value);
1591 if (!EFI_ERROR(err))
1592 config->console_mode_efivar = value;
1593
1594 err = efivar_get(LOADER_GUID, L"LoaderEntryOneShot", &config->entry_oneshot);
1595 if (!EFI_ERROR(err))
1596 /* Unset variable now, after all it's "one shot". */
1597 (void) efivar_set(LOADER_GUID, L"LoaderEntryOneShot", NULL, EFI_VARIABLE_NON_VOLATILE);
1598
1599 (void) efivar_get(LOADER_GUID, L"LoaderEntryDefault", &config->entry_default_efivar);
1600
1601 config->use_saved_entry = streq_ptr(config->entry_default_config, L"@saved");
1602 config->use_saved_entry_efivar = streq_ptr(config->entry_default_efivar, L"@saved");
1603 if (config->use_saved_entry || config->use_saved_entry_efivar)
1604 (void) efivar_get(LOADER_GUID, L"LoaderEntryLastBooted", &config->entry_saved);
1605 }
1606
1607 static void config_load_entries(
1608 Config *config,
1609 EFI_HANDLE *device,
1610 EFI_FILE *root_dir,
1611 const CHAR16 *loaded_image_path) {
1612
1613 _cleanup_(file_closep) EFI_FILE *entries_dir = NULL;
1614 _cleanup_freepool_ EFI_FILE_INFO *f = NULL;
1615 UINTN f_size = 0;
1616 EFI_STATUS err;
1617
1618 assert(config);
1619 assert(device);
1620 assert(root_dir);
1621
1622 /* Adds Boot Loader Type #1 entries (i.e. /loader/entries/….conf) */
1623
1624 err = open_directory(root_dir, L"\\loader\\entries", &entries_dir);
1625 if (EFI_ERROR(err))
1626 return;
1627
1628 for (;;) {
1629 _cleanup_freepool_ CHAR8 *content = NULL;
1630
1631 err = readdir_harder(entries_dir, &f, &f_size);
1632 if (EFI_ERROR(err) || !f)
1633 break;
1634
1635 if (f->FileName[0] == '.')
1636 continue;
1637 if (FLAGS_SET(f->Attribute, EFI_FILE_DIRECTORY))
1638 continue;
1639
1640 if (!endswith_no_case(f->FileName, L".conf"))
1641 continue;
1642 if (startswith(f->FileName, L"auto-"))
1643 continue;
1644
1645 err = file_read(entries_dir, f->FileName, 0, 0, &content, NULL);
1646 if (!EFI_ERROR(err))
1647 config_entry_add_from_file(config, device, root_dir, L"\\loader\\entries", f->FileName, content, loaded_image_path);
1648 }
1649 }
1650
1651 static INTN config_entry_compare(const ConfigEntry *a, const ConfigEntry *b) {
1652 INTN r;
1653
1654 assert(a);
1655 assert(b);
1656
1657 /* Order entries that have no tries left to the end of the list */
1658 r = CMP(a->tries_left == 0, b->tries_left == 0);
1659 if (r != 0)
1660 return r;
1661
1662 /* If there's a sort key defined for *both* entries, then we do new-style ordering, i.e. by
1663 * sort-key/machine-id/version, with a final fallback to id. If there's no sort key for either, we do
1664 * old-style ordering, i.e. by id only. If one has sort key and the other does not, we put new-style
1665 * before old-style. */
1666 r = CMP(!a->sort_key, !b->sort_key);
1667 if (r != 0) /* one is old-style, one new-style */
1668 return r;
1669
1670 if (a->sort_key && b->sort_key) {
1671 r = strcmp(a->sort_key, b->sort_key);
1672 if (r != 0)
1673 return r;
1674
1675 /* If multiple installations of the same OS are around, group by machine ID */
1676 r = strcmp_ptr(a->machine_id, b->machine_id);
1677 if (r != 0)
1678 return r;
1679
1680 /* If the sort key was defined, then order by version now (downwards, putting the newest first) */
1681 r = -strverscmp_improved(a->version, b->version);
1682 if (r != 0)
1683 return r;
1684 }
1685
1686 /* Now order by ID. The version is likely part of the ID, thus note that this will generatelly put
1687 * the newer versions earlier. Specifying a sort key explicitly is preferable, because it gives an
1688 * explicit sort order. */
1689 r = -strverscmp_improved(a->id, b->id);
1690 if (r != 0)
1691 return r;
1692
1693 if (a->tries_left == UINTN_MAX || b->tries_left == UINTN_MAX)
1694 return 0;
1695
1696 /* If both items have boot counting, and otherwise are identical, put the entry with more tries left first */
1697 r = -CMP(a->tries_left, b->tries_left);
1698 if (r != 0)
1699 return r;
1700
1701 /* If they have the same number of tries left, then let the one win which was tried fewer times so far */
1702 return CMP(a->tries_done, b->tries_done);
1703 }
1704
1705 static UINTN config_entry_find(Config *config, const CHAR16 *needle) {
1706 assert(config);
1707
1708 if (!needle)
1709 return IDX_INVALID;
1710
1711 for (UINTN i = 0; i < config->entry_count; i++)
1712 if (MetaiMatch(config->entries[i]->id, (CHAR16*) needle))
1713 return i;
1714
1715 return IDX_INVALID;
1716 }
1717
1718 static void config_default_entry_select(Config *config) {
1719 UINTN i;
1720
1721 assert(config);
1722
1723 i = config_entry_find(config, config->entry_oneshot);
1724 if (i != IDX_INVALID) {
1725 config->idx_default = i;
1726 return;
1727 }
1728
1729 i = config_entry_find(config, config->use_saved_entry_efivar ? config->entry_saved : config->entry_default_efivar);
1730 if (i != IDX_INVALID) {
1731 config->idx_default = i;
1732 config->idx_default_efivar = i;
1733 return;
1734 }
1735
1736 if (config->use_saved_entry)
1737 /* No need to do the same thing twice. */
1738 i = config->use_saved_entry_efivar ? IDX_INVALID : config_entry_find(config, config->entry_saved);
1739 else
1740 i = config_entry_find(config, config->entry_default_config);
1741 if (i != IDX_INVALID) {
1742 config->idx_default = i;
1743 return;
1744 }
1745
1746 /* select the first suitable entry */
1747 for (i = 0; i < config->entry_count; i++) {
1748 if (config->entries[i]->type == LOADER_AUTO || config->entries[i]->call)
1749 continue;
1750 config->idx_default = i;
1751 return;
1752 }
1753
1754 /* If no configured entry to select from was found, enable the menu. */
1755 config->idx_default = 0;
1756 if (config->timeout_sec == 0)
1757 config->timeout_sec = 10;
1758 }
1759
1760 static BOOLEAN entries_unique(ConfigEntry **entries, BOOLEAN *unique, UINTN entry_count) {
1761 BOOLEAN is_unique = TRUE;
1762
1763 assert(entries);
1764 assert(unique);
1765
1766 for (UINTN i = 0; i < entry_count; i++)
1767 for (UINTN k = i + 1; k < entry_count; k++) {
1768 if (StrCmp(entries[i]->title_show, entries[k]->title_show) != 0)
1769 continue;
1770
1771 is_unique = unique[i] = unique[k] = FALSE;
1772 }
1773
1774 return is_unique;
1775 }
1776
1777 /* generate a unique title, avoiding non-distinguishable menu entries */
1778 static void config_title_generate(Config *config) {
1779 assert(config);
1780
1781 BOOLEAN unique[config->entry_count];
1782
1783 /* set title */
1784 for (UINTN i = 0; i < config->entry_count; i++) {
1785 assert(!config->entries[i]->title_show);
1786 unique[i] = TRUE;
1787 config->entries[i]->title_show = xstrdup(config->entries[i]->title ?: config->entries[i]->id);
1788 }
1789
1790 if (entries_unique(config->entries, unique, config->entry_count))
1791 return;
1792
1793 /* add version to non-unique titles */
1794 for (UINTN i = 0; i < config->entry_count; i++) {
1795 if (unique[i])
1796 continue;
1797
1798 unique[i] = TRUE;
1799
1800 if (!config->entries[i]->version)
1801 continue;
1802
1803 _cleanup_freepool_ CHAR16 *t = config->entries[i]->title_show;
1804 config->entries[i]->title_show = xpool_print(L"%s (%s)", t, config->entries[i]->version);
1805 }
1806
1807 if (entries_unique(config->entries, unique, config->entry_count))
1808 return;
1809
1810 /* add machine-id to non-unique titles */
1811 for (UINTN i = 0; i < config->entry_count; i++) {
1812 if (unique[i])
1813 continue;
1814
1815 unique[i] = TRUE;
1816
1817 if (!config->entries[i]->machine_id)
1818 continue;
1819
1820 _cleanup_freepool_ CHAR16 *t = config->entries[i]->title_show;
1821 config->entries[i]->title_show = xpool_print(
1822 L"%s (%.*s)",
1823 t,
1824 StrnLen(config->entries[i]->machine_id, 8),
1825 config->entries[i]->machine_id);
1826 }
1827
1828 if (entries_unique(config->entries, unique, config->entry_count))
1829 return;
1830
1831 /* add file name to non-unique titles */
1832 for (UINTN i = 0; i < config->entry_count; i++) {
1833 if (unique[i])
1834 continue;
1835
1836 _cleanup_freepool_ CHAR16 *t = config->entries[i]->title_show;
1837 config->entries[i]->title_show = xpool_print(L"%s (%s)", t, config->entries[i]->id);
1838 }
1839 }
1840
1841 static BOOLEAN config_entry_add_call(
1842 Config *config,
1843 const CHAR16 *id,
1844 const CHAR16 *title,
1845 EFI_STATUS (*call)(void)) {
1846
1847 ConfigEntry *entry;
1848
1849 assert(config);
1850 assert(id);
1851 assert(title);
1852 assert(call);
1853
1854 entry = xnew(ConfigEntry, 1);
1855 *entry = (ConfigEntry) {
1856 .id = xstrdup(id),
1857 .title = xstrdup(title),
1858 .call = call,
1859 .tries_done = UINTN_MAX,
1860 .tries_left = UINTN_MAX,
1861 };
1862
1863 config_add_entry(config, entry);
1864 return TRUE;
1865 }
1866
1867 static ConfigEntry *config_entry_add_loader(
1868 Config *config,
1869 EFI_HANDLE *device,
1870 enum loader_type type,
1871 const CHAR16 *id,
1872 CHAR16 key,
1873 const CHAR16 *title,
1874 const CHAR16 *loader,
1875 const CHAR16 *sort_key,
1876 const CHAR16 *version) {
1877
1878 ConfigEntry *entry;
1879
1880 assert(config);
1881 assert(device);
1882 assert(id);
1883 assert(title);
1884 assert(loader);
1885
1886 entry = xnew(ConfigEntry, 1);
1887 *entry = (ConfigEntry) {
1888 .type = type,
1889 .title = xstrdup(title),
1890 .version = version ? xstrdup(version) : NULL,
1891 .device = device,
1892 .loader = xstrdup(loader),
1893 .id = xstrdup(id),
1894 .sort_key = xstrdup(sort_key),
1895 .key = key,
1896 .tries_done = UINTN_MAX,
1897 .tries_left = UINTN_MAX,
1898 };
1899
1900 StrLwr(entry->id);
1901
1902 config_add_entry(config, entry);
1903 return entry;
1904 }
1905
1906 static BOOLEAN is_sd_boot(EFI_FILE *root_dir, const CHAR16 *loader_path) {
1907 EFI_STATUS err;
1908 const CHAR8 *sections[] = {
1909 (CHAR8 *)".sdmagic",
1910 NULL
1911 };
1912 UINTN offset = 0, size = 0, read;
1913 _cleanup_freepool_ CHAR8 *content = NULL;
1914
1915 assert(root_dir);
1916 assert(loader_path);
1917
1918 err = pe_file_locate_sections(root_dir, loader_path, sections, &offset, &size);
1919 if (EFI_ERROR(err) || size != sizeof(magic))
1920 return FALSE;
1921
1922 err = file_read(root_dir, loader_path, offset, size, &content, &read);
1923 if (EFI_ERROR(err) || size != read)
1924 return FALSE;
1925
1926 return CompareMem(content, magic, sizeof(magic)) == 0;
1927 }
1928
1929 static ConfigEntry *config_entry_add_loader_auto(
1930 Config *config,
1931 EFI_HANDLE *device,
1932 EFI_FILE *root_dir,
1933 const CHAR16 *loaded_image_path,
1934 const CHAR16 *id,
1935 CHAR16 key,
1936 const CHAR16 *title,
1937 const CHAR16 *loader) {
1938
1939 assert(config);
1940 assert(device);
1941 assert(root_dir);
1942 assert(id);
1943 assert(title);
1944 assert(loader || loaded_image_path);
1945
1946 if (!config->auto_entries)
1947 return NULL;
1948
1949 if (loaded_image_path) {
1950 loader = L"\\EFI\\BOOT\\BOOT" EFI_MACHINE_TYPE_NAME ".efi";
1951
1952 /* We are trying to add the default EFI loader here,
1953 * but we do not want to do that if that would be us.
1954 *
1955 * If the default loader is not us, it might be shim. It would
1956 * chainload GRUBX64.EFI in that case, which might be us.*/
1957 if (StriCmp(loader, loaded_image_path) == 0 ||
1958 is_sd_boot(root_dir, loader) ||
1959 is_sd_boot(root_dir, L"\\EFI\\BOOT\\GRUB" EFI_MACHINE_TYPE_NAME L".EFI"))
1960 return NULL;
1961 }
1962
1963 /* check existence */
1964 _cleanup_(file_closep) EFI_FILE *handle = NULL;
1965 EFI_STATUS err = root_dir->Open(root_dir, &handle, (CHAR16*) loader, EFI_FILE_MODE_READ, 0ULL);
1966 if (EFI_ERROR(err))
1967 return NULL;
1968
1969 return config_entry_add_loader(config, device, LOADER_AUTO, id, key, title, loader, NULL, NULL);
1970 }
1971
1972 static void config_entry_add_osx(Config *config) {
1973 EFI_STATUS err;
1974 UINTN n_handles = 0;
1975 _cleanup_freepool_ EFI_HANDLE *handles = NULL;
1976
1977 assert(config);
1978
1979 if (!config->auto_entries)
1980 return;
1981
1982 err = LibLocateHandle(ByProtocol, &FileSystemProtocol, NULL, &n_handles, &handles);
1983 if (EFI_ERROR(err))
1984 return;
1985
1986 for (UINTN i = 0; i < n_handles; i++) {
1987 _cleanup_(file_closep) EFI_FILE *root = LibOpenRoot(handles[i]);
1988 if (!root)
1989 continue;
1990
1991 if (config_entry_add_loader_auto(
1992 config,
1993 handles[i],
1994 root,
1995 NULL,
1996 L"auto-osx",
1997 'a',
1998 L"macOS",
1999 L"\\System\\Library\\CoreServices\\boot.efi"))
2000 break;
2001 }
2002 }
2003
2004 static EFI_STATUS boot_windows_bitlocker(void) {
2005 _cleanup_freepool_ EFI_HANDLE *handles = NULL;
2006 UINTN n_handles;
2007 EFI_STATUS err;
2008
2009 // FIXME: Experimental for now. Should be generalized, and become a per-entry option that can be
2010 // enabled independently of BitLocker, and without a BootXXXX entry pre-existing.
2011
2012 /* BitLocker key cannot be sealed without a TPM present. */
2013 if (!tpm_present())
2014 return EFI_NOT_FOUND;
2015
2016 err = BS->LocateHandleBuffer(ByProtocol, &BlockIoProtocol, NULL, &n_handles, &handles);
2017 if (EFI_ERROR(err))
2018 return err;
2019
2020 /* Look for BitLocker magic string on all block drives. */
2021 BOOLEAN found = FALSE;
2022 for (UINTN i = 0; i < n_handles; i++) {
2023 EFI_BLOCK_IO *block_io;
2024 err = BS->HandleProtocol(handles[i], &BlockIoProtocol, (void **) &block_io);
2025 if (EFI_ERROR(err) || block_io->Media->BlockSize < 512)
2026 continue;
2027
2028 CHAR8 buf[block_io->Media->BlockSize];
2029 err = block_io->ReadBlocks(block_io, block_io->Media->MediaId, 0, sizeof(buf), buf);
2030 if (EFI_ERROR(err))
2031 continue;
2032
2033 if (CompareMem(buf + 3, "-FVE-FS-", STRLEN("-FVE-FS-")) == 0) {
2034 found = TRUE;
2035 break;
2036 }
2037 }
2038
2039 /* If no BitLocker drive was found, we can just chainload bootmgfw.efi directly. */
2040 if (!found)
2041 return EFI_NOT_FOUND;
2042
2043 _cleanup_freepool_ UINT16 *boot_order = NULL;
2044 UINTN boot_order_size;
2045
2046 /* There can be gaps in Boot#### entries. Instead of iterating over the full
2047 * EFI var list or UINT16 namespace, just look for "Windows Boot Manager" in BootOrder. */
2048 err = efivar_get_raw(EFI_GLOBAL_GUID, L"BootOrder", (CHAR8 **) &boot_order, &boot_order_size);
2049 if (EFI_ERROR(err) || boot_order_size % sizeof(UINT16) != 0)
2050 return err;
2051
2052 for (UINTN i = 0; i < boot_order_size / sizeof(UINT16); i++) {
2053 _cleanup_freepool_ CHAR8 *buf = NULL;
2054 CHAR16 name[sizeof(L"Boot0000")];
2055 UINTN buf_size;
2056
2057 SPrint(name, sizeof(name), L"Boot%04x", boot_order[i]);
2058 err = efivar_get_raw(EFI_GLOBAL_GUID, name, &buf, &buf_size);
2059 if (EFI_ERROR(err))
2060 continue;
2061
2062 /* Boot#### are EFI_LOAD_OPTION. But we really are only interested
2063 * for the description, which is at this offset. */
2064 UINTN offset = sizeof(UINT32) + sizeof(UINT16);
2065 if (buf_size < offset + sizeof(CHAR16))
2066 continue;
2067
2068 if (streq((CHAR16 *) (buf + offset), L"Windows Boot Manager")) {
2069 err = efivar_set_raw(
2070 EFI_GLOBAL_GUID,
2071 L"BootNext",
2072 boot_order + i,
2073 sizeof(boot_order[i]),
2074 EFI_VARIABLE_NON_VOLATILE);
2075 if (EFI_ERROR(err))
2076 return err;
2077 return RT->ResetSystem(EfiResetWarm, EFI_SUCCESS, 0, NULL);
2078 }
2079 }
2080
2081 return EFI_NOT_FOUND;
2082 }
2083
2084 static void config_entry_add_windows(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir) {
2085 #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
2086 _cleanup_freepool_ CHAR8 *bcd = NULL;
2087 CHAR16 *title = NULL;
2088 EFI_STATUS err;
2089 UINTN len;
2090
2091 assert(config);
2092 assert(device);
2093 assert(root_dir);
2094
2095 if (!config->auto_entries)
2096 return;
2097
2098 /* Try to find a better title. */
2099 err = file_read(root_dir, L"\\EFI\\Microsoft\\Boot\\BCD", 0, 100*1024, &bcd, &len);
2100 if (!EFI_ERROR(err))
2101 title = get_bcd_title((UINT8 *) bcd, len);
2102
2103 ConfigEntry *e = config_entry_add_loader_auto(config, device, root_dir, NULL,
2104 L"auto-windows", 'w', title ?: L"Windows Boot Manager",
2105 L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
2106
2107 if (config->reboot_for_bitlocker)
2108 e->call = boot_windows_bitlocker;
2109 #endif
2110 }
2111
2112 static void config_entry_add_linux(
2113 Config *config,
2114 EFI_HANDLE *device,
2115 EFI_FILE *root_dir) {
2116
2117 _cleanup_(file_closep) EFI_FILE *linux_dir = NULL;
2118 _cleanup_freepool_ EFI_FILE_INFO *f = NULL;
2119 ConfigEntry *entry;
2120 UINTN f_size = 0;
2121 EFI_STATUS err;
2122
2123 /* Adds Boot Loader Type #2 entries (i.e. /EFI/Linux/….efi) */
2124
2125 assert(config);
2126 assert(device);
2127 assert(root_dir);
2128
2129 err = open_directory(root_dir, L"\\EFI\\Linux", &linux_dir);
2130 if (EFI_ERROR(err))
2131 return;
2132
2133 for (;;) {
2134 enum {
2135 SECTION_CMDLINE,
2136 SECTION_OSREL,
2137 _SECTION_MAX,
2138 };
2139
2140 static const CHAR8* const sections[_SECTION_MAX + 1] = {
2141 [SECTION_CMDLINE] = (const CHAR8 *) ".cmdline",
2142 [SECTION_OSREL] = (const CHAR8 *) ".osrel",
2143 NULL,
2144 };
2145
2146 _cleanup_freepool_ CHAR16 *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
2147 *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL,
2148 *path = NULL;
2149 const CHAR16 *good_name, *good_version, *good_sort_key;
2150 _cleanup_freepool_ CHAR8 *content = NULL;
2151 UINTN offs[_SECTION_MAX] = {};
2152 UINTN szs[_SECTION_MAX] = {};
2153 CHAR8 *line;
2154 UINTN pos = 0;
2155 CHAR8 *key, *value;
2156
2157 err = readdir_harder(linux_dir, &f, &f_size);
2158 if (EFI_ERROR(err) || !f)
2159 break;
2160
2161 if (f->FileName[0] == '.')
2162 continue;
2163 if (FLAGS_SET(f->Attribute, EFI_FILE_DIRECTORY))
2164 continue;
2165 if (!endswith_no_case(f->FileName, L".efi"))
2166 continue;
2167 if (startswith(f->FileName, L"auto-"))
2168 continue;
2169
2170 /* look for .osrel and .cmdline sections in the .efi binary */
2171 err = pe_file_locate_sections(linux_dir, f->FileName, (const CHAR8**) sections, offs, szs);
2172 if (EFI_ERROR(err) || szs[SECTION_OSREL] == 0)
2173 continue;
2174
2175 err = file_read(linux_dir, f->FileName, offs[SECTION_OSREL], szs[SECTION_OSREL], &content, NULL);
2176 if (EFI_ERROR(err))
2177 continue;
2178
2179 /* read properties from the embedded os-release file */
2180 while ((line = line_get_key_value(content, (CHAR8 *)"=", &pos, &key, &value))) {
2181 if (strcmpa((const CHAR8*) "PRETTY_NAME", key) == 0) {
2182 FreePool(os_pretty_name);
2183 os_pretty_name = xstra_to_str(value);
2184 continue;
2185 }
2186
2187 if (strcmpa((const CHAR8*) "IMAGE_ID", key) == 0) {
2188 FreePool(os_image_id);
2189 os_image_id = xstra_to_str(value);
2190 continue;
2191 }
2192
2193 if (strcmpa((const CHAR8*) "NAME", key) == 0) {
2194 FreePool(os_name);
2195 os_name = xstra_to_str(value);
2196 continue;
2197 }
2198
2199 if (strcmpa((const CHAR8*) "ID", key) == 0) {
2200 FreePool(os_id);
2201 os_id = xstra_to_str(value);
2202 continue;
2203 }
2204
2205 if (strcmpa((const CHAR8*) "IMAGE_VERSION", key) == 0) {
2206 FreePool(os_image_version);
2207 os_image_version = xstra_to_str(value);
2208 continue;
2209 }
2210
2211 if (strcmpa((const CHAR8*) "VERSION", key) == 0) {
2212 FreePool(os_version);
2213 os_version = xstra_to_str(value);
2214 continue;
2215 }
2216
2217 if (strcmpa((const CHAR8*) "VERSION_ID", key) == 0) {
2218 FreePool(os_version_id);
2219 os_version_id = xstra_to_str(value);
2220 continue;
2221 }
2222
2223 if (strcmpa((const CHAR8*) "BUILD_ID", key) == 0) {
2224 FreePool(os_build_id);
2225 os_build_id = xstra_to_str(value);
2226 continue;
2227 }
2228 }
2229
2230 if (!bootspec_pick_name_version_sort_key(
2231 os_pretty_name,
2232 os_image_id,
2233 os_name,
2234 os_id,
2235 os_image_version,
2236 os_version,
2237 os_version_id,
2238 os_build_id,
2239 &good_name,
2240 &good_version,
2241 &good_sort_key))
2242 continue;
2243
2244 path = xpool_print(L"\\EFI\\Linux\\%s", f->FileName);
2245 entry = config_entry_add_loader(
2246 config,
2247 device,
2248 LOADER_UNIFIED_LINUX,
2249 /* id= */ f->FileName,
2250 /* key= */ 'l',
2251 /* title= */ good_name,
2252 /* loader= */ path,
2253 /* sort_key= */ good_sort_key,
2254 good_version);
2255
2256 config_entry_parse_tries(entry, L"\\EFI\\Linux", f->FileName, L".efi");
2257
2258 if (szs[SECTION_CMDLINE] == 0)
2259 continue;
2260
2261 content = mfree(content);
2262
2263 /* read the embedded cmdline file */
2264 err = file_read(linux_dir, f->FileName, offs[SECTION_CMDLINE], szs[SECTION_CMDLINE], &content, NULL);
2265 if (!EFI_ERROR(err)) {
2266 /* chomp the newline */
2267 if (content[szs[SECTION_CMDLINE] - 1] == '\n')
2268 content[szs[SECTION_CMDLINE] - 1] = '\0';
2269
2270 entry->options = xstra_to_str(content);
2271 }
2272 }
2273 }
2274
2275 static void config_load_xbootldr(
2276 Config *config,
2277 EFI_HANDLE *device) {
2278
2279 _cleanup_(file_closep) EFI_FILE *root_dir = NULL;
2280 EFI_HANDLE new_device;
2281 EFI_STATUS err;
2282
2283 assert(config);
2284 assert(device);
2285
2286 err = xbootldr_open(device, &new_device, &root_dir);
2287 if (EFI_ERROR(err))
2288 return;
2289
2290 config_entry_add_linux(config, new_device, root_dir);
2291 config_load_entries(config, new_device, root_dir, NULL);
2292 }
2293
2294 static EFI_STATUS image_start(
2295 EFI_FILE *root_dir,
2296 EFI_HANDLE parent_image,
2297 const Config *config,
2298 const ConfigEntry *entry) {
2299
2300 _cleanup_(devicetree_cleanup) struct devicetree_state dtstate = {};
2301 EFI_HANDLE image;
2302 _cleanup_freepool_ EFI_DEVICE_PATH *path = NULL;
2303 CHAR16 *options;
2304 EFI_STATUS err;
2305
2306 assert(config);
2307 assert(entry);
2308
2309 /* If this loader entry has a special way to boot, try that first. */
2310 if (entry->call)
2311 (void) entry->call();
2312
2313 path = FileDevicePath(entry->device, entry->loader);
2314 if (!path)
2315 return log_error_status_stall(EFI_INVALID_PARAMETER, L"Error getting device path.");
2316
2317 err = BS->LoadImage(FALSE, parent_image, path, NULL, 0, &image);
2318 if (EFI_ERROR(err))
2319 return log_error_status_stall(err, L"Error loading %s: %r", entry->loader, err);
2320
2321 if (entry->devicetree) {
2322 err = devicetree_install(&dtstate, root_dir, entry->devicetree);
2323 if (EFI_ERROR(err))
2324 return log_error_status_stall(err, L"Error loading %s: %r", entry->devicetree, err);
2325 }
2326
2327 if (config->options_edit)
2328 options = config->options_edit;
2329 else if (entry->options)
2330 options = entry->options;
2331 else
2332 options = NULL;
2333 if (options) {
2334 EFI_LOADED_IMAGE *loaded_image;
2335
2336 err = BS->OpenProtocol(image, &LoadedImageProtocol, (void **)&loaded_image,
2337 parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
2338 if (EFI_ERROR(err)) {
2339 log_error_stall(L"Error getting LoadedImageProtocol handle: %r", err);
2340 goto out_unload;
2341 }
2342 loaded_image->LoadOptions = options;
2343 loaded_image->LoadOptionsSize = StrSize(loaded_image->LoadOptions);
2344
2345 /* Try to log any options to the TPM, especially to catch manually edited options */
2346 (void) tpm_log_load_options(options);
2347 }
2348
2349 efivar_set_time_usec(LOADER_GUID, L"LoaderTimeExecUSec", 0);
2350 err = BS->StartImage(image, NULL, NULL);
2351 out_unload:
2352 BS->UnloadImage(image);
2353 return err;
2354 }
2355
2356 static void config_free(Config *config) {
2357 assert(config);
2358 for (UINTN i = 0; i < config->entry_count; i++)
2359 config_entry_free(config->entries[i]);
2360 FreePool(config->entries);
2361 FreePool(config->entry_default_config);
2362 FreePool(config->options_edit);
2363 FreePool(config->entry_oneshot);
2364 }
2365
2366 static void config_write_entries_to_variable(Config *config) {
2367 _cleanup_freepool_ CHAR8 *buffer = NULL;
2368 UINTN sz = 0;
2369 CHAR8 *p;
2370
2371 assert(config);
2372
2373 for (UINTN i = 0; i < config->entry_count; i++)
2374 sz += StrSize(config->entries[i]->id);
2375
2376 p = buffer = xallocate_pool(sz);
2377
2378 for (UINTN i = 0; i < config->entry_count; i++) {
2379 UINTN l;
2380
2381 l = StrSize(config->entries[i]->id);
2382 CopyMem(p, config->entries[i]->id, l);
2383
2384 p += l;
2385 }
2386
2387 assert(p == buffer + sz);
2388
2389 /* Store the full list of discovered entries. */
2390 (void) efivar_set_raw(LOADER_GUID, L"LoaderEntries", buffer, sz, 0);
2391 }
2392
2393 static void save_selected_entry(const Config *config, const ConfigEntry *entry) {
2394 assert(config);
2395 assert(entry);
2396 assert(entry->loader || !entry->call);
2397
2398 /* Always export the selected boot entry to the system in a volatile var. */
2399 (void) efivar_set(LOADER_GUID, L"LoaderEntrySelected", entry->id, 0);
2400
2401 /* Do not save or delete if this was a oneshot boot. */
2402 if (streq_ptr(config->entry_oneshot, entry->id))
2403 return;
2404
2405 if (config->use_saved_entry_efivar || (!config->entry_default_efivar && config->use_saved_entry)) {
2406 /* Avoid unnecessary NVRAM writes. */
2407 if (streq_ptr(config->entry_saved, entry->id))
2408 return;
2409
2410 (void) efivar_set(LOADER_GUID, L"LoaderEntryLastBooted", entry->id, EFI_VARIABLE_NON_VOLATILE);
2411 } else
2412 /* Delete the non-volatile var if not needed. */
2413 (void) efivar_set(LOADER_GUID, L"LoaderEntryLastBooted", NULL, EFI_VARIABLE_NON_VOLATILE);
2414 }
2415
2416 static void export_variables(
2417 EFI_LOADED_IMAGE *loaded_image,
2418 const CHAR16 *loaded_image_path,
2419 UINT64 init_usec) {
2420
2421 static const UINT64 loader_features =
2422 EFI_LOADER_FEATURE_CONFIG_TIMEOUT |
2423 EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT |
2424 EFI_LOADER_FEATURE_ENTRY_DEFAULT |
2425 EFI_LOADER_FEATURE_ENTRY_ONESHOT |
2426 EFI_LOADER_FEATURE_BOOT_COUNTING |
2427 EFI_LOADER_FEATURE_XBOOTLDR |
2428 EFI_LOADER_FEATURE_RANDOM_SEED |
2429 EFI_LOADER_FEATURE_LOAD_DRIVER |
2430 0;
2431
2432 _cleanup_freepool_ CHAR16 *infostr = NULL, *typestr = NULL;
2433 CHAR16 uuid[37];
2434
2435 assert(loaded_image);
2436 assert(loaded_image_path);
2437
2438 efivar_set_time_usec(LOADER_GUID, L"LoaderTimeInitUSec", init_usec);
2439 efivar_set(LOADER_GUID, L"LoaderInfo", L"systemd-boot " GIT_VERSION, 0);
2440
2441 infostr = xpool_print(L"%s %d.%02d", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
2442 efivar_set(LOADER_GUID, L"LoaderFirmwareInfo", infostr, 0);
2443
2444 typestr = xpool_print(L"UEFI %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
2445 efivar_set(LOADER_GUID, L"LoaderFirmwareType", typestr, 0);
2446
2447 (void) efivar_set_uint64_le(LOADER_GUID, L"LoaderFeatures", loader_features, 0);
2448
2449 /* the filesystem path to this image, to prevent adding ourselves to the menu */
2450 efivar_set(LOADER_GUID, L"LoaderImageIdentifier", loaded_image_path, 0);
2451
2452 /* export the device path this image is started from */
2453 if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS)
2454 efivar_set(LOADER_GUID, L"LoaderDevicePartUUID", uuid, 0);
2455 }
2456
2457 static void config_load_all_entries(
2458 Config *config,
2459 EFI_LOADED_IMAGE *loaded_image,
2460 const CHAR16 *loaded_image_path,
2461 EFI_FILE *root_dir) {
2462
2463 assert(config);
2464 assert(loaded_image);
2465 assert(loaded_image_path);
2466 assert(root_dir);
2467
2468 config_load_defaults(config, root_dir);
2469
2470 /* scan /EFI/Linux/ directory */
2471 config_entry_add_linux(config, loaded_image->DeviceHandle, root_dir);
2472
2473 /* scan /loader/entries/\*.conf files */
2474 config_load_entries(config, loaded_image->DeviceHandle, root_dir, loaded_image_path);
2475
2476 /* Similar, but on any XBOOTLDR partition */
2477 config_load_xbootldr(config, loaded_image->DeviceHandle);
2478
2479 /* sort entries after version number */
2480 sort_pointer_array((void **) config->entries, config->entry_count, (compare_pointer_func_t) config_entry_compare);
2481
2482 /* if we find some well-known loaders, add them to the end of the list */
2483 config_entry_add_osx(config);
2484 config_entry_add_windows(config, loaded_image->DeviceHandle, root_dir);
2485 config_entry_add_loader_auto(config, loaded_image->DeviceHandle, root_dir, NULL,
2486 L"auto-efi-shell", 's', L"EFI Shell", L"\\shell" EFI_MACHINE_TYPE_NAME ".efi");
2487 config_entry_add_loader_auto(config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
2488 L"auto-efi-default", '\0', L"EFI Default Loader", NULL);
2489
2490 if (config->auto_firmware && FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI))
2491 config_entry_add_call(config,
2492 L"auto-reboot-to-firmware-setup",
2493 L"Reboot Into Firmware Interface",
2494 reboot_into_firmware);
2495
2496 if (config->entry_count == 0)
2497 return;
2498
2499 config_write_entries_to_variable(config);
2500
2501 config_title_generate(config);
2502
2503 /* select entry by configured pattern or EFI LoaderDefaultEntry= variable */
2504 config_default_entry_select(config);
2505 }
2506
2507 EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
2508 EFI_LOADED_IMAGE *loaded_image;
2509 _cleanup_(file_closep) EFI_FILE *root_dir = NULL;
2510 _cleanup_(config_free) Config config = {};
2511 CHAR16 *loaded_image_path;
2512 EFI_STATUS err;
2513 UINT64 init_usec;
2514 BOOLEAN menu = FALSE;
2515
2516 InitializeLib(image, sys_table);
2517 init_usec = time_usec();
2518 debug_hook(L"systemd-boot");
2519 /* Uncomment the next line if you need to wait for debugger. */
2520 // debug_break();
2521
2522 err = BS->OpenProtocol(image,
2523 &LoadedImageProtocol,
2524 (void **)&loaded_image,
2525 image,
2526 NULL,
2527 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
2528 if (EFI_ERROR(err))
2529 return log_error_status_stall(err, L"Error getting a LoadedImageProtocol handle: %r", err);
2530
2531 loaded_image_path = DevicePathToStr(loaded_image->FilePath);
2532 if (!loaded_image_path)
2533 return log_oom();
2534
2535 export_variables(loaded_image, loaded_image_path, init_usec);
2536
2537 root_dir = LibOpenRoot(loaded_image->DeviceHandle);
2538 if (!root_dir)
2539 return log_error_status_stall(EFI_LOAD_ERROR, L"Unable to open root directory.", EFI_LOAD_ERROR);
2540
2541 if (secure_boot_enabled() && shim_loaded()) {
2542 err = security_policy_install();
2543 if (EFI_ERROR(err))
2544 return log_error_status_stall(err, L"Error installing security policy: %r", err);
2545 }
2546
2547 (void) load_drivers(image, loaded_image, root_dir);
2548
2549 config_load_all_entries(&config, loaded_image, loaded_image_path, root_dir);
2550
2551 if (config.entry_count == 0) {
2552 log_error_stall(L"No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
2553 goto out;
2554 }
2555
2556 /* select entry or show menu when key is pressed or timeout is set */
2557 if (config.force_menu || config.timeout_sec > 0)
2558 menu = TRUE;
2559 else {
2560 UINT64 key;
2561
2562 /* Block up to 100ms to give firmware time to get input working. */
2563 err = console_key_read(&key, 100 * 1000);
2564 if (!EFI_ERROR(err)) {
2565 /* find matching key in config entries */
2566 UINTN idx = entry_lookup_key(&config, config.idx_default, KEYCHAR(key));
2567 if (idx != IDX_INVALID)
2568 config.idx_default = idx;
2569 else
2570 menu = TRUE;
2571 }
2572 }
2573
2574 for (;;) {
2575 ConfigEntry *entry;
2576
2577 entry = config.entries[config.idx_default];
2578 if (menu) {
2579 efivar_set_time_usec(LOADER_GUID, L"LoaderTimeMenuUSec", 0);
2580 if (!menu_run(&config, &entry, loaded_image_path))
2581 break;
2582 }
2583
2584 /* Run special entry like "reboot" now. Those that have a loader
2585 * will be handled by image_start() instead. */
2586 if (entry->call && !entry->loader) {
2587 entry->call();
2588 continue;
2589 }
2590
2591 config_entry_bump_counters(entry, root_dir);
2592 save_selected_entry(&config, entry);
2593
2594 /* Optionally, read a random seed off the ESP and pass it to the OS */
2595 (void) process_random_seed(root_dir, config.random_seed_mode);
2596
2597 err = image_start(root_dir, image, &config, entry);
2598 if (EFI_ERROR(err)) {
2599 graphics_mode(FALSE);
2600 log_error_stall(L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err);
2601 goto out;
2602 }
2603
2604 menu = TRUE;
2605 config.timeout_sec = 0;
2606 }
2607 err = EFI_SUCCESS;
2608 out:
2609 BS->CloseProtocol(image, &LoadedImageProtocol, image, NULL);
2610 return err;
2611 }