1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "bootspec-fundamental.h"
10 #include "devicetree.h"
13 #include "efivars-fundamental.h"
18 #include "random-seed.h"
19 #include "secure-boot.h"
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.
32 #define TEXT_ATTR_SWAP(c) EFI_TEXT_ATTR(((c) & 0b11110000) >> 4, (c) & 0b1111)
34 /* Magic string for recognizing our own binaries */
35 _used_
_section_(".sdmagic") static const char magic
[] =
36 "#### LoaderInfo: systemd-boot " GIT_VERSION
" ####";
38 /* Makes systemd-boot available from \EFI\Linux\ for testing purposes. */
39 _used_
_section_(".osrel") static const char osrel
[] =
41 "VERSION=\"" GIT_VERSION
"\"\n"
42 "NAME=\"systemd-boot " GIT_VERSION
"\"\n";
48 LOADER_LINUX
, /* Boot loader spec type #1 entries */
49 LOADER_UNIFIED_LINUX
, /* Boot loader spec type #2 entries */
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 */
60 enum loader_type type
;
65 EFI_STATUS (*call
)(void);
74 ConfigEntry
**entries
;
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
;
88 BOOLEAN auto_firmware
;
89 BOOLEAN reboot_for_bitlocker
;
91 BOOLEAN use_saved_entry
;
92 BOOLEAN use_saved_entry_efivar
;
95 INT64 console_mode_efivar
;
96 RandomSeedMode random_seed_mode
;
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 ↔ … */
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
,
116 static void cursor_left(UINTN
*cursor
, UINTN
*first
) {
122 else if ((*first
) > 0)
126 static void cursor_right(
135 if ((*cursor
)+1 < x_max
)
137 else if ((*first
) + (*cursor
) < len
)
141 static BOOLEAN
line_edit(
142 const CHAR16
*line_in
,
147 _cleanup_freepool_ CHAR16
*line
= NULL
, *print
= NULL
;
148 UINTN size
, len
, first
= 0, cursor
= 0, clear
= 0;
155 len
= StrLen(line_in
);
157 line
= xnew(CHAR16
, size
);
158 print
= xnew(CHAR16
, x_max
+ 1);
159 StrCpy(line
, line_in
);
165 UINTN cursor_color
= TEXT_ATTR_SWAP(COLOR_EDIT
);
167 j
= MIN(len
- first
, x_max
);
168 CopyMem(print
, line
+ first
, j
* sizeof(CHAR16
));
169 while (clear
> 0 && j
< x_max
) {
175 /* See comment at edit_line() call site for why we start at 1. */
176 print_at(1, y_pos
, COLOR_EDIT
, print
);
180 print
[cursor
+1] = '\0';
182 print_at(cursor
+ 1, y_pos
, cursor_color
, print
+ cursor
);
183 cursor_color
= TEXT_ATTR_SWAP(cursor_color
);
185 err
= console_key_read(&key
, 750 * 1000);
186 if (!IN_SET(err
, EFI_SUCCESS
, EFI_TIMEOUT
, EFI_NOT_READY
))
189 print_at(cursor
+ 1, y_pos
, COLOR_EDIT
, print
+ cursor
);
190 } while (EFI_ERROR(err
));
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')):
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 */
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')):
212 cursor
= len
- first
;
213 if (cursor
+1 >= x_max
) {
215 first
= len
- (x_max
-1);
219 case KEYPRESS(0, SCAN_DOWN
, 0):
220 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'f'):
221 case KEYPRESS(EFI_CONTROL_PRESSED
, SCAN_RIGHT
, 0):
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
);
229 case KEYPRESS(0, SCAN_UP
, 0):
230 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'b'):
231 case KEYPRESS(EFI_CONTROL_PRESSED
, SCAN_LEFT
, 0):
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
);
238 while ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] != ' ')
239 cursor_left(&cursor
, &first
);
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')):
246 if (first
+ cursor
== len
)
248 cursor_right(&cursor
, &first
, x_max
, len
);
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')):
255 cursor_left(&cursor
, &first
);
258 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'd'):
263 for (k
= first
+ cursor
; k
< len
&& line
[k
] == ' '; k
++)
265 for (; k
< len
&& line
[k
] != ' '; k
++)
268 for (UINTN i
= first
+ cursor
; i
+ clear
< len
; i
++)
269 line
[i
] = line
[i
+ clear
];
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 */
279 if ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] == ' ') {
280 cursor_left(&cursor
, &first
);
282 while ((first
+ cursor
) > 0 && line
[first
+ cursor
] == ' ') {
283 cursor_left(&cursor
, &first
);
287 while ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] != ' ') {
288 cursor_left(&cursor
, &first
);
292 for (UINTN i
= first
+ cursor
; i
+ clear
< len
; i
++)
293 line
[i
] = line
[i
+ clear
];
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')):
303 if (first
+ cursor
== len
)
305 for (UINTN i
= first
+ cursor
; i
< len
; i
++)
311 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'k'):
312 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('k')):
314 line
[first
+ cursor
] = '\0';
315 clear
= len
- (first
+ cursor
);
316 len
= first
+ cursor
;
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
);
327 case KEYPRESS(0, 0, CHAR_BACKSPACE
):
330 if (first
== 0 && cursor
== 0)
332 for (UINTN i
= first
+ cursor
-1; i
< len
; i
++)
338 if (cursor
> 0 || first
== 0)
340 /* show full line if it fits */
346 /* jump left to see what we delete */
356 case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
357 case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
360 for (UINTN i
= len
; i
> first
+ cursor
; i
--)
362 line
[first
+ cursor
] = KEYCHAR(key
);
365 if (cursor
+1 < x_max
)
367 else if (first
+ cursor
< len
)
374 static UINTN
entry_lookup_key(Config
*config
, UINTN start
, CHAR16 key
) {
380 /* select entry by number key */
381 if (key
>= '1' && key
<= '9') {
383 if (i
> config
->entry_count
)
384 i
= config
->entry_count
;
388 /* find matching key in config entries */
389 for (UINTN i
= start
; i
< config
->entry_count
; i
++)
390 if (config
->entries
[i
]->key
== key
)
393 for (UINTN i
= 0; i
< start
; i
++)
394 if (config
->entries
[i
]->key
== key
)
400 static CHAR16
*update_timeout_efivar(UINT32
*t
, BOOLEAN inc
) {
405 *t
= inc
? TIMEOUT_MAX
: (*t
- 1);
408 *t
= inc
? TIMEOUT_MENU_FORCE
: TIMEOUT_UNSET
;
410 case TIMEOUT_MENU_FORCE
:
411 *t
= inc
? TIMEOUT_MENU_HIDDEN
: TIMEOUT_UNSET
;
413 case TIMEOUT_MENU_HIDDEN
:
414 *t
= inc
? TIMEOUT_MIN
: TIMEOUT_MENU_FORCE
;
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.");
428 return xpool_print(L
"Menu timeout set to %u s.", *t
);
432 static BOOLEAN
unicode_supported(void) {
433 static INTN cache
= -1;
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
"─"));
443 static void ps_string(const CHAR16
*fmt
, const void *value
) {
449 static void ps_bool(const CHAR16
*fmt
, BOOLEAN value
) {
451 Print(fmt
, yes_no(value
));
454 static BOOLEAN
ps_continue(void) {
458 if (unicode_supported())
459 Print(L
"\n─── Press any key to continue, ESC or q to quit. ───\n\n");
461 Print(L
"\n--- Press any key to continue, ESC or q to quit. ---\n\n");
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'));
467 static void print_status(Config
*config
, CHAR16
*loaded_image_path
) {
469 UINT32 screen_width
= 0, screen_height
= 0;
470 SecureBootMode secure
;
471 _cleanup_freepool_ CHAR16
*device_part_uuid
= NULL
;
474 assert(loaded_image_path
);
476 clear_screen(COLOR_NORMAL
);
477 console_query_mode(&x_max
, &y_max
);
478 query_screen_resolution(&screen_width
, &screen_height
);
480 secure
= secure_boot_mode();
481 (void) efivar_get(LOADER_GUID
, L
"LoaderDevicePartUUID", &device_part_uuid
);
483 /* We employ some unusual indentation here for readability. */
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
);
501 switch (config
->timeout_sec_config
) {
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;
509 Print(L
" timeout: %lu s\n", config
->timeout_sec_config
);
512 switch (config
->timeout_sec_efivar
) {
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;
520 Print(L
" timeout (EFI var): %lu s\n", config
->timeout_sec_efivar
);
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
]);
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;
542 Print(L
" console-mode: %ld\n", config
->console_mode
); break;
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
);
552 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
553 ConfigEntry
*entry
= config
->entries
[i
];
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
);
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
);
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
);
581 static EFI_STATUS
reboot_into_firmware(void) {
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.");
588 (void) efivar_get_uint64_le(EFI_GLOBAL_GUID
, L
"OsIndications", &osind
);
589 osind
|= EFI_OS_INDICATIONS_BOOT_TO_FW_UI
;
591 err
= efivar_set_uint64_le(EFI_GLOBAL_GUID
, L
"OsIndications", osind
, EFI_VARIABLE_NON_VOLATILE
);
593 return log_error_status_stall(err
, L
"Error setting OsIndications: %r", err
);
595 err
= RT
->ResetSystem(EfiResetCold
, EFI_SUCCESS
, 0, NULL
);
596 return log_error_status_stall(err
, L
"Error calling ResetSystem: %r", err
);
599 static BOOLEAN
menu_run(
601 ConfigEntry
**chosen_entry
,
602 CHAR16
*loaded_image_path
) {
605 assert(chosen_entry
);
606 assert(loaded_image_path
);
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;
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
;
625 graphics_mode(FALSE
);
626 ST
->ConIn
->Reset(ST
->ConIn
, FALSE
);
627 ST
->ConOut
->EnableCursor(ST
->ConOut
, FALSE
);
629 /* draw a single character to make ClearScreen work on some firmware */
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
);
639 UINTN line_width
= 0, entry_padding
= 3;
644 console_query_mode(&x_max
, &y_max
);
646 /* account for padding+status */
647 visible_max
= y_max
- 2;
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
653 if (config
->entry_count
<= visible_max
|| idx_highlight
<= visible_max
/ 2)
655 else if (idx_highlight
>= config
->entry_count
- (visible_max
/ 2))
656 idx_first
= config
->entry_count
- visible_max
;
658 idx_first
= idx_highlight
- (visible_max
/ 2);
659 idx_last
= idx_first
+ visible_max
- 1;
661 /* length of the longest entry */
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
);
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;
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);
677 lines
= strv_free(lines
);
678 clearline
= mfree(clearline
);
679 separator
= mfree(separator
);
681 /* menu entries title lines */
682 lines
= xnew(CHAR16
*, config
->entry_count
+ 1);
684 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
687 lines
[i
] = xnew(CHAR16
, line_width
+ 1);
688 padding
= (line_width
- MIN(StrLen(config
->entries
[i
]->title_show
), line_width
)) / 2;
690 for (j
= 0; j
< padding
; j
++)
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
];
696 for (; j
< line_width
; j
++)
698 lines
[i
][line_width
] = '\0';
700 lines
[config
->entry_count
] = NULL
;
702 clearline
= xnew(CHAR16
, x_max
+ 1);
703 separator
= xnew(CHAR16
, x_max
+ 1);
704 for (UINTN i
= 0; i
< x_max
; i
++) {
706 separator
[i
] = unicode_supported() ? L
'─' : L
'-';
708 clearline
[x_max
] = 0;
709 separator
[x_max
] = 0;
716 clear_screen(COLOR_NORMAL
);
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
,
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
"=>"));
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
"=>"));
742 if (timeout_remain
> 0) {
744 status
= xpool_print(L
"Boot in %u s.", timeout_remain
);
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;
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
);
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
);
763 print_at(0, y_status
- 1, COLOR_NORMAL
, clearline
);
764 print_at(0, y_status
, COLOR_NORMAL
, clearline
+ 1); /* See comment above. */
767 /* Beep several times so that the selected entry can be distinguished. */
769 beep(idx_highlight
+ 1);
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. */
776 if (err
== EFI_TIMEOUT
) {
777 assert(timeout_remain
> 0);
779 if (timeout_remain
== 0) {
787 if (EFI_ERROR(err
)) {
794 /* clear status after keystroke */
795 status
= mfree(status
);
797 idx_highlight_prev
= idx_highlight
;
799 if (firmware_setup
) {
800 firmware_setup
= FALSE
;
801 if (key
== KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN
))
802 reboot_into_firmware();
807 case KEYPRESS(0, SCAN_UP
, 0):
808 case KEYPRESS(0, 0, 'k'):
809 case KEYPRESS(0, 0, 'K'):
810 if (idx_highlight
> 0)
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)
821 case KEYPRESS(0, SCAN_HOME
, 0):
822 case KEYPRESS(EFI_ALT_PRESSED
, 0, '<'):
823 if (idx_highlight
> 0) {
829 case KEYPRESS(0, SCAN_END
, 0):
830 case KEYPRESS(EFI_ALT_PRESSED
, 0, '>'):
831 if (idx_highlight
< config
->entry_count
-1) {
833 idx_highlight
= config
->entry_count
-1;
837 case KEYPRESS(0, SCAN_PAGE_UP
, 0):
838 if (idx_highlight
> visible_max
)
839 idx_highlight
-= visible_max
;
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;
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):
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");
866 case KEYPRESS(0, 0, 'Q'):
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.");
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.");
883 config
->use_saved_entry_efivar
= FALSE
;
887 case KEYPRESS(0, 0, '-'):
888 case KEYPRESS(0, 0, 'T'):
889 status
= update_timeout_efivar(&config
->timeout_sec_efivar
, FALSE
);
892 case KEYPRESS(0, 0, '+'):
893 case KEYPRESS(0, 0, 't'):
894 status
= update_timeout_efivar(&config
->timeout_sec_efivar
, TRUE
);
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
))
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
)
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);
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);
929 case KEYPRESS(0, 0, 'p'):
930 case KEYPRESS(0, 0, 'P'):
931 print_status(config
, loaded_image_path
);
935 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'l'):
936 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('l')):
940 case KEYPRESS(0, 0, 'r'):
941 err
= console_set_mode(CONSOLE_MODE_NEXT
);
943 status
= xpool_print(L
"Error changing console mode: %r", err
);
945 config
->console_mode_efivar
= ST
->ConOut
->Mode
->Mode
;
946 status
= xpool_print(L
"Console mode changed to %ld.", config
->console_mode_efivar
);
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
);
956 status
= xpool_print(L
"Error resetting console mode: %r", err
);
958 status
= xpool_print(L
"Console mode reset to %s default.",
959 config
->console_mode
== CONSOLE_MODE_KEEP
? L
"firmware" : L
"configuration file");
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.");
974 status
= xpool_print(L
"Reboot into firmware interface not supported.");
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
)
986 if (idx_highlight
> idx_last
) {
987 idx_last
= idx_highlight
;
988 idx_first
= 1 + idx_highlight
- visible_max
;
990 } else if (idx_highlight
< idx_first
) {
991 idx_first
= idx_highlight
;
992 idx_last
= idx_highlight
+ visible_max
-1;
996 if (!refresh
&& idx_highlight
!= idx_highlight_prev
)
1000 *chosen_entry
= config
->entries
[idx_highlight
];
1002 /* Update EFI vars after we left the menu to reduce NVRAM writes. */
1004 if (default_efivar_saved
!= config
->idx_default_efivar
)
1005 efivar_set(LOADER_GUID
, L
"LoaderEntryDefault", config
->entry_default_efivar
, EFI_VARIABLE_NON_VOLATILE
);
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
);
1011 efivar_set_uint_string(LOADER_GUID
, L
"LoaderConfigConsoleMode",
1012 config
->console_mode_efivar
, EFI_VARIABLE_NON_VOLATILE
);
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
);
1019 efivar_set_uint_string(LOADER_GUID
, L
"LoaderConfigTimeout",
1020 config
->timeout_sec_efivar
, EFI_VARIABLE_NON_VOLATILE
);
1023 clear_screen(COLOR_NORMAL
);
1027 static void config_add_entry(Config
*config
, ConfigEntry
*entry
) {
1031 /* This is just for paranoia. */
1032 assert(config
->entry_count
< IDX_MAX
);
1034 if ((config
->entry_count
& 15) == 0) {
1035 UINTN i
= config
->entry_count
+ 16;
1036 config
->entries
= xreallocate_pool(
1038 sizeof(void *) * config
->entry_count
,
1039 sizeof(void *) * i
);
1041 config
->entries
[config
->entry_count
++] = entry
;
1044 static void config_entry_free(ConfigEntry
*entry
) {
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
);
1063 static inline void config_entry_freep(ConfigEntry
**entry
) {
1064 config_entry_free(*entry
);
1067 static CHAR8
*line_get_key_value(
1072 CHAR8
**value_ret
) {
1074 CHAR8
*line
, *value
;
1084 line
= content
+ *pos
;
1089 while (line
[linelen
] && !strchra((CHAR8
*) "\n\r", line
[linelen
]))
1092 /* move pos to next line */
1101 /* terminate line */
1102 line
[linelen
] = '\0';
1104 /* remove leading whitespace */
1105 while (strchra((CHAR8
*) " \t", *line
)) {
1110 /* remove trailing whitespace */
1111 while (linelen
> 0 && strchra((CHAR8
*) " \t", line
[linelen
- 1]))
1113 line
[linelen
] = '\0';
1118 /* split key/value */
1120 while (*value
&& !strchra(sep
, *value
))
1126 while (*value
&& strchra(sep
, *value
))
1130 if (value
[0] == '"' && line
[linelen
- 1] == '"') {
1132 line
[linelen
- 1] = '\0';
1141 static void config_defaults_load_from_file(Config
*config
, CHAR8
*content
) {
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
;
1157 _cleanup_freepool_ CHAR16
*s
= NULL
;
1159 s
= xstra_to_str(value
);
1160 config
->timeout_sec_config
= MIN(Atoi(s
), TIMEOUT_TYPE_MAX
);
1162 config
->timeout_sec
= config
->timeout_sec_config
;
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
);
1171 FreePool(config
->entry_default_config
);
1172 config
->entry_default_config
= xstra_to_str(value
);
1176 if (strcmpa((CHAR8
*)"editor", key
) == 0) {
1177 err
= parse_boolean(value
, &config
->editor
);
1179 log_error_stall(L
"Error parsing 'editor' config option: %a", value
);
1183 if (strcmpa((CHAR8
*)"auto-entries", key
) == 0) {
1184 err
= parse_boolean(value
, &config
->auto_entries
);
1186 log_error_stall(L
"Error parsing 'auto-entries' config option: %a", value
);
1190 if (strcmpa((CHAR8
*)"auto-firmware", key
) == 0) {
1191 err
= parse_boolean(value
, &config
->auto_firmware
);
1193 log_error_stall(L
"Error parsing 'auto-firmware' config option: %a", value
);
1197 if (strcmpa((CHAR8
*)"beep", key
) == 0) {
1198 err
= parse_boolean(value
, &config
->beep
);
1200 log_error_stall(L
"Error parsing 'beep' config option: %a", value
);
1203 if (strcmpa((CHAR8
*)"reboot-for-bitlocker", key
) == 0) {
1204 err
= parse_boolean(value
, &config
->reboot_for_bitlocker
);
1206 log_error_stall(L
"Error parsing 'reboot-for-bitlocker' config option: %a", value
);
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
;
1218 _cleanup_freepool_ CHAR16
*s
= NULL
;
1220 s
= xstra_to_str(value
);
1221 config
->console_mode
= MIN(Atoi(s
), (UINTN
)CONSOLE_MODE_RANGE_MAX
);
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
;
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
);
1243 config
->random_seed_mode
= on
? RANDOM_SEED_ALWAYS
: RANDOM_SEED_OFF
;
1249 static void config_entry_parse_tries(
1253 const CHAR16
*suffix
) {
1255 UINTN left
= UINTN_MAX
, done
= UINTN_MAX
, factor
= 1, i
, next_left
, next_done
;
1256 _cleanup_freepool_ CHAR16
*prefix
= NULL
;
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").
1267 * Names we grok, and the series they result in:
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!
1275 /* Chop off any suffix such as ".conf" or ".efi" */
1277 UINTN suffix_length
;
1279 suffix_length
= StrLen(suffix
);
1280 if (i
< suffix_length
)
1286 /* Go backwards through the string and parse everything we encounter */
1296 if (left
== UINTN_MAX
) /* didn't read at least one digit for 'left'? */
1299 if (done
== UINTN_MAX
) /* no 'done' counter? If so, it's equivalent to 0 */
1305 if (left
== UINTN_MAX
) /* didn't parse any digit yet? */
1308 if (done
!= UINTN_MAX
) /* already encountered a dash earlier? */
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. */
1322 if (left
== UINTN_MAX
)
1323 left
= file
[i
] - '0';
1325 UINTN new_left
, digit
;
1327 digit
= file
[i
] - '0';
1328 if (digit
> UINTN_MAX
/ factor
) /* overflow check */
1331 new_left
= left
+ digit
* factor
;
1332 if (new_left
< left
) /* overflow check */
1335 if (new_left
== UINTN_MAX
) /* don't allow us to be confused */
1339 new_factor
= factor
* 10;
1340 if (new_factor
< factor
) /* overflow check */
1343 factor
= new_factor
;
1353 entry
->tries_left
= left
;
1354 entry
->tries_done
= done
;
1356 entry
->path
= xstrdup(path
);
1357 entry
->current_name
= xstrdup(file
);
1359 next_left
= left
<= 0 ? 0 : left
- 1;
1360 next_done
= done
>= (UINTN
) -2 ? (UINTN
) -2 : done
+ 1;
1362 prefix
= xstrdup(file
);
1365 entry
->next_name
= xpool_print(L
"%s+%u-%u%s", prefix
, next_left
, next_done
, suffix
?: L
"");
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
;
1378 if (entry
->tries_left
== UINTN_MAX
)
1381 if (!entry
->path
|| !entry
->current_name
|| !entry
->next_name
)
1384 old_path
= xpool_print(L
"%s\\%s", entry
->path
, entry
->current_name
);
1386 err
= root_dir
->Open(root_dir
, &handle
, old_path
, EFI_FILE_MODE_READ
|EFI_FILE_MODE_WRITE
, 0ULL);
1390 err
= get_file_info_harder(handle
, &file_info
, &file_info_size
);
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
);
1402 /* Flush everything to disk, just in case… */
1403 (void) handle
->Flush(handle
);
1405 /* Let's tell the OS that we renamed this file, so that it knows what to rename to the counter-less name on
1407 new_path
= xpool_print(L
"%s\\%s", entry
->path
, entry
->next_name
);
1408 efivar_set(LOADER_GUID
, L
"LoaderBootCountPath", new_path
, 0);
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
);
1417 static void config_entry_add_from_file(
1424 const CHAR16
*loaded_image_path
) {
1426 _cleanup_(config_entry_freep
) ConfigEntry
*entry
= NULL
;
1431 _cleanup_freepool_ CHAR16
*initrd
= NULL
;
1440 entry
= xnew(ConfigEntry
, 1);
1441 *entry
= (ConfigEntry
) {
1442 .tries_done
= UINTN_MAX
,
1443 .tries_left
= UINTN_MAX
,
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
);
1453 if (strcmpa((CHAR8
*)"sort-key", key
) == 0) {
1454 FreePool(entry
->sort_key
);
1455 entry
->sort_key
= xstra_to_str(value
);
1459 if (strcmpa((CHAR8
*)"version", key
) == 0) {
1460 FreePool(entry
->version
);
1461 entry
->version
= xstra_to_str(value
);
1465 if (strcmpa((CHAR8
*)"machine-id", key
) == 0) {
1466 FreePool(entry
->machine_id
);
1467 entry
->machine_id
= xstra_to_str(value
);
1471 if (strcmpa((CHAR8
*)"linux", key
) == 0) {
1472 FreePool(entry
->loader
);
1473 entry
->type
= LOADER_LINUX
;
1474 entry
->loader
= xstra_to_path(value
);
1479 if (strcmpa((CHAR8
*)"efi", key
) == 0) {
1480 entry
->type
= LOADER_EFI
;
1481 FreePool(entry
->loader
);
1482 entry
->loader
= xstra_to_path(value
);
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
;
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
;
1501 if (strcmpa((CHAR8
*)"devicetree", key
) == 0) {
1502 FreePool(entry
->devicetree
);
1503 entry
->devicetree
= xstra_to_path(value
);
1507 if (strcmpa((CHAR8
*)"initrd", key
) == 0) {
1508 _cleanup_freepool_ CHAR16
*new = NULL
;
1510 new = xstra_to_path(value
);
1514 s
= xpool_print(L
"%s initrd=%s", initrd
, new);
1518 initrd
= xpool_print(L
"initrd=%s", new);
1523 if (strcmpa((CHAR8
*)"options", key
) == 0) {
1524 _cleanup_freepool_ CHAR16
*new = NULL
;
1526 new = xstra_to_str(value
);
1527 if (entry
->options
) {
1530 s
= xpool_print(L
"%s %s", entry
->options
, new);
1531 FreePool(entry
->options
);
1534 entry
->options
= TAKE_PTR(new);
1540 if (entry
->type
== LOADER_UNDEFINED
)
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);
1549 /* add initrd= to options */
1550 if (entry
->type
== LOADER_LINUX
&& initrd
) {
1551 if (entry
->options
) {
1554 s
= xpool_print(L
"%s %s", initrd
, entry
->options
);
1555 FreePool(entry
->options
);
1558 entry
->options
= TAKE_PTR(initrd
);
1561 entry
->device
= device
;
1562 entry
->id
= xstrdup(file
);
1565 config_add_entry(config
, entry
);
1567 config_entry_parse_tries(entry
, path
, file
, L
".conf");
1571 static void config_load_defaults(Config
*config
, EFI_FILE
*root_dir
) {
1572 _cleanup_freepool_ CHAR8
*content
= NULL
;
1578 *config
= (Config
) {
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
,
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
);
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
;
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
);
1606 config
->timeout_sec
= MIN(value
, TIMEOUT_TYPE_MAX
);
1607 config
->force_menu
= TRUE
; /* force the menu when this is set */
1610 err
= efivar_get_uint_string(LOADER_GUID
, L
"LoaderConfigConsoleMode", &value
);
1611 if (!EFI_ERROR(err
))
1612 config
->console_mode_efivar
= value
;
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
);
1619 (void) efivar_get(LOADER_GUID
, L
"LoaderEntryDefault", &config
->entry_default_efivar
);
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
);
1627 static void config_load_entries(
1631 const CHAR16
*loaded_image_path
) {
1633 _cleanup_(file_closep
) EFI_FILE
*entries_dir
= NULL
;
1634 _cleanup_freepool_ EFI_FILE_INFO
*f
= NULL
;
1642 /* Adds Boot Loader Type #1 entries (i.e. /loader/entries/….conf) */
1644 err
= open_directory(root_dir
, L
"\\loader\\entries", &entries_dir
);
1649 _cleanup_freepool_ CHAR8
*content
= NULL
;
1651 err
= readdir_harder(entries_dir
, &f
, &f_size
);
1652 if (EFI_ERROR(err
) || !f
)
1655 if (f
->FileName
[0] == '.')
1657 if (FLAGS_SET(f
->Attribute
, EFI_FILE_DIRECTORY
))
1660 if (!endswith_no_case(f
->FileName
, L
".conf"))
1662 if (startswith(f
->FileName
, L
"auto-"))
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
);
1671 static INTN
config_entry_compare(const ConfigEntry
*a
, const ConfigEntry
*b
) {
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);
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 */
1690 if (a
->sort_key
&& b
->sort_key
) {
1691 r
= strcmp(a
->sort_key
, b
->sort_key
);
1695 /* If multiple installations of the same OS are around, group by machine ID */
1696 r
= strcmp_ptr(a
->machine_id
, b
->machine_id
);
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
);
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
);
1713 if (a
->tries_left
== UINTN_MAX
|| b
->tries_left
== UINTN_MAX
)
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
);
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
);
1725 static UINTN
config_entry_find(Config
*config
, const CHAR16
*needle
) {
1731 for (UINTN i
= 0; i
< config
->entry_count
; i
++)
1732 if (MetaiMatch(config
->entries
[i
]->id
, (CHAR16
*) needle
))
1738 static void config_default_entry_select(Config
*config
) {
1743 i
= config_entry_find(config
, config
->entry_oneshot
);
1744 if (i
!= IDX_INVALID
) {
1745 config
->idx_default
= i
;
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
;
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
);
1760 i
= config_entry_find(config
, config
->entry_default_config
);
1761 if (i
!= IDX_INVALID
) {
1762 config
->idx_default
= i
;
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
)
1770 config
->idx_default
= i
;
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;
1780 static BOOLEAN
entries_unique(ConfigEntry
**entries
, BOOLEAN
*unique
, UINTN entry_count
) {
1781 BOOLEAN is_unique
= TRUE
;
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)
1791 is_unique
= unique
[i
] = unique
[k
] = FALSE
;
1797 /* generate a unique title, avoiding non-distinguishable menu entries */
1798 static void config_title_generate(Config
*config
) {
1801 BOOLEAN unique
[config
->entry_count
];
1804 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
1805 assert(!config
->entries
[i
]->title_show
);
1807 config
->entries
[i
]->title_show
= xstrdup(config
->entries
[i
]->title
?: config
->entries
[i
]->id
);
1810 if (entries_unique(config
->entries
, unique
, config
->entry_count
))
1813 /* add version to non-unique titles */
1814 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
1820 if (!config
->entries
[i
]->version
)
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
);
1827 if (entries_unique(config
->entries
, unique
, config
->entry_count
))
1830 /* add machine-id to non-unique titles */
1831 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
1837 if (!config
->entries
[i
]->machine_id
)
1840 _cleanup_freepool_ CHAR16
*t
= config
->entries
[i
]->title_show
;
1841 config
->entries
[i
]->title_show
= xpool_print(
1844 StrnLen(config
->entries
[i
]->machine_id
, 8),
1845 config
->entries
[i
]->machine_id
);
1848 if (entries_unique(config
->entries
, unique
, config
->entry_count
))
1851 /* add file name to non-unique titles */
1852 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
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
);
1861 static BOOLEAN
config_entry_add_call(
1864 const CHAR16
*title
,
1865 EFI_STATUS (*call
)(void)) {
1874 entry
= xnew(ConfigEntry
, 1);
1875 *entry
= (ConfigEntry
) {
1877 .title
= xstrdup(title
),
1879 .tries_done
= UINTN_MAX
,
1880 .tries_left
= UINTN_MAX
,
1883 config_add_entry(config
, entry
);
1887 static ConfigEntry
*config_entry_add_loader(
1890 enum loader_type type
,
1893 const CHAR16
*title
,
1894 const CHAR16
*loader
,
1895 const CHAR16
*sort_key
,
1896 const CHAR16
*version
) {
1906 entry
= xnew(ConfigEntry
, 1);
1907 *entry
= (ConfigEntry
) {
1909 .title
= xstrdup(title
),
1910 .version
= version
? xstrdup(version
) : NULL
,
1912 .loader
= xstrdup(loader
),
1914 .sort_key
= xstrdup(sort_key
),
1916 .tries_done
= UINTN_MAX
,
1917 .tries_left
= UINTN_MAX
,
1922 config_add_entry(config
, entry
);
1926 static BOOLEAN
is_sd_boot(EFI_FILE
*root_dir
, const CHAR16
*loader_path
) {
1928 const CHAR8
*sections
[] = {
1929 (CHAR8
*)".sdmagic",
1932 UINTN offset
= 0, size
= 0, read
;
1933 _cleanup_freepool_ CHAR8
*content
= NULL
;
1936 assert(loader_path
);
1938 err
= pe_file_locate_sections(root_dir
, loader_path
, sections
, &offset
, &size
);
1939 if (EFI_ERROR(err
) || size
!= sizeof(magic
))
1942 err
= file_read(root_dir
, loader_path
, offset
, size
, &content
, &read
);
1943 if (EFI_ERROR(err
) || size
!= read
)
1946 return CompareMem(content
, magic
, sizeof(magic
)) == 0;
1949 static ConfigEntry
*config_entry_add_loader_auto(
1953 const CHAR16
*loaded_image_path
,
1956 const CHAR16
*title
,
1957 const CHAR16
*loader
) {
1964 assert(loader
|| loaded_image_path
);
1966 if (!config
->auto_entries
)
1969 if (loaded_image_path
) {
1970 loader
= L
"\\EFI\\BOOT\\BOOT" EFI_MACHINE_TYPE_NAME
".efi";
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.
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"))
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);
1989 return config_entry_add_loader(config
, device
, LOADER_AUTO
, id
, key
, title
, loader
, NULL
, NULL
);
1992 static void config_entry_add_osx(Config
*config
) {
1994 UINTN n_handles
= 0;
1995 _cleanup_freepool_ EFI_HANDLE
*handles
= NULL
;
1999 if (!config
->auto_entries
)
2002 err
= LibLocateHandle(ByProtocol
, &FileSystemProtocol
, NULL
, &n_handles
, &handles
);
2006 for (UINTN i
= 0; i
< n_handles
; i
++) {
2007 _cleanup_(file_closep
) EFI_FILE
*root
= LibOpenRoot(handles
[i
]);
2011 if (config_entry_add_loader_auto(
2019 L
"\\System\\Library\\CoreServices\\boot.efi"))
2024 static EFI_STATUS
boot_windows_bitlocker(void) {
2025 _cleanup_freepool_ EFI_HANDLE
*handles
= NULL
;
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.
2032 /* BitLocker key cannot be sealed without a TPM present. */
2034 return EFI_NOT_FOUND
;
2036 err
= BS
->LocateHandleBuffer(ByProtocol
, &BlockIoProtocol
, NULL
, &n_handles
, &handles
);
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)
2048 CHAR8 buf
[block_io
->Media
->BlockSize
];
2049 err
= block_io
->ReadBlocks(block_io
, block_io
->Media
->MediaId
, 0, sizeof(buf
), buf
);
2053 if (CompareMem(buf
+ 3, "-FVE-FS-", STRLEN("-FVE-FS-")) == 0) {
2059 /* If no BitLocker drive was found, we can just chainload bootmgfw.efi directly. */
2061 return EFI_NOT_FOUND
;
2063 _cleanup_freepool_ UINT16
*boot_order
= NULL
;
2064 UINTN boot_order_size
;
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)
2072 for (UINTN i
= 0; i
< boot_order_size
/ sizeof(UINT16
); i
++) {
2073 _cleanup_freepool_ CHAR8
*buf
= NULL
;
2074 CHAR16 name
[sizeof(L
"Boot0000")];
2077 SPrint(name
, sizeof(name
), L
"Boot%04x", boot_order
[i
]);
2078 err
= efivar_get_raw(EFI_GLOBAL_GUID
, name
, &buf
, &buf_size
);
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
))
2088 if (streq((CHAR16
*) (buf
+ offset
), L
"Windows Boot Manager")) {
2089 err
= efivar_set_raw(
2093 sizeof(boot_order
[i
]),
2094 EFI_VARIABLE_NON_VOLATILE
);
2097 return RT
->ResetSystem(EfiResetWarm
, EFI_SUCCESS
, 0, NULL
);
2101 return EFI_NOT_FOUND
;
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
;
2115 if (!config
->auto_entries
)
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
);
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");
2127 if (config
->reboot_for_bitlocker
)
2128 e
->call
= boot_windows_bitlocker
;
2132 static void config_entry_add_linux(
2135 EFI_FILE
*root_dir
) {
2137 _cleanup_(file_closep
) EFI_FILE
*linux_dir
= NULL
;
2138 _cleanup_freepool_ EFI_FILE_INFO
*f
= NULL
;
2143 /* Adds Boot Loader Type #2 entries (i.e. /EFI/Linux/….efi) */
2149 err
= open_directory(root_dir
, L
"\\EFI\\Linux", &linux_dir
);
2160 static const CHAR8
* const sections
[_SECTION_MAX
+ 1] = {
2161 [SECTION_CMDLINE
] = (const CHAR8
*) ".cmdline",
2162 [SECTION_OSREL
] = (const CHAR8
*) ".osrel",
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
,
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
] = {};
2177 err
= readdir_harder(linux_dir
, &f
, &f_size
);
2178 if (EFI_ERROR(err
) || !f
)
2181 if (f
->FileName
[0] == '.')
2183 if (FLAGS_SET(f
->Attribute
, EFI_FILE_DIRECTORY
))
2185 if (!endswith_no_case(f
->FileName
, L
".efi"))
2187 if (startswith(f
->FileName
, L
"auto-"))
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)
2195 err
= file_read(linux_dir
, f
->FileName
, offs
[SECTION_OSREL
], szs
[SECTION_OSREL
], &content
, NULL
);
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
);
2207 if (strcmpa((const CHAR8
*) "IMAGE_ID", key
) == 0) {
2208 FreePool(os_image_id
);
2209 os_image_id
= xstra_to_str(value
);
2213 if (strcmpa((const CHAR8
*) "NAME", key
) == 0) {
2215 os_name
= xstra_to_str(value
);
2219 if (strcmpa((const CHAR8
*) "ID", key
) == 0) {
2221 os_id
= xstra_to_str(value
);
2225 if (strcmpa((const CHAR8
*) "IMAGE_VERSION", key
) == 0) {
2226 FreePool(os_image_version
);
2227 os_image_version
= xstra_to_str(value
);
2231 if (strcmpa((const CHAR8
*) "VERSION", key
) == 0) {
2232 FreePool(os_version
);
2233 os_version
= xstra_to_str(value
);
2237 if (strcmpa((const CHAR8
*) "VERSION_ID", key
) == 0) {
2238 FreePool(os_version_id
);
2239 os_version_id
= xstra_to_str(value
);
2243 if (strcmpa((const CHAR8
*) "BUILD_ID", key
) == 0) {
2244 FreePool(os_build_id
);
2245 os_build_id
= xstra_to_str(value
);
2250 if (!bootspec_pick_name_version_sort_key(
2264 path
= xpool_print(L
"\\EFI\\Linux\\%s", f
->FileName
);
2265 entry
= config_entry_add_loader(
2268 LOADER_UNIFIED_LINUX
,
2269 /* id= */ f
->FileName
,
2271 /* title= */ good_name
,
2273 /* sort_key= */ good_sort_key
,
2276 config_entry_parse_tries(entry
, L
"\\EFI\\Linux", f
->FileName
, L
".efi");
2278 if (szs
[SECTION_CMDLINE
] == 0)
2281 content
= mfree(content
);
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';
2290 entry
->options
= xstra_to_str(content
);
2295 static void config_load_xbootldr(
2297 EFI_HANDLE
*device
) {
2299 _cleanup_(file_closep
) EFI_FILE
*root_dir
= NULL
;
2300 EFI_HANDLE new_device
;
2306 err
= xbootldr_open(device
, &new_device
, &root_dir
);
2310 config_entry_add_linux(config
, new_device
, root_dir
);
2311 config_load_entries(config
, new_device
, root_dir
, NULL
);
2314 static EFI_STATUS
image_start(
2316 EFI_HANDLE parent_image
,
2317 const Config
*config
,
2318 const ConfigEntry
*entry
) {
2320 _cleanup_(devicetree_cleanup
) struct devicetree_state dtstate
= {};
2322 _cleanup_freepool_ EFI_DEVICE_PATH
*path
= NULL
;
2329 /* If this loader entry has a special way to boot, try that first. */
2331 (void) entry
->call();
2333 path
= FileDevicePath(entry
->device
, entry
->loader
);
2335 return log_error_status_stall(EFI_INVALID_PARAMETER
, L
"Error getting device path.");
2337 err
= BS
->LoadImage(FALSE
, parent_image
, path
, NULL
, 0, &image
);
2339 return log_error_status_stall(err
, L
"Error loading %s: %r", entry
->loader
, err
);
2341 if (entry
->devicetree
) {
2342 err
= devicetree_install(&dtstate
, root_dir
, entry
->devicetree
);
2344 return log_error_status_stall(err
, L
"Error loading %s: %r", entry
->devicetree
, err
);
2347 if (config
->options_edit
)
2348 options
= config
->options_edit
;
2349 else if (entry
->options
)
2350 options
= entry
->options
;
2354 EFI_LOADED_IMAGE
*loaded_image
;
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
);
2362 loaded_image
->LoadOptions
= options
;
2363 loaded_image
->LoadOptionsSize
= StrSize(loaded_image
->LoadOptions
);
2365 /* Try to log any options to the TPM, especially to catch manually edited options */
2366 (void) tpm_log_load_options(options
);
2369 efivar_set_time_usec(LOADER_GUID
, L
"LoaderTimeExecUSec", 0);
2370 err
= BS
->StartImage(image
, NULL
, NULL
);
2372 BS
->UnloadImage(image
);
2376 static void config_free(Config
*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
);
2386 static void config_write_entries_to_variable(Config
*config
) {
2387 _cleanup_freepool_ CHAR8
*buffer
= NULL
;
2393 for (UINTN i
= 0; i
< config
->entry_count
; i
++)
2394 sz
+= StrSize(config
->entries
[i
]->id
);
2396 p
= buffer
= xallocate_pool(sz
);
2398 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
2401 l
= StrSize(config
->entries
[i
]->id
);
2402 CopyMem(p
, config
->entries
[i
]->id
, l
);
2407 assert(p
== buffer
+ sz
);
2409 /* Store the full list of discovered entries. */
2410 (void) efivar_set_raw(LOADER_GUID
, L
"LoaderEntries", buffer
, sz
, 0);
2413 static void save_selected_entry(const Config
*config
, const ConfigEntry
*entry
) {
2416 assert(entry
->loader
|| !entry
->call
);
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);
2421 /* Do not save or delete if this was a oneshot boot. */
2422 if (streq_ptr(config
->entry_oneshot
, entry
->id
))
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
))
2430 (void) efivar_set(LOADER_GUID
, L
"LoaderEntryLastBooted", entry
->id
, EFI_VARIABLE_NON_VOLATILE
);
2432 /* Delete the non-volatile var if not needed. */
2433 (void) efivar_set(LOADER_GUID
, L
"LoaderEntryLastBooted", NULL
, EFI_VARIABLE_NON_VOLATILE
);
2436 static void export_variables(
2437 EFI_LOADED_IMAGE
*loaded_image
,
2438 const CHAR16
*loaded_image_path
,
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
|
2452 _cleanup_freepool_ CHAR16
*infostr
= NULL
, *typestr
= NULL
;
2455 assert(loaded_image
);
2456 assert(loaded_image_path
);
2458 efivar_set_time_usec(LOADER_GUID
, L
"LoaderTimeInitUSec", init_usec
);
2459 efivar_set(LOADER_GUID
, L
"LoaderInfo", L
"systemd-boot " GIT_VERSION
, 0);
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);
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);
2467 (void) efivar_set_uint64_le(LOADER_GUID
, L
"LoaderFeatures", loader_features
, 0);
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);
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);
2477 static void config_load_all_entries(
2479 EFI_LOADED_IMAGE
*loaded_image
,
2480 const CHAR16
*loaded_image_path
,
2481 EFI_FILE
*root_dir
) {
2484 assert(loaded_image
);
2485 assert(loaded_image_path
);
2488 config_load_defaults(config
, root_dir
);
2490 /* scan /EFI/Linux/ directory */
2491 config_entry_add_linux(config
, loaded_image
->DeviceHandle
, root_dir
);
2493 /* scan /loader/entries/\*.conf files */
2494 config_load_entries(config
, loaded_image
->DeviceHandle
, root_dir
, loaded_image_path
);
2496 /* Similar, but on any XBOOTLDR partition */
2497 config_load_xbootldr(config
, loaded_image
->DeviceHandle
);
2499 /* sort entries after version number */
2500 sort_pointer_array((void **) config
->entries
, config
->entry_count
, (compare_pointer_func_t
) config_entry_compare
);
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
);
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
);
2516 if (config
->entry_count
== 0)
2519 config_write_entries_to_variable(config
);
2521 config_title_generate(config
);
2523 /* select entry by configured pattern or EFI LoaderDefaultEntry= variable */
2524 config_default_entry_select(config
);
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
;
2534 BOOLEAN menu
= FALSE
;
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. */
2542 err
= BS
->OpenProtocol(image
,
2543 &LoadedImageProtocol
,
2544 (void **)&loaded_image
,
2547 EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
2549 return log_error_status_stall(err
, L
"Error getting a LoadedImageProtocol handle: %r", err
);
2551 loaded_image_path
= DevicePathToStr(loaded_image
->FilePath
);
2552 if (!loaded_image_path
)
2555 export_variables(loaded_image
, loaded_image_path
, init_usec
);
2557 root_dir
= LibOpenRoot(loaded_image
->DeviceHandle
);
2559 return log_error_status_stall(EFI_LOAD_ERROR
, L
"Unable to open root directory.", EFI_LOAD_ERROR
);
2561 if (secure_boot_enabled() && shim_loaded()) {
2562 err
= security_policy_install();
2564 return log_error_status_stall(err
, L
"Error installing security policy: %r", err
);
2567 (void) load_drivers(image
, loaded_image
, root_dir
);
2569 config_load_all_entries(&config
, loaded_image
, loaded_image_path
, root_dir
);
2571 if (config
.entry_count
== 0) {
2572 log_error_stall(L
"No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
2576 /* select entry or show menu when key is pressed or timeout is set */
2577 if (config
.force_menu
|| config
.timeout_sec
> 0)
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
;
2597 entry
= config
.entries
[config
.idx_default
];
2599 efivar_set_time_usec(LOADER_GUID
, L
"LoaderTimeMenuUSec", 0);
2600 if (!menu_run(&config
, &entry
, loaded_image_path
))
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
) {
2611 config_entry_bump_counters(entry
, root_dir
);
2612 save_selected_entry(&config
, entry
);
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
);
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
);
2625 config
.timeout_sec
= 0;
2629 BS
->CloseProtocol(image
, &LoadedImageProtocol
, image
, NULL
);