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