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