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"
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.
31 #define TEXT_ATTR_SWAP(c) EFI_TEXT_ATTR(((c) & 0b11110000) >> 4, (c) & 0b1111)
33 /* Magic string for recognizing our own binaries */
34 _used_
_section_(".sdmagic") static const char magic
[] =
35 "#### LoaderInfo: systemd-boot " GIT_VERSION
" ####";
37 /* Makes systemd-boot available from \EFI\Linux\ for testing purposes. */
38 _used_
_section_(".osrel") static const char osrel
[] =
40 "VERSION=\"" GIT_VERSION
"\"\n"
41 "NAME=\"systemd-boot " GIT_VERSION
"\"\n";
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 */
56 enum loader_type type
;
61 EFI_STATUS (*call
)(void);
62 BOOLEAN no_autoselect
;
72 ConfigEntry
**entries
;
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
;
86 BOOLEAN auto_firmware
;
88 BOOLEAN use_saved_entry
;
89 BOOLEAN use_saved_entry_efivar
;
91 INT64 console_mode_efivar
;
92 RandomSeedMode random_seed_mode
;
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 ↔ … */
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
,
107 static void cursor_left(UINTN
*cursor
, UINTN
*first
) {
113 else if ((*first
) > 0)
117 static void cursor_right(
126 if ((*cursor
)+1 < x_max
)
128 else if ((*first
) + (*cursor
) < len
)
132 static BOOLEAN
line_edit(
133 const CHAR16
*line_in
,
138 _cleanup_freepool_ CHAR16
*line
= NULL
, *print
= NULL
;
139 UINTN size
, len
, first
, cursor
, clear
;
147 size
= StrLen(line_in
) + 1024;
148 line
= xnew(CHAR16
, size
);
150 StrCpy(line
, line_in
);
152 print
= xnew(CHAR16
, x_max
+ 1);
163 UINTN cursor_color
= TEXT_ATTR_SWAP(COLOR_EDIT
);
165 j
= MIN(len
- first
, x_max
);
166 CopyMem(print
, line
+ first
, j
* sizeof(CHAR16
));
167 while (clear
> 0 && j
< x_max
) {
173 /* See comment at edit_line() call site for why we start at 1. */
174 print_at(1, y_pos
, COLOR_EDIT
, print
);
178 print
[cursor
+1] = '\0';
180 print_at(cursor
+ 1, y_pos
, cursor_color
, print
+ cursor
);
181 cursor_color
= TEXT_ATTR_SWAP(cursor_color
);
183 err
= console_key_read(&key
, 750 * 1000);
184 print_at(cursor
+ 1, y_pos
, COLOR_EDIT
, print
+ cursor
);
185 } while (EFI_ERROR(err
));
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')):
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 */
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')):
208 cursor
= len
- first
;
209 if (cursor
+1 >= x_max
) {
211 first
= len
- (x_max
-1);
215 case KEYPRESS(0, SCAN_DOWN
, 0):
216 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'f'):
217 case KEYPRESS(EFI_CONTROL_PRESSED
, SCAN_RIGHT
, 0):
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
);
225 case KEYPRESS(0, SCAN_UP
, 0):
226 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'b'):
227 case KEYPRESS(EFI_CONTROL_PRESSED
, SCAN_LEFT
, 0):
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
);
234 while ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] != ' ')
235 cursor_left(&cursor
, &first
);
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')):
242 if (first
+ cursor
== len
)
244 cursor_right(&cursor
, &first
, x_max
, len
);
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')):
251 cursor_left(&cursor
, &first
);
254 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'd'):
259 for (k
= first
+ cursor
; k
< len
&& line
[k
] == ' '; k
++)
261 for (; k
< len
&& line
[k
] != ' '; k
++)
264 for (UINTN i
= first
+ cursor
; i
+ clear
< len
; i
++)
265 line
[i
] = line
[i
+ clear
];
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 */
275 if ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] == ' ') {
276 cursor_left(&cursor
, &first
);
278 while ((first
+ cursor
) > 0 && line
[first
+ cursor
] == ' ') {
279 cursor_left(&cursor
, &first
);
283 while ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] != ' ') {
284 cursor_left(&cursor
, &first
);
288 for (UINTN i
= first
+ cursor
; i
+ clear
< len
; i
++)
289 line
[i
] = line
[i
+ clear
];
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')):
299 if (first
+ cursor
== len
)
301 for (UINTN i
= first
+ cursor
; i
< len
; i
++)
307 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'k'):
308 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('k')):
310 line
[first
+ cursor
] = '\0';
311 clear
= len
- (first
+ cursor
);
312 len
= first
+ cursor
;
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
);
325 case KEYPRESS(0, 0, CHAR_BACKSPACE
):
328 if (first
== 0 && cursor
== 0)
330 for (UINTN i
= first
+ cursor
-1; i
< len
; i
++)
336 if (cursor
> 0 || first
== 0)
338 /* show full line if it fits */
344 /* jump left to see what we delete */
354 case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
355 case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
358 for (UINTN i
= len
; i
> first
+ cursor
; i
--)
360 line
[first
+ cursor
] = KEYCHAR(key
);
363 if (cursor
+1 < x_max
)
365 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 void ps_string(const CHAR16
*fmt
, const void *value
) {
438 static void ps_bool(const CHAR16
*fmt
, BOOLEAN value
) {
440 Print(fmt
, yes_no(value
));
443 static void print_status(Config
*config
, CHAR16
*loaded_image_path
) {
446 SecureBootMode secure
;
447 _cleanup_freepool_ CHAR16
*device_part_uuid
= NULL
;
450 assert(loaded_image_path
);
452 clear_screen(COLOR_NORMAL
);
453 console_query_mode(&x_max
, &y_max
);
455 secure
= secure_boot_mode();
456 (void) efivar_get(LOADER_GUID
, L
"LoaderDevicePartUUID", &device_part_uuid
);
458 /* We employ some unusual indentation here for readability. */
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
);
472 Print(L
"\n--- Press any key to continue. ---\n\n");
473 console_key_read(&key
, UINT64_MAX
);
475 switch (config
->timeout_sec_config
) {
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;
483 Print(L
" timeout: %lu s\n", config
->timeout_sec_config
);
486 switch (config
->timeout_sec_efivar
) {
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;
494 Print(L
" timeout (EFI var): %lu s\n", config
->timeout_sec_efivar
);
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
]);
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;
514 Print(L
" console-mode: %ld\n", config
->console_mode
); break;
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
);
521 Print(L
"\n--- Press any key to continue. ---\n\n");
522 console_key_read(&key
, UINT64_MAX
);
524 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
525 ConfigEntry
*entry
= config
->entries
[i
];
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
);
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
);
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
);
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'))
555 static EFI_STATUS
reboot_into_firmware(void) {
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.");
562 (void) efivar_get_uint64_le(EFI_GLOBAL_GUID
, L
"OsIndications", &osind
);
563 osind
|= EFI_OS_INDICATIONS_BOOT_TO_FW_UI
;
565 err
= efivar_set_uint64_le(EFI_GLOBAL_GUID
, L
"OsIndications", osind
, EFI_VARIABLE_NON_VOLATILE
);
567 return log_error_status_stall(err
, L
"Error setting OsIndications: %r", err
);
569 err
= RT
->ResetSystem(EfiResetCold
, EFI_SUCCESS
, 0, NULL
);
570 return log_error_status_stall(err
, L
"Error calling ResetSystem: %r", err
);
573 static BOOLEAN
menu_run(
575 ConfigEntry
**chosen_entry
,
576 CHAR16
*loaded_image_path
) {
579 assert(chosen_entry
);
580 assert(loaded_image_path
);
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;
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
;
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
;
600 graphics_mode(FALSE
);
601 ST
->ConIn
->Reset(ST
->ConIn
, FALSE
);
602 ST
->ConOut
->EnableCursor(ST
->ConOut
, FALSE
);
604 /* draw a single character to make ClearScreen work on some firmware */
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
);
618 UINTN line_width
= 0, entry_padding
= 3;
620 console_query_mode(&x_max
, &y_max
);
622 /* account for padding+status */
623 visible_max
= y_max
- 2;
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
629 if (config
->entry_count
<= visible_max
|| idx_highlight
<= visible_max
/ 2)
631 else if (idx_highlight
>= config
->entry_count
- (visible_max
/ 2))
632 idx_first
= config
->entry_count
- visible_max
;
634 idx_first
= idx_highlight
- (visible_max
/ 2);
635 idx_last
= idx_first
+ visible_max
- 1;
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
);
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;
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);
652 lines
= strv_free(lines
);
653 clearline
= mfree(clearline
);
655 /* menu entries title lines */
656 lines
= xnew(CHAR16
*, config
->entry_count
+ 1);
658 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
661 lines
[i
] = xnew(CHAR16
, line_width
+ 1);
662 padding
= (line_width
- MIN(StrLen(config
->entries
[i
]->title_show
), line_width
)) / 2;
664 for (j
= 0; j
< padding
; j
++)
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
];
670 for (; j
< line_width
; j
++)
672 lines
[i
][line_width
] = '\0';
674 lines
[config
->entry_count
] = NULL
;
676 clearline
= xnew(CHAR16
, x_max
+ 1);
677 for (UINTN i
= 0; i
< x_max
; i
++)
679 clearline
[x_max
] = 0;
686 clear_screen(COLOR_NORMAL
);
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
,
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
,
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
"=>");
712 if (timeout_remain
> 0) {
714 status
= xpool_print(L
"Boot in %u s.", timeout_remain
);
717 /* print status at last line of screen */
723 len
= StrLen(status
);
725 x
= (x_max
- len
) / 2;
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
);
733 err
= console_key_read(&key
, timeout_remain
> 0 ? 1000 * 1000 : UINT64_MAX
);
734 if (err
== EFI_TIMEOUT
) {
736 if (timeout_remain
== 0) {
746 /* clear status after keystroke */
750 print_at(0, y_status
, COLOR_NORMAL
, clearline
+ 1);
753 idx_highlight_prev
= idx_highlight
;
755 if (firmware_setup
) {
756 firmware_setup
= FALSE
;
757 if (key
== KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN
))
758 reboot_into_firmware();
763 case KEYPRESS(0, SCAN_UP
, 0):
764 case KEYPRESS(0, 0, 'k'):
765 case KEYPRESS(0, 0, 'K'):
766 if (idx_highlight
> 0)
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)
777 case KEYPRESS(0, SCAN_HOME
, 0):
778 case KEYPRESS(EFI_ALT_PRESSED
, 0, '<'):
779 if (idx_highlight
> 0) {
785 case KEYPRESS(0, SCAN_END
, 0):
786 case KEYPRESS(EFI_ALT_PRESSED
, 0, '>'):
787 if (idx_highlight
< config
->entry_count
-1) {
789 idx_highlight
= config
->entry_count
-1;
793 case KEYPRESS(0, SCAN_PAGE_UP
, 0):
794 if (idx_highlight
> visible_max
)
795 idx_highlight
-= visible_max
;
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;
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):
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");
822 case KEYPRESS(0, 0, 'Q'):
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.");
835 config
->entry_default_efivar
= mfree(config
->entry_default_efivar
);
836 config
->idx_default_efivar
= -1;
837 status
= xstrdup(L
"Default boot entry cleared.");
839 config
->use_saved_entry_efivar
= FALSE
;
843 case KEYPRESS(0, 0, '-'):
844 case KEYPRESS(0, 0, 'T'):
845 status
= update_timeout_efivar(&config
->timeout_sec_efivar
, FALSE
);
848 case KEYPRESS(0, 0, '+'):
849 case KEYPRESS(0, 0, 't'):
850 status
= update_timeout_efivar(&config
->timeout_sec_efivar
, TRUE
);
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
)
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);
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);
875 case KEYPRESS(0, 0, 'p'):
876 case KEYPRESS(0, 0, 'P'):
877 print_status(config
, loaded_image_path
);
881 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'l'):
882 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('l')):
886 case KEYPRESS(0, 0, 'r'):
887 err
= console_set_mode(CONSOLE_MODE_NEXT
);
889 status
= xpool_print(L
"Error changing console mode: %r", err
);
891 config
->console_mode_efivar
= ST
->ConOut
->Mode
->Mode
;
892 status
= xpool_print(L
"Console mode changed to %ld.", config
->console_mode_efivar
);
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
);
902 status
= xpool_print(L
"Error resetting console mode: %r", err
);
904 status
= xpool_print(L
"Console mode reset to %s default.",
905 config
->console_mode
== CONSOLE_MODE_KEEP
? L
"firmware" : L
"configuration file");
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.");
920 status
= xpool_print(L
"Reboot into firmware interface not supported.");
924 /* jump with a hotkey directly to a matching entry */
925 idx
= entry_lookup_key(config
, idx_highlight
+1, KEYCHAR(key
));
932 if (idx_highlight
> idx_last
) {
933 idx_last
= idx_highlight
;
934 idx_first
= 1 + idx_highlight
- visible_max
;
936 } else if (idx_highlight
< idx_first
) {
937 idx_first
= idx_highlight
;
938 idx_last
= idx_highlight
+ visible_max
-1;
942 if (!refresh
&& idx_highlight
!= idx_highlight_prev
)
946 *chosen_entry
= config
->entries
[idx_highlight
];
948 /* Update EFI vars after we left the menu to reduce NVRAM writes. */
950 if (default_efivar_saved
!= config
->idx_default_efivar
)
951 efivar_set(LOADER_GUID
, L
"LoaderEntryDefault", config
->entry_default_efivar
, EFI_VARIABLE_NON_VOLATILE
);
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
);
957 efivar_set_uint_string(LOADER_GUID
, L
"LoaderConfigConsoleMode",
958 config
->console_mode_efivar
, EFI_VARIABLE_NON_VOLATILE
);
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
);
965 efivar_set_uint_string(LOADER_GUID
, L
"LoaderConfigTimeout",
966 config
->timeout_sec_efivar
, EFI_VARIABLE_NON_VOLATILE
);
969 clear_screen(COLOR_NORMAL
);
973 static void config_add_entry(Config
*config
, ConfigEntry
*entry
) {
977 if ((config
->entry_count
& 15) == 0) {
978 UINTN i
= config
->entry_count
+ 16;
979 config
->entries
= xreallocate_pool(
981 sizeof(void *) * config
->entry_count
,
984 config
->entries
[config
->entry_count
++] = entry
;
987 static void config_entry_free(ConfigEntry
*entry
) {
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
);
1005 static inline void config_entry_freep(ConfigEntry
**entry
) {
1006 config_entry_free(*entry
);
1009 static CHAR8
*line_get_key_value(
1014 CHAR8
**value_ret
) {
1016 CHAR8
*line
, *value
;
1026 line
= content
+ *pos
;
1031 while (line
[linelen
] && !strchra((CHAR8
*)"\n\r", line
[linelen
]))
1034 /* move pos to next line */
1043 /* terminate line */
1044 line
[linelen
] = '\0';
1046 /* remove leading whitespace */
1047 while (strchra((CHAR8
*)" \t", *line
)) {
1052 /* remove trailing whitespace */
1053 while (linelen
> 0 && strchra((CHAR8
*)" \t", line
[linelen
-1]))
1055 line
[linelen
] = '\0';
1060 /* split key/value */
1062 while (*value
&& !strchra(sep
, *value
))
1068 while (*value
&& strchra(sep
, *value
))
1072 if (value
[0] == '"' && line
[linelen
-1] == '"') {
1074 line
[linelen
-1] = '\0';
1082 static void config_defaults_load_from_file(Config
*config
, CHAR8
*content
) {
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
;
1098 _cleanup_freepool_ CHAR16
*s
= NULL
;
1100 s
= xstra_to_str(value
);
1101 config
->timeout_sec_config
= MIN(Atoi(s
), TIMEOUT_TYPE_MAX
);
1103 config
->timeout_sec
= config
->timeout_sec_config
;
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
);
1112 FreePool(config
->entry_default_config
);
1113 config
->entry_default_config
= xstra_to_str(value
);
1117 if (strcmpa((CHAR8
*)"editor", key
) == 0) {
1118 err
= parse_boolean(value
, &config
->editor
);
1120 log_error_stall(L
"Error parsing 'editor' config option: %a", value
);
1124 if (strcmpa((CHAR8
*)"auto-entries", key
) == 0) {
1125 err
= parse_boolean(value
, &config
->auto_entries
);
1127 log_error_stall(L
"Error parsing 'auto-entries' config option: %a", value
);
1131 if (strcmpa((CHAR8
*)"auto-firmware", key
) == 0) {
1132 err
= parse_boolean(value
, &config
->auto_firmware
);
1134 log_error_stall(L
"Error parsing 'auto-firmware' config option: %a", value
);
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
;
1146 _cleanup_freepool_ CHAR16
*s
= NULL
;
1148 s
= xstra_to_str(value
);
1149 config
->console_mode
= MIN(Atoi(s
), (UINTN
)CONSOLE_MODE_RANGE_MAX
);
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
;
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
);
1171 config
->random_seed_mode
= on
? RANDOM_SEED_ALWAYS
: RANDOM_SEED_OFF
;
1177 static void config_entry_parse_tries(
1181 const CHAR16
*suffix
) {
1183 UINTN left
= UINTN_MAX
, done
= UINTN_MAX
, factor
= 1, i
, next_left
, next_done
;
1184 _cleanup_freepool_ CHAR16
*prefix
= NULL
;
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").
1195 * Names we grok, and the series they result in:
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!
1203 /* Chop off any suffix such as ".conf" or ".efi" */
1205 UINTN suffix_length
;
1207 suffix_length
= StrLen(suffix
);
1208 if (i
< suffix_length
)
1214 /* Go backwards through the string and parse everything we encounter */
1224 if (left
== UINTN_MAX
) /* didn't read at least one digit for 'left'? */
1227 if (done
== UINTN_MAX
) /* no 'done' counter? If so, it's equivalent to 0 */
1233 if (left
== UINTN_MAX
) /* didn't parse any digit yet? */
1236 if (done
!= UINTN_MAX
) /* already encountered a dash earlier? */
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. */
1250 if (left
== UINTN_MAX
)
1251 left
= file
[i
] - '0';
1253 UINTN new_left
, digit
;
1255 digit
= file
[i
] - '0';
1256 if (digit
> UINTN_MAX
/ factor
) /* overflow check */
1259 new_left
= left
+ digit
* factor
;
1260 if (new_left
< left
) /* overflow check */
1263 if (new_left
== UINTN_MAX
) /* don't allow us to be confused */
1267 new_factor
= factor
* 10;
1268 if (new_factor
< factor
) /* overflow check */
1271 factor
= new_factor
;
1281 entry
->tries_left
= left
;
1282 entry
->tries_done
= done
;
1284 entry
->path
= xstrdup(path
);
1285 entry
->current_name
= xstrdup(file
);
1287 next_left
= left
<= 0 ? 0 : left
- 1;
1288 next_done
= done
>= (UINTN
) -2 ? (UINTN
) -2 : done
+ 1;
1290 prefix
= xstrdup(file
);
1293 entry
->next_name
= xpool_print(L
"%s+%u-%u%s", prefix
, next_left
, next_done
, suffix
?: L
"");
1296 static void config_entry_bump_counters(
1298 EFI_FILE_HANDLE root_dir
) {
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
;
1309 if (entry
->tries_left
== UINTN_MAX
)
1312 if (!entry
->path
|| !entry
->current_name
|| !entry
->next_name
)
1315 old_path
= xpool_print(L
"%s\\%s", entry
->path
, entry
->current_name
);
1317 err
= root_dir
->Open(root_dir
, &handle
, old_path
, EFI_FILE_MODE_READ
|EFI_FILE_MODE_WRITE
, 0ULL);
1321 err
= get_file_info_harder(handle
, &file_info
, &file_info_size
);
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
);
1333 /* Flush everything to disk, just in case… */
1334 (void) handle
->Flush(handle
);
1336 /* Let's tell the OS that we renamed this file, so that it knows what to rename to the counter-less name on
1338 new_path
= xpool_print(L
"%s\\%s", entry
->path
, entry
->next_name
);
1339 efivar_set(LOADER_GUID
, L
"LoaderBootCountPath", new_path
, 0);
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
);
1348 static void config_entry_add_from_file(
1355 const CHAR16
*loaded_image_path
) {
1357 _cleanup_(config_entry_freep
) ConfigEntry
*entry
= NULL
;
1362 EFI_FILE_HANDLE handle
;
1363 _cleanup_freepool_ CHAR16
*initrd
= NULL
;
1372 entry
= xnew(ConfigEntry
, 1);
1373 *entry
= (ConfigEntry
) {
1374 .tries_done
= UINTN_MAX
,
1375 .tries_left
= UINTN_MAX
,
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
);
1385 if (strcmpa((CHAR8
*)"version", key
) == 0) {
1386 FreePool(entry
->version
);
1387 entry
->version
= xstra_to_str(value
);
1391 if (strcmpa((CHAR8
*)"machine-id", key
) == 0) {
1392 FreePool(entry
->machine_id
);
1393 entry
->machine_id
= xstra_to_str(value
);
1397 if (strcmpa((CHAR8
*)"linux", key
) == 0) {
1398 FreePool(entry
->loader
);
1399 entry
->type
= LOADER_LINUX
;
1400 entry
->loader
= xstra_to_path(value
);
1405 if (strcmpa((CHAR8
*)"efi", key
) == 0) {
1406 entry
->type
= LOADER_EFI
;
1407 FreePool(entry
->loader
);
1408 entry
->loader
= xstra_to_path(value
);
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
;
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
;
1427 if (strcmpa((CHAR8
*)"devicetree", key
) == 0) {
1428 FreePool(entry
->devicetree
);
1429 entry
->devicetree
= xstra_to_path(value
);
1433 if (strcmpa((CHAR8
*)"initrd", key
) == 0) {
1434 _cleanup_freepool_ CHAR16
*new = NULL
;
1436 new = xstra_to_path(value
);
1440 s
= xpool_print(L
"%s initrd=%s", initrd
, new);
1444 initrd
= xpool_print(L
"initrd=%s", new);
1449 if (strcmpa((CHAR8
*)"options", key
) == 0) {
1450 _cleanup_freepool_ CHAR16
*new = NULL
;
1452 new = xstra_to_str(value
);
1453 if (entry
->options
) {
1456 s
= xpool_print(L
"%s %s", entry
->options
, new);
1457 FreePool(entry
->options
);
1460 entry
->options
= TAKE_PTR(new);
1466 if (entry
->type
== LOADER_UNDEFINED
)
1469 /* check existence */
1470 err
= root_dir
->Open(root_dir
, &handle
, entry
->loader
, EFI_FILE_MODE_READ
, 0ULL);
1473 handle
->Close(handle
);
1475 /* add initrd= to options */
1476 if (entry
->type
== LOADER_LINUX
&& initrd
) {
1477 if (entry
->options
) {
1480 s
= xpool_print(L
"%s %s", initrd
, entry
->options
);
1481 FreePool(entry
->options
);
1484 entry
->options
= TAKE_PTR(initrd
);
1487 entry
->device
= device
;
1488 entry
->id
= xstrdup(file
);
1491 config_add_entry(config
, entry
);
1493 config_entry_parse_tries(entry
, path
, file
, L
".conf");
1497 static void config_load_defaults(Config
*config
, EFI_FILE
*root_dir
) {
1498 _cleanup_freepool_ CHAR8
*content
= NULL
;
1504 *config
= (Config
) {
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
,
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
);
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
;
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
);
1531 config
->timeout_sec
= MIN(value
, TIMEOUT_TYPE_MAX
);
1532 config
->force_menu
= TRUE
; /* force the menu when this is set */
1535 err
= efivar_get_uint_string(LOADER_GUID
, L
"LoaderConfigConsoleMode", &value
);
1536 if (!EFI_ERROR(err
))
1537 config
->console_mode_efivar
= value
;
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
);
1544 (void) efivar_get(LOADER_GUID
, L
"LoaderEntryDefault", &config
->entry_default_efivar
);
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
);
1552 static void config_load_entries(
1556 const CHAR16
*loaded_image_path
) {
1558 _cleanup_(FileHandleClosep
) EFI_FILE_HANDLE entries_dir
= NULL
;
1559 _cleanup_freepool_ EFI_FILE_INFO
*f
= NULL
;
1567 err
= open_directory(root_dir
, L
"\\loader\\entries", &entries_dir
);
1572 _cleanup_freepool_ CHAR8
*content
= NULL
;
1574 err
= readdir_harder(entries_dir
, &f
, &f_size
);
1575 if (f_size
== 0 || EFI_ERROR(err
))
1578 if (f
->FileName
[0] == '.')
1580 if (FLAGS_SET(f
->Attribute
, EFI_FILE_DIRECTORY
))
1583 if (!endswith_no_case(f
->FileName
, L
".conf"))
1585 if (startswith(f
->FileName
, L
"auto-"))
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
);
1594 static INTN
config_entry_compare(const ConfigEntry
*a
, const ConfigEntry
*b
) {
1600 /* Order entries that have no tries left to the beginning of the list */
1601 if (a
->tries_left
!= 0 && b
->tries_left
== 0)
1603 if (a
->tries_left
== 0 && b
->tries_left
!= 0)
1606 r
= strverscmp_improved(a
->id
, b
->id
);
1610 if (a
->tries_left
== UINTN_MAX
||
1611 b
->tries_left
== UINTN_MAX
)
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
)
1617 if (a
->tries_left
< b
->tries_left
)
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
)
1623 if (a
->tries_done
> b
->tries_done
)
1629 static void config_sort_entries(Config
*config
) {
1632 sort_pointer_array((void**) config
->entries
, config
->entry_count
, (compare_pointer_func_t
) config_entry_compare
);
1635 static INTN
config_entry_find(Config
*config
, const CHAR16
*needle
) {
1641 for (UINTN i
= 0; i
< config
->entry_count
; i
++)
1642 if (MetaiMatch(config
->entries
[i
]->id
, (CHAR16
*) needle
))
1648 static void config_default_entry_select(Config
*config
) {
1653 i
= config_entry_find(config
, config
->entry_oneshot
);
1655 config
->idx_default
= i
;
1659 i
= config_entry_find(config
, config
->use_saved_entry_efivar
? config
->entry_saved
: config
->entry_default_efivar
);
1661 config
->idx_default
= i
;
1662 config
->idx_default_efivar
= i
;
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
);
1670 i
= config_entry_find(config
, config
->entry_default_config
);
1672 config
->idx_default
= i
;
1676 /* select the last suitable entry */
1677 i
= config
->entry_count
;
1679 if (config
->entries
[i
]->no_autoselect
)
1681 config
->idx_default
= i
;
1685 /* no entry found */
1686 config
->idx_default
= -1;
1689 static BOOLEAN
find_nonunique(ConfigEntry
**entries
, UINTN entry_count
) {
1690 BOOLEAN non_unique
= FALSE
;
1694 for (UINTN i
= 0; i
< entry_count
; i
++)
1695 entries
[i
]->non_unique
= FALSE
;
1697 for (UINTN i
= 0; i
< entry_count
; i
++)
1698 for (UINTN k
= 0; k
< entry_count
; k
++) {
1701 if (StrCmp(entries
[i
]->title_show
, entries
[k
]->title_show
) != 0)
1704 non_unique
= entries
[i
]->non_unique
= entries
[k
]->non_unique
= TRUE
;
1710 /* generate a unique title, avoiding non-distinguishable menu entries */
1711 static void config_title_generate(Config
*config
) {
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
);
1721 if (!find_nonunique(config
->entries
, config
->entry_count
))
1724 /* add version to non-unique titles */
1725 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
1728 if (!config
->entries
[i
]->non_unique
)
1730 if (!config
->entries
[i
]->version
)
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
;
1738 if (!find_nonunique(config
->entries
, config
->entry_count
))
1741 /* add machine-id to non-unique titles */
1742 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
1744 _cleanup_freepool_ CHAR16
*m
= NULL
;
1746 if (!config
->entries
[i
]->non_unique
)
1748 if (!config
->entries
[i
]->machine_id
)
1751 m
= xstrdup(config
->entries
[i
]->machine_id
);
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
;
1758 if (!find_nonunique(config
->entries
, config
->entry_count
))
1761 /* add file name to non-unique titles */
1762 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
1765 if (!config
->entries
[i
]->non_unique
)
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
;
1774 static BOOLEAN
config_entry_add_call(
1777 const CHAR16
*title
,
1778 EFI_STATUS (*call
)(void)) {
1787 entry
= xnew(ConfigEntry
, 1);
1788 *entry
= (ConfigEntry
) {
1790 .title
= xstrdup(title
),
1792 .no_autoselect
= TRUE
,
1793 .tries_done
= UINTN_MAX
,
1794 .tries_left
= UINTN_MAX
,
1797 config_add_entry(config
, entry
);
1801 static ConfigEntry
*config_entry_add_loader(
1804 enum loader_type type
,
1807 const CHAR16
*title
,
1808 const CHAR16
*loader
,
1809 const CHAR16
*version
) {
1819 entry
= xnew(ConfigEntry
, 1);
1820 *entry
= (ConfigEntry
) {
1822 .title
= xstrdup(title
),
1823 .version
= version
? xstrdup(version
) : NULL
,
1825 .loader
= xstrdup(loader
),
1828 .tries_done
= UINTN_MAX
,
1829 .tries_left
= UINTN_MAX
,
1834 config_add_entry(config
, entry
);
1838 static BOOLEAN
is_sd_boot(EFI_FILE
*root_dir
, const CHAR16
*loader_path
) {
1840 const CHAR8
*sections
[] = {
1841 (CHAR8
*)".sdmagic",
1844 UINTN offset
= 0, size
= 0, read
;
1845 _cleanup_freepool_ CHAR8
*content
= NULL
;
1848 assert(loader_path
);
1850 err
= pe_file_locate_sections(root_dir
, loader_path
, sections
, &offset
, &size
);
1851 if (EFI_ERROR(err
) || size
!= sizeof(magic
))
1854 err
= file_read(root_dir
, loader_path
, offset
, size
, &content
, &read
);
1855 if (EFI_ERROR(err
) || size
!= read
)
1858 return CompareMem(content
, magic
, sizeof(magic
)) == 0;
1861 static BOOLEAN
config_entry_add_loader_auto(
1865 const CHAR16
*loaded_image_path
,
1868 const CHAR16
*title
,
1869 const CHAR16
*loader
) {
1871 EFI_FILE_HANDLE handle
;
1880 assert(loader
|| loaded_image_path
);
1882 if (!config
->auto_entries
)
1885 if (loaded_image_path
) {
1886 loader
= L
"\\EFI\\BOOT\\BOOT" EFI_MACHINE_TYPE_NAME
".efi";
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.
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"))
1899 /* check existence */
1900 err
= root_dir
->Open(root_dir
, &handle
, (CHAR16
*) loader
, EFI_FILE_MODE_READ
, 0ULL);
1903 handle
->Close(handle
);
1905 entry
= config_entry_add_loader(config
, device
, LOADER_UNDEFINED
, id
, key
, title
, loader
, NULL
);
1909 /* do not boot right away into auto-detected entries */
1910 entry
->no_autoselect
= TRUE
;
1915 static void config_entry_add_osx(Config
*config
) {
1917 UINTN handle_count
= 0;
1918 _cleanup_freepool_ EFI_HANDLE
*handles
= NULL
;
1922 if (!config
->auto_entries
)
1925 err
= LibLocateHandle(ByProtocol
, &FileSystemProtocol
, NULL
, &handle_count
, &handles
);
1926 if (!EFI_ERROR(err
)) {
1927 for (UINTN i
= 0; i
< handle_count
; i
++) {
1931 root
= LibOpenRoot(handles
[i
]);
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");
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
;
1953 if (!config
->auto_entries
)
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
);
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");
1966 static void config_entry_add_linux(
1969 EFI_FILE
*root_dir
) {
1971 _cleanup_(FileHandleClosep
) EFI_FILE_HANDLE linux_dir
= NULL
;
1972 _cleanup_freepool_ EFI_FILE_INFO
*f
= NULL
;
1981 err
= open_directory(root_dir
, L
"\\EFI\\Linux", &linux_dir
);
1992 static const CHAR8
* const sections
[_SECTION_MAX
+ 1] = {
1993 [SECTION_CMDLINE
] = (const CHAR8
*) ".cmdline",
1994 [SECTION_OSREL
] = (const CHAR8
*) ".osrel",
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
,
2001 const CHAR16
*good_name
, *good_version
;
2002 _cleanup_freepool_ CHAR8
*content
= NULL
;
2003 UINTN offs
[_SECTION_MAX
] = {};
2004 UINTN szs
[_SECTION_MAX
] = {};
2009 err
= readdir_harder(linux_dir
, &f
, &f_size
);
2010 if (f_size
== 0 || EFI_ERROR(err
))
2013 if (f
->FileName
[0] == '.')
2015 if (FLAGS_SET(f
->Attribute
, EFI_FILE_DIRECTORY
))
2017 if (!endswith_no_case(f
->FileName
, L
".efi"))
2019 if (startswith(f
->FileName
, L
"auto-"))
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)
2027 err
= file_read(linux_dir
, f
->FileName
, offs
[SECTION_OSREL
], szs
[SECTION_OSREL
], &content
, NULL
);
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
);
2039 if (strcmpa((const CHAR8
*) "IMAGE_ID", key
) == 0) {
2040 FreePool(os_image_id
);
2041 os_image_id
= xstra_to_str(value
);
2045 if (strcmpa((const CHAR8
*) "NAME", key
) == 0) {
2047 os_name
= xstra_to_str(value
);
2051 if (strcmpa((const CHAR8
*) "ID", key
) == 0) {
2053 os_id
= xstra_to_str(value
);
2057 if (strcmpa((const CHAR8
*) "IMAGE_VERSION", key
) == 0) {
2058 FreePool(os_image_version
);
2059 os_image_version
= xstra_to_str(value
);
2063 if (strcmpa((const CHAR8
*) "VERSION", key
) == 0) {
2064 FreePool(os_version
);
2065 os_version
= xstra_to_str(value
);
2069 if (strcmpa((const CHAR8
*) "VERSION_ID", key
) == 0) {
2070 FreePool(os_version_id
);
2071 os_version_id
= xstra_to_str(value
);
2075 if (strcmpa((const CHAR8
*) "BUILD_ID", key
) == 0) {
2076 FreePool(os_build_id
);
2077 os_build_id
= xstra_to_str(value
);
2082 if (!bootspec_pick_name_version(
2095 path
= xpool_print(L
"\\EFI\\Linux\\%s", f
->FileName
);
2096 entry
= config_entry_add_loader(
2106 config_entry_parse_tries(entry
, L
"\\EFI\\Linux", f
->FileName
, L
".efi");
2108 if (szs
[SECTION_CMDLINE
] == 0)
2111 content
= mfree(content
);
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';
2120 entry
->options
= xstra_to_str(content
);
2125 static void config_load_xbootldr(
2127 EFI_HANDLE
*device
) {
2129 EFI_HANDLE new_device
;
2136 err
= xbootldr_open(device
, &new_device
, &root_dir
);
2140 config_entry_add_linux(config
, new_device
, root_dir
);
2141 config_load_entries(config
, new_device
, root_dir
, NULL
);
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
) {
2150 _cleanup_(devicetree_cleanup
) struct devicetree_state dtstate
= {};
2152 _cleanup_freepool_ EFI_DEVICE_PATH
*path
= NULL
;
2159 path
= FileDevicePath(entry
->device
, entry
->loader
);
2161 return log_error_status_stall(EFI_INVALID_PARAMETER
, L
"Error getting device path.");
2163 err
= BS
->LoadImage(FALSE
, parent_image
, path
, NULL
, 0, &image
);
2165 return log_error_status_stall(err
, L
"Error loading %s: %r", entry
->loader
, err
);
2167 if (entry
->devicetree
) {
2168 err
= devicetree_install(&dtstate
, root_dir
, entry
->devicetree
);
2170 return log_error_status_stall(err
, L
"Error loading %s: %r", entry
->devicetree
, err
);
2173 if (config
->options_edit
)
2174 options
= config
->options_edit
;
2175 else if (entry
->options
)
2176 options
= entry
->options
;
2180 EFI_LOADED_IMAGE
*loaded_image
;
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
);
2188 loaded_image
->LoadOptions
= options
;
2189 loaded_image
->LoadOptionsSize
= StrSize(loaded_image
->LoadOptions
);
2191 /* Try to log any options to the TPM, especially to catch manually edited options */
2192 (void) tpm_log_load_options(options
);
2195 efivar_set_time_usec(LOADER_GUID
, L
"LoaderTimeExecUSec", 0);
2196 err
= BS
->StartImage(image
, NULL
, NULL
);
2198 BS
->UnloadImage(image
);
2202 static void config_free(Config
*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
);
2212 static void config_write_entries_to_variable(Config
*config
) {
2213 _cleanup_freepool_ CHAR8
*buffer
= NULL
;
2219 for (UINTN i
= 0; i
< config
->entry_count
; i
++)
2220 sz
+= StrSize(config
->entries
[i
]->id
);
2222 p
= buffer
= xallocate_pool(sz
);
2224 for (UINTN i
= 0; i
< config
->entry_count
; i
++) {
2227 l
= StrSize(config
->entries
[i
]->id
);
2228 CopyMem(p
, config
->entries
[i
]->id
, l
);
2233 assert(p
== buffer
+ sz
);
2235 /* Store the full list of discovered entries. */
2236 (void) efivar_set_raw(LOADER_GUID
, L
"LoaderEntries", buffer
, sz
, 0);
2239 static void save_selected_entry(const Config
*config
, const ConfigEntry
*entry
) {
2242 assert(!entry
->call
);
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);
2247 /* Do not save or delete if this was a oneshot boot. */
2248 if (streq_ptr(config
->entry_oneshot
, entry
->id
))
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
))
2256 (void) efivar_set(LOADER_GUID
, L
"LoaderEntryLastBooted", entry
->id
, EFI_VARIABLE_NON_VOLATILE
);
2258 /* Delete the non-volatile var if not needed. */
2259 (void) efivar_set(LOADER_GUID
, L
"LoaderEntryLastBooted", NULL
, EFI_VARIABLE_NON_VOLATILE
);
2262 static void export_variables(
2263 EFI_LOADED_IMAGE
*loaded_image
,
2264 const CHAR16
*loaded_image_path
,
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
|
2278 _cleanup_freepool_ CHAR16
*infostr
= NULL
, *typestr
= NULL
;
2281 assert(loaded_image
);
2282 assert(loaded_image_path
);
2284 efivar_set_time_usec(LOADER_GUID
, L
"LoaderTimeInitUSec", init_usec
);
2285 efivar_set(LOADER_GUID
, L
"LoaderInfo", L
"systemd-boot " GIT_VERSION
, 0);
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);
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);
2293 (void) efivar_set_uint64_le(LOADER_GUID
, L
"LoaderFeatures", loader_features
, 0);
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);
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);
2303 static void config_load_all_entries(
2305 EFI_LOADED_IMAGE
*loaded_image
,
2306 const CHAR16
*loaded_image_path
,
2307 EFI_FILE
*root_dir
) {
2310 assert(loaded_image
);
2311 assert(loaded_image_path
);
2314 config_load_defaults(config
, root_dir
);
2316 /* scan /EFI/Linux/ directory */
2317 config_entry_add_linux(config
, loaded_image
->DeviceHandle
, root_dir
);
2319 /* scan /loader/entries/\*.conf files */
2320 config_load_entries(config
, loaded_image
->DeviceHandle
, root_dir
, loaded_image_path
);
2322 /* Similar, but on any XBOOTLDR partition */
2323 config_load_xbootldr(config
, loaded_image
->DeviceHandle
);
2325 /* sort entries after version number */
2326 config_sort_entries(config
);
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
);
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
);
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
;
2350 BOOLEAN menu
= FALSE
;
2352 InitializeLib(image
, sys_table
);
2353 init_usec
= time_usec();
2355 err
= BS
->OpenProtocol(image
,
2356 &LoadedImageProtocol
,
2357 (void **)&loaded_image
,
2360 EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
2362 return log_error_status_stall(err
, L
"Error getting a LoadedImageProtocol handle: %r", err
);
2364 loaded_image_path
= DevicePathToStr(loaded_image
->FilePath
);
2365 if (!loaded_image_path
)
2368 export_variables(loaded_image
, loaded_image_path
, init_usec
);
2370 root_dir
= LibOpenRoot(loaded_image
->DeviceHandle
);
2372 return log_error_status_stall(EFI_LOAD_ERROR
, L
"Unable to open root directory.", EFI_LOAD_ERROR
);
2374 if (secure_boot_enabled() && shim_loaded()) {
2375 err
= security_policy_install();
2377 return log_error_status_stall(err
, L
"Error installing security policy: %r", err
);
2380 (void) load_drivers(image
, loaded_image
, root_dir
);
2382 config_load_all_entries(&config
, loaded_image
, loaded_image_path
, root_dir
);
2384 if (config
.entry_count
== 0) {
2385 log_error_stall(L
"No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
2389 config_write_entries_to_variable(&config
);
2391 config_title_generate(&config
);
2393 /* select entry by configured pattern or EFI LoaderDefaultEntry= variable */
2394 config_default_entry_select(&config
);
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;
2403 /* select entry or show menu when key is pressed or timeout is set */
2404 if (config
.force_menu
|| config
.timeout_sec
> 0)
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
)) {
2414 /* find matching key in config entries */
2415 idx
= entry_lookup_key(&config
, config
.idx_default
, KEYCHAR(key
));
2417 config
.idx_default
= idx
;
2426 entry
= config
.entries
[config
.idx_default
];
2428 efivar_set_time_usec(LOADER_GUID
, L
"LoaderTimeMenuUSec", 0);
2429 if (!menu_run(&config
, &entry
, loaded_image_path
))
2433 /* run special entry like "reboot" */
2439 config_entry_bump_counters(entry
, root_dir
);
2440 save_selected_entry(&config
, entry
);
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
);
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
);
2453 config
.timeout_sec
= 0;
2457 BS
->CloseProtocol(image
, &LoadedImageProtocol
, image
, NULL
);