1 /* SPDX-License-Identifier: LGPL-2.1+ */
15 #ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI
16 #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL
19 /* magic string to find in the binary image */
20 static const char __attribute__((used
)) magic
[] = "#### LoaderInfo: systemd-boot " GIT_VERSION
" ####";
22 static const EFI_GUID global_guid
= EFI_GLOBAL_VARIABLE
;
31 CHAR16
*id
; /* The identifier for this entry (note that this id is not necessarily unique though!) */
37 enum loader_type type
;
41 EFI_STATUS (*call
)(VOID
);
42 BOOLEAN no_autoselect
;
52 ConfigEntry
**entries
;
55 INTN idx_default_efivar
;
57 UINTN timeout_sec_config
;
58 INTN timeout_sec_efivar
;
59 CHAR16
*entry_default_pattern
;
60 CHAR16
*entry_oneshot
;
64 BOOLEAN auto_firmware
;
67 enum console_mode_change_type console_mode_change
;
70 static VOID
cursor_left(UINTN
*cursor
, UINTN
*first
) {
73 else if ((*first
) > 0)
77 static VOID
cursor_right(
83 if ((*cursor
)+1 < x_max
)
85 else if ((*first
) + (*cursor
) < len
)
89 static BOOLEAN
line_edit(
95 _cleanup_freepool_ CHAR16
*line
= NULL
, *print
= NULL
;
96 UINTN size
, len
, first
, cursor
, clear
;
101 size
= StrLen(line_in
) + 1024;
102 line
= AllocatePool(size
* sizeof(CHAR16
));
103 StrCpy(line
, line_in
);
105 print
= AllocatePool((x_max
+1) * sizeof(CHAR16
));
107 uefi_call_wrapper(ST
->ConOut
->EnableCursor
, 2, ST
->ConOut
, TRUE
);
122 CopyMem(print
, line
+ first
, i
* sizeof(CHAR16
));
123 while (clear
> 0 && i
< x_max
-1) {
129 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_pos
);
130 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, print
);
131 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
133 err
= console_key_read(&key
, TRUE
);
138 case KEYPRESS(0, SCAN_ESC
, 0):
139 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'c'):
140 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'g'):
141 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('c')):
142 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('g')):
146 case KEYPRESS(0, SCAN_HOME
, 0):
147 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'a'):
148 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('a')):
149 /* beginning-of-line */
154 case KEYPRESS(0, SCAN_END
, 0):
155 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'e'):
156 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('e')):
158 cursor
= len
- first
;
159 if (cursor
+1 >= x_max
) {
161 first
= len
- (x_max
-1);
165 case KEYPRESS(0, SCAN_DOWN
, 0):
166 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'f'):
167 case KEYPRESS(EFI_CONTROL_PRESSED
, SCAN_RIGHT
, 0):
169 while (line
[first
+ cursor
] == ' ')
170 cursor_right(&cursor
, &first
, x_max
, len
);
171 while (line
[first
+ cursor
] && line
[first
+ cursor
] != ' ')
172 cursor_right(&cursor
, &first
, x_max
, len
);
173 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
176 case KEYPRESS(0, SCAN_UP
, 0):
177 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'b'):
178 case KEYPRESS(EFI_CONTROL_PRESSED
, SCAN_LEFT
, 0):
180 if ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] == ' ') {
181 cursor_left(&cursor
, &first
);
182 while ((first
+ cursor
) > 0 && line
[first
+ cursor
] == ' ')
183 cursor_left(&cursor
, &first
);
185 while ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] != ' ')
186 cursor_left(&cursor
, &first
);
187 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
190 case KEYPRESS(0, SCAN_RIGHT
, 0):
191 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'f'):
192 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('f')):
194 if (first
+ cursor
== len
)
196 cursor_right(&cursor
, &first
, x_max
, len
);
197 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
200 case KEYPRESS(0, SCAN_LEFT
, 0):
201 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'b'):
202 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('b')):
204 cursor_left(&cursor
, &first
);
205 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
208 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'd'):
211 for (i
= first
+ cursor
; i
< len
&& line
[i
] == ' '; i
++)
213 for (; i
< len
&& line
[i
] != ' '; i
++)
216 for (i
= first
+ cursor
; i
+ clear
< len
; i
++)
217 line
[i
] = line
[i
+ clear
];
222 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'w'):
223 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('w')):
224 case KEYPRESS(EFI_ALT_PRESSED
, 0, CHAR_BACKSPACE
):
225 /* backward-kill-word */
227 if ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] == ' ') {
228 cursor_left(&cursor
, &first
);
230 while ((first
+ cursor
) > 0 && line
[first
+ cursor
] == ' ') {
231 cursor_left(&cursor
, &first
);
235 while ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] != ' ') {
236 cursor_left(&cursor
, &first
);
239 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
241 for (i
= first
+ cursor
; i
+ clear
< len
; i
++)
242 line
[i
] = line
[i
+ clear
];
247 case KEYPRESS(0, SCAN_DELETE
, 0):
248 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'd'):
249 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('d')):
252 if (first
+ cursor
== len
)
254 for (i
= first
+ cursor
; i
< len
; i
++)
260 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'k'):
261 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('k')):
263 line
[first
+ cursor
] = '\0';
264 clear
= len
- (first
+ cursor
);
265 len
= first
+ cursor
;
268 case KEYPRESS(0, 0, CHAR_LINEFEED
):
269 case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN
):
270 if (StrCmp(line
, line_in
) != 0) {
278 case KEYPRESS(0, 0, CHAR_BACKSPACE
):
281 if (first
== 0 && cursor
== 0)
283 for (i
= first
+ cursor
-1; i
< len
; i
++)
289 if (cursor
> 0 || first
== 0)
291 /* show full line if it fits */
297 /* jump left to see what we delete */
307 case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
308 case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
311 for (i
= len
; i
> first
+ cursor
; i
--)
313 line
[first
+ cursor
] = KEYCHAR(key
);
316 if (cursor
+1 < x_max
)
318 else if (first
+ cursor
< len
)
324 uefi_call_wrapper(ST
->ConOut
->EnableCursor
, 2, ST
->ConOut
, FALSE
);
328 static UINTN
entry_lookup_key(Config
*config
, UINTN start
, CHAR16 key
) {
334 /* select entry by number key */
335 if (key
>= '1' && key
<= '9') {
337 if (i
> config
->entry_count
)
338 i
= config
->entry_count
;
342 /* find matching key in config entries */
343 for (i
= start
; i
< config
->entry_count
; i
++)
344 if (config
->entries
[i
]->key
== key
)
347 for (i
= 0; i
< start
; i
++)
348 if (config
->entries
[i
]->key
== key
)
354 static VOID
print_status(Config
*config
, CHAR16
*loaded_image_path
) {
357 _cleanup_freepool_ CHAR8
*bootvar
= NULL
, *modevar
= NULL
, *indvar
= NULL
;
358 _cleanup_freepool_ CHAR16
*partstr
= NULL
, *defaultstr
= NULL
;
361 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
362 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
364 Print(L
"systemd-boot version: " GIT_VERSION
"\n");
365 Print(L
"architecture: " EFI_MACHINE_TYPE_NAME
"\n");
366 Print(L
"loaded image: %s\n", loaded_image_path
);
367 Print(L
"UEFI specification: %d.%02d\n", ST
->Hdr
.Revision
>> 16, ST
->Hdr
.Revision
& 0xffff);
368 Print(L
"firmware vendor: %s\n", ST
->FirmwareVendor
);
369 Print(L
"firmware version: %d.%02d\n", ST
->FirmwareRevision
>> 16, ST
->FirmwareRevision
& 0xffff);
371 if (uefi_call_wrapper(ST
->ConOut
->QueryMode
, 4, ST
->ConOut
, ST
->ConOut
->Mode
->Mode
, &x
, &y
) == EFI_SUCCESS
)
372 Print(L
"console size: %d x %d\n", x
, y
);
374 if (efivar_get_raw(&global_guid
, L
"SecureBoot", &bootvar
, &size
) == EFI_SUCCESS
)
375 Print(L
"SecureBoot: %s\n", yes_no(*bootvar
> 0));
377 if (efivar_get_raw(&global_guid
, L
"SetupMode", &modevar
, &size
) == EFI_SUCCESS
)
378 Print(L
"SetupMode: %s\n", *modevar
> 0 ? L
"setup" : L
"user");
381 Print(L
"Shim: present\n");
383 if (efivar_get_raw(&global_guid
, L
"OsIndicationsSupported", &indvar
, &size
) == EFI_SUCCESS
)
384 Print(L
"OsIndicationsSupported: %d\n", (UINT64
)*indvar
);
386 Print(L
"\n--- press key ---\n\n");
387 console_key_read(&key
, TRUE
);
389 Print(L
"timeout: %u\n", config
->timeout_sec
);
390 if (config
->timeout_sec_efivar
>= 0)
391 Print(L
"timeout (EFI var): %d\n", config
->timeout_sec_efivar
);
392 Print(L
"timeout (config): %u\n", config
->timeout_sec_config
);
393 if (config
->entry_default_pattern
)
394 Print(L
"default pattern: '%s'\n", config
->entry_default_pattern
);
395 Print(L
"editor: %s\n", yes_no(config
->editor
));
396 Print(L
"auto-entries: %s\n", yes_no(config
->auto_entries
));
397 Print(L
"auto-firmware: %s\n", yes_no(config
->auto_firmware
));
400 Print(L
"config entry count: %d\n", config
->entry_count
);
401 Print(L
"entry selected idx: %d\n", config
->idx_default
);
402 if (config
->idx_default_efivar
>= 0)
403 Print(L
"entry EFI var idx: %d\n", config
->idx_default_efivar
);
406 if (efivar_get_int(L
"LoaderConfigTimeout", &i
) == EFI_SUCCESS
)
407 Print(L
"LoaderConfigTimeout: %u\n", i
);
409 if (config
->entry_oneshot
)
410 Print(L
"LoaderEntryOneShot: %s\n", config
->entry_oneshot
);
411 if (efivar_get(L
"LoaderDevicePartUUID", &partstr
) == EFI_SUCCESS
)
412 Print(L
"LoaderDevicePartUUID: %s\n", partstr
);
413 if (efivar_get(L
"LoaderEntryDefault", &defaultstr
) == EFI_SUCCESS
)
414 Print(L
"LoaderEntryDefault: %s\n", defaultstr
);
416 Print(L
"\n--- press key ---\n\n");
417 console_key_read(&key
, TRUE
);
419 for (i
= 0; i
< config
->entry_count
; i
++) {
422 if (key
== KEYPRESS(0, SCAN_ESC
, 0) || key
== KEYPRESS(0, 0, 'q'))
425 entry
= config
->entries
[i
];
426 Print(L
"config entry: %d/%d\n", i
+1, config
->entry_count
);
428 Print(L
"id '%s'\n", entry
->id
);
429 Print(L
"title show '%s'\n", entry
->title_show
);
431 Print(L
"title '%s'\n", entry
->title
);
433 Print(L
"version '%s'\n", entry
->version
);
434 if (entry
->machine_id
)
435 Print(L
"machine-id '%s'\n", entry
->machine_id
);
437 EFI_DEVICE_PATH
*device_path
;
439 device_path
= DevicePathFromHandle(entry
->device
);
441 _cleanup_freepool_ CHAR16
*str
;
443 str
= DevicePathToStr(device_path
);
444 Print(L
"device handle '%s'\n", str
);
448 Print(L
"loader '%s'\n", entry
->loader
);
450 Print(L
"options '%s'\n", entry
->options
);
451 Print(L
"auto-select %s\n", yes_no(!entry
->no_autoselect
));
453 Print(L
"internal call yes\n");
455 if (entry
->tries_left
!= (UINTN
) -1)
456 Print(L
"counting boots yes\n"
459 "current path %s\\%s\n"
460 "next path %s\\%s\n",
463 entry
->path
, entry
->current_name
,
464 entry
->path
, entry
->next_name
);
466 Print(L
"\n--- press key ---\n\n");
467 console_key_read(&key
, TRUE
);
470 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
473 static BOOLEAN
menu_run(
475 ConfigEntry
**chosen_entry
,
476 CHAR16
*loaded_image_path
) {
481 UINTN idx_highlight_prev
;
497 BOOLEAN exit
= FALSE
;
499 BOOLEAN wait
= FALSE
;
500 BOOLEAN cleared_screen
= FALSE
;
502 graphics_mode(FALSE
);
503 uefi_call_wrapper(ST
->ConIn
->Reset
, 2, ST
->ConIn
, FALSE
);
504 uefi_call_wrapper(ST
->ConOut
->EnableCursor
, 2, ST
->ConOut
, FALSE
);
505 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
507 /* draw a single character to make ClearScreen work on some firmware */
508 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, L
" ");
510 if (config
->console_mode_change
!= CONSOLE_MODE_KEEP
) {
511 err
= console_set_mode(&config
->console_mode
, config
->console_mode_change
);
513 cleared_screen
= TRUE
;
517 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
519 if (config
->console_mode_change
!= CONSOLE_MODE_KEEP
&& EFI_ERROR(err
))
520 Print(L
"Error switching console mode to %ld: %r.\r", (UINT64
)config
->console_mode
, err
);
522 err
= uefi_call_wrapper(ST
->ConOut
->QueryMode
, 4, ST
->ConOut
, ST
->ConOut
->Mode
->Mode
, &x_max
, &y_max
);
523 if (EFI_ERROR(err
)) {
528 /* we check 10 times per second for a keystroke */
529 if (config
->timeout_sec
> 0)
530 timeout_remain
= config
->timeout_sec
* 10;
534 idx_highlight
= config
->idx_default
;
535 idx_highlight_prev
= 0;
537 visible_max
= y_max
- 2;
539 if ((UINTN
)config
->idx_default
>= visible_max
)
540 idx_first
= config
->idx_default
-1;
544 idx_last
= idx_first
+ visible_max
-1;
549 /* length of the longest entry */
551 for (i
= 0; i
< config
->entry_count
; i
++) {
554 entry_len
= StrLen(config
->entries
[i
]->title_show
);
555 if (line_width
< entry_len
)
556 line_width
= entry_len
;
558 if (line_width
> x_max
-6)
559 line_width
= x_max
-6;
561 /* offsets to center the entries on the screen */
562 x_start
= (x_max
- (line_width
)) / 2;
563 if (config
->entry_count
< visible_max
)
564 y_start
= ((visible_max
- config
->entry_count
) / 2) + 1;
568 /* menu entries title lines */
569 lines
= AllocatePool(sizeof(CHAR16
*) * config
->entry_count
);
570 for (i
= 0; i
< config
->entry_count
; i
++) {
573 lines
[i
] = AllocatePool(((x_max
+1) * sizeof(CHAR16
)));
574 for (j
= 0; j
< x_start
; j
++)
577 for (k
= 0; config
->entries
[i
]->title_show
[k
] != '\0' && j
< x_max
; j
++, k
++)
578 lines
[i
][j
] = config
->entries
[i
]->title_show
[k
];
580 for (; j
< x_max
; j
++)
582 lines
[i
][x_max
] = '\0';
586 clearline
= AllocatePool((x_max
+1) * sizeof(CHAR16
));
587 for (i
= 0; i
< x_max
; i
++)
595 for (i
= 0; i
< config
->entry_count
; i
++) {
596 if (i
< idx_first
|| i
> idx_last
)
598 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_start
+ i
- idx_first
);
599 if (i
== idx_highlight
)
600 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
,
601 EFI_BLACK
|EFI_BACKGROUND_LIGHTGRAY
);
603 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
,
604 EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
605 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, lines
[i
]);
606 if ((INTN
)i
== config
->idx_default_efivar
) {
607 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, x_start
-3, y_start
+ i
- idx_first
);
608 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, L
"=>");
612 } else if (highlight
) {
613 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_start
+ idx_highlight_prev
- idx_first
);
614 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
615 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, lines
[idx_highlight_prev
]);
616 if ((INTN
)idx_highlight_prev
== config
->idx_default_efivar
) {
617 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, x_start
-3, y_start
+ idx_highlight_prev
- idx_first
);
618 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, L
"=>");
621 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_start
+ idx_highlight
- idx_first
);
622 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_BLACK
|EFI_BACKGROUND_LIGHTGRAY
);
623 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, lines
[idx_highlight
]);
624 if ((INTN
)idx_highlight
== config
->idx_default_efivar
) {
625 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, x_start
-3, y_start
+ idx_highlight
- idx_first
);
626 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, L
"=>");
631 if (timeout_remain
> 0) {
633 status
= PoolPrint(L
"Boot in %d sec.", (timeout_remain
+ 5) / 10);
636 /* print status at last line of screen */
642 len
= StrLen(status
);
644 x
= (x_max
- len
) / 2;
647 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
648 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_max
-1);
649 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, clearline
+ (x_max
- x
));
650 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, status
);
651 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, clearline
+1 + x
+ len
);
654 err
= console_key_read(&key
, wait
);
655 if (EFI_ERROR(err
)) {
656 /* timeout reached */
657 if (timeout_remain
== 0) {
662 /* sleep and update status */
663 if (timeout_remain
> 0) {
664 uefi_call_wrapper(BS
->Stall
, 1, 100 * 1000);
669 /* timeout disabled, wait for next key */
676 /* clear status after keystroke */
680 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
681 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_max
-1);
682 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, clearline
+1);
685 idx_highlight_prev
= idx_highlight
;
688 case KEYPRESS(0, SCAN_UP
, 0):
689 case KEYPRESS(0, 0, 'k'):
690 if (idx_highlight
> 0)
694 case KEYPRESS(0, SCAN_DOWN
, 0):
695 case KEYPRESS(0, 0, 'j'):
696 if (idx_highlight
< config
->entry_count
-1)
700 case KEYPRESS(0, SCAN_HOME
, 0):
701 case KEYPRESS(EFI_ALT_PRESSED
, 0, '<'):
702 if (idx_highlight
> 0) {
708 case KEYPRESS(0, SCAN_END
, 0):
709 case KEYPRESS(EFI_ALT_PRESSED
, 0, '>'):
710 if (idx_highlight
< config
->entry_count
-1) {
712 idx_highlight
= config
->entry_count
-1;
716 case KEYPRESS(0, SCAN_PAGE_UP
, 0):
717 if (idx_highlight
> visible_max
)
718 idx_highlight
-= visible_max
;
723 case KEYPRESS(0, SCAN_PAGE_DOWN
, 0):
724 idx_highlight
+= visible_max
;
725 if (idx_highlight
> config
->entry_count
-1)
726 idx_highlight
= config
->entry_count
-1;
729 case KEYPRESS(0, 0, CHAR_LINEFEED
):
730 case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN
):
734 case KEYPRESS(0, SCAN_F1
, 0):
735 case KEYPRESS(0, 0, 'h'):
736 case KEYPRESS(0, 0, '?'):
737 status
= StrDuplicate(L
"(d)efault, (t/T)timeout, (e)dit, (v)ersion (Q)uit (P)rint (h)elp");
740 case KEYPRESS(0, 0, 'Q'):
745 case KEYPRESS(0, 0, 'd'):
746 if (config
->idx_default_efivar
!= (INTN
)idx_highlight
) {
747 /* store the selected entry in a persistent EFI variable */
748 efivar_set(L
"LoaderEntryDefault", config
->entries
[idx_highlight
]->id
, TRUE
);
749 config
->idx_default_efivar
= idx_highlight
;
750 status
= StrDuplicate(L
"Default boot entry selected.");
752 /* clear the default entry EFI variable */
753 efivar_set(L
"LoaderEntryDefault", NULL
, TRUE
);
754 config
->idx_default_efivar
= -1;
755 status
= StrDuplicate(L
"Default boot entry cleared.");
760 case KEYPRESS(0, 0, '-'):
761 case KEYPRESS(0, 0, 'T'):
762 if (config
->timeout_sec_efivar
> 0) {
763 config
->timeout_sec_efivar
--;
764 efivar_set_int(L
"LoaderConfigTimeout", config
->timeout_sec_efivar
, TRUE
);
765 if (config
->timeout_sec_efivar
> 0)
766 status
= PoolPrint(L
"Menu timeout set to %d sec.", config
->timeout_sec_efivar
);
768 status
= StrDuplicate(L
"Menu disabled. Hold down key at bootup to show menu.");
769 } else if (config
->timeout_sec_efivar
<= 0){
770 config
->timeout_sec_efivar
= -1;
771 efivar_set(L
"LoaderConfigTimeout", NULL
, TRUE
);
772 if (config
->timeout_sec_config
> 0)
773 status
= PoolPrint(L
"Menu timeout of %d sec is defined by configuration file.",
774 config
->timeout_sec_config
);
776 status
= StrDuplicate(L
"Menu disabled. Hold down key at bootup to show menu.");
780 case KEYPRESS(0, 0, '+'):
781 case KEYPRESS(0, 0, 't'):
782 if (config
->timeout_sec_efivar
== -1 && config
->timeout_sec_config
== 0)
783 config
->timeout_sec_efivar
++;
784 config
->timeout_sec_efivar
++;
785 efivar_set_int(L
"LoaderConfigTimeout", config
->timeout_sec_efivar
, TRUE
);
786 if (config
->timeout_sec_efivar
> 0)
787 status
= PoolPrint(L
"Menu timeout set to %d sec.",
788 config
->timeout_sec_efivar
);
790 status
= StrDuplicate(L
"Menu disabled. Hold down key at bootup to show menu.");
793 case KEYPRESS(0, 0, 'e'):
794 /* only the options of configured entries can be edited */
795 if (!config
->editor
|| config
->entries
[idx_highlight
]->type
== LOADER_UNDEFINED
)
797 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
798 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_max
-1);
799 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, clearline
+1);
800 if (line_edit(config
->entries
[idx_highlight
]->options
, &config
->options_edit
, x_max
-1, y_max
-1))
802 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_max
-1);
803 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, clearline
+1);
806 case KEYPRESS(0, 0, 'v'):
807 status
= PoolPrint(L
"systemd-boot " GIT_VERSION
" (" EFI_MACHINE_TYPE_NAME
"), UEFI Specification %d.%02d, Vendor %s %d.%02d",
808 ST
->Hdr
.Revision
>> 16, ST
->Hdr
.Revision
& 0xffff,
809 ST
->FirmwareVendor
, ST
->FirmwareRevision
>> 16, ST
->FirmwareRevision
& 0xffff);
812 case KEYPRESS(0, 0, 'P'):
813 print_status(config
, loaded_image_path
);
817 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'l'):
818 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('l')):
823 /* jump with a hotkey directly to a matching entry */
824 idx
= entry_lookup_key(config
, idx_highlight
+1, KEYCHAR(key
));
831 if (idx_highlight
> idx_last
) {
832 idx_last
= idx_highlight
;
833 idx_first
= 1 + idx_highlight
- visible_max
;
835 } else if (idx_highlight
< idx_first
) {
836 idx_first
= idx_highlight
;
837 idx_last
= idx_highlight
+ visible_max
-1;
841 if (!refresh
&& idx_highlight
!= idx_highlight_prev
)
845 *chosen_entry
= config
->entries
[idx_highlight
];
847 for (i
= 0; i
< config
->entry_count
; i
++)
852 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_WHITE
|EFI_BACKGROUND_BLACK
);
853 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
857 static VOID
config_add_entry(Config
*config
, ConfigEntry
*entry
) {
858 if ((config
->entry_count
& 15) == 0) {
861 i
= config
->entry_count
+ 16;
862 if (config
->entry_count
== 0)
863 config
->entries
= AllocatePool(sizeof(VOID
*) * i
);
865 config
->entries
= ReallocatePool(config
->entries
,
866 sizeof(VOID
*) * config
->entry_count
, sizeof(VOID
*) * i
);
868 config
->entries
[config
->entry_count
++] = entry
;
871 static VOID
config_entry_free(ConfigEntry
*entry
) {
876 FreePool(entry
->title_show
);
877 FreePool(entry
->title
);
878 FreePool(entry
->version
);
879 FreePool(entry
->machine_id
);
880 FreePool(entry
->loader
);
881 FreePool(entry
->options
);
882 FreePool(entry
->path
);
883 FreePool(entry
->current_name
);
884 FreePool(entry
->next_name
);
888 static BOOLEAN
is_digit(CHAR16 c
) {
889 return (c
>= '0') && (c
<= '9');
892 static UINTN
c_order(CHAR16 c
) {
897 else if ((c
>= 'a') && (c
<= 'z'))
903 static INTN
str_verscmp(CHAR16
*s1
, CHAR16
*s2
) {
910 while ((*s1
&& !is_digit(*s1
)) || (*s2
&& !is_digit(*s2
))) {
913 order
= c_order(*s1
) - c_order(*s2
);
926 while (is_digit(*s1
) && is_digit(*s2
)) {
942 return StrCmp(os1
, os2
);
945 static CHAR8
*line_get_key_value(
957 line
= content
+ *pos
;
962 while (line
[linelen
] && !strchra((CHAR8
*)"\n\r", line
[linelen
]))
965 /* move pos to next line */
975 line
[linelen
] = '\0';
977 /* remove leading whitespace */
978 while (strchra((CHAR8
*)" \t", *line
)) {
983 /* remove trailing whitespace */
984 while (linelen
> 0 && strchra((CHAR8
*)" \t", line
[linelen
-1]))
986 line
[linelen
] = '\0';
991 /* split key/value */
993 while (*value
&& !strchra(sep
, *value
))
999 while (*value
&& strchra(sep
, *value
))
1003 if (value
[0] == '\"' && line
[linelen
-1] == '\"') {
1005 line
[linelen
-1] = '\0';
1013 static VOID
config_defaults_load_from_file(Config
*config
, CHAR8
*content
) {
1018 while ((line
= line_get_key_value(content
, (CHAR8
*)" \t", &pos
, &key
, &value
))) {
1019 if (strcmpa((CHAR8
*)"timeout", key
) == 0) {
1020 _cleanup_freepool_ CHAR16
*s
= NULL
;
1022 s
= stra_to_str(value
);
1023 config
->timeout_sec_config
= Atoi(s
);
1024 config
->timeout_sec
= config
->timeout_sec_config
;
1028 if (strcmpa((CHAR8
*)"default", key
) == 0) {
1029 FreePool(config
->entry_default_pattern
);
1030 config
->entry_default_pattern
= stra_to_str(value
);
1031 StrLwr(config
->entry_default_pattern
);
1035 if (strcmpa((CHAR8
*)"editor", key
) == 0) {
1038 if (EFI_ERROR(parse_boolean(value
, &on
)))
1040 config
->editor
= on
;
1043 if (strcmpa((CHAR8
*)"auto-entries", key
) == 0) {
1046 if (EFI_ERROR(parse_boolean(value
, &on
)))
1048 config
->auto_entries
= on
;
1051 if (strcmpa((CHAR8
*)"auto-firmware", key
) == 0) {
1054 if (EFI_ERROR(parse_boolean(value
, &on
)))
1056 config
->auto_firmware
= on
;
1059 if (strcmpa((CHAR8
*)"console-mode", key
) == 0) {
1060 if (strcmpa((CHAR8
*)"auto", value
) == 0)
1061 config
->console_mode_change
= CONSOLE_MODE_AUTO
;
1062 else if (strcmpa((CHAR8
*)"max", value
) == 0)
1063 config
->console_mode_change
= CONSOLE_MODE_MAX
;
1064 else if (strcmpa((CHAR8
*)"keep", value
) == 0)
1065 config
->console_mode_change
= CONSOLE_MODE_KEEP
;
1067 _cleanup_freepool_ CHAR16
*s
= NULL
;
1069 s
= stra_to_str(value
);
1070 config
->console_mode
= Atoi(s
);
1071 config
->console_mode_change
= CONSOLE_MODE_SET
;
1079 static VOID
config_entry_parse_tries(
1085 UINTN left
= (UINTN
) -1, done
= (UINTN
) -1, factor
= 1, i
, next_left
, next_done
;
1086 _cleanup_freepool_ CHAR16
*prefix
= NULL
;
1089 * Parses a suffix of two counters (one going down, one going up) in the form "+LEFT-DONE" from the end of the
1090 * filename (but before the .efi/.conf suffix), where the "-DONE" part is optional and may be left out (in
1091 * which case that counter as assumed to be zero, i.e. the missing part is synonymous to "-0").
1093 * Names we grok, and the series they result in:
1095 * foobar+3.efi → foobar+2-1.efi → foobar+1-2.efi → foobar+0-3.efi → STOP!
1096 * foobar+4-0.efi → foobar+3-1.efi → foobar+2-2.efi → foobar+1-3.efi → foobar+0-4.efi → STOP!
1101 /* Chop off any suffix such as ".conf" or ".efi" */
1103 UINTN suffix_length
;
1105 suffix_length
= StrLen(suffix
);
1106 if (i
< suffix_length
)
1112 /* Go backwards through the string and parse everything we encounter */
1122 if (left
== (UINTN
) -1) /* didn't read at least one digit for 'left'? */
1125 if (done
== (UINTN
) -1) /* no 'done' counter? If so, it's equivalent to 0 */
1131 if (left
== (UINTN
) -1) /* didn't parse any digit yet? */
1134 if (done
!= (UINTN
) -1) /* already encountered a dash earlier? */
1137 /* So we encountered a dash. This means this counter is of the form +LEFT-DONE. Let's assign
1138 * what we already parsed to 'done', and start fresh for the 'left' part. */
1148 if (left
== (UINTN
) -1)
1149 left
= file
[i
] - '0';
1151 UINTN new_left
, digit
;
1153 digit
= file
[i
] - '0';
1154 if (digit
> (UINTN
) -1 / factor
) /* overflow check */
1157 new_left
= left
+ digit
* factor
;
1158 if (new_left
< left
) /* overflow check */
1161 if (new_left
== (UINTN
) -1) /* don't allow us to be confused */
1165 new_factor
= factor
* 10;
1166 if (new_factor
< factor
) /* overflow chck */
1169 factor
= new_factor
;
1179 entry
->tries_left
= left
;
1180 entry
->tries_done
= done
;
1182 entry
->path
= StrDuplicate(path
);
1183 entry
->current_name
= StrDuplicate(file
);
1185 next_left
= left
<= 0 ? 0 : left
- 1;
1186 next_done
= done
>= (UINTN
) -2 ? (UINTN
) -2 : done
+ 1;
1188 prefix
= StrDuplicate(file
);
1191 entry
->next_name
= PoolPrint(L
"%s+%u-%u%s", prefix
, next_left
, next_done
, suffix
?: L
"");
1194 static VOID
config_entry_bump_counters(
1196 EFI_FILE_HANDLE root_dir
) {
1198 _cleanup_freepool_ CHAR16
* old_path
= NULL
, *new_path
= NULL
;
1199 _cleanup_(FileHandleClosep
) EFI_FILE_HANDLE handle
= NULL
;
1200 static EFI_GUID EfiFileInfoGuid
= EFI_FILE_INFO_ID
;
1201 _cleanup_freepool_ EFI_FILE_INFO
*file_info
= NULL
;
1202 UINTN file_info_size
, a
, b
;
1205 if (entry
->tries_left
== (UINTN
) -1)
1208 if (!entry
->path
|| !entry
->current_name
|| !entry
->next_name
)
1211 old_path
= PoolPrint(L
"%s\\%s", entry
->path
, entry
->current_name
);
1213 r
= uefi_call_wrapper(root_dir
->Open
, 5, root_dir
, &handle
, old_path
, EFI_FILE_MODE_READ
|EFI_FILE_MODE_WRITE
, 0ULL);
1217 a
= StrLen(entry
->current_name
);
1218 b
= StrLen(entry
->next_name
);
1220 file_info_size
= OFFSETOF(EFI_FILE_INFO
, FileName
) + (a
> b
? a
: b
) + 1;
1223 file_info
= AllocatePool(file_info_size
);
1225 r
= uefi_call_wrapper(handle
->GetInfo
, 4, handle
, &EfiFileInfoGuid
, &file_info_size
, file_info
);
1229 if (r
!= EFI_BUFFER_TOO_SMALL
|| file_info_size
* 2 < file_info_size
) {
1230 Print(L
"\nFailed to get file info for '%s': %r\n", old_path
, r
);
1231 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1235 file_info_size
*= 2;
1236 FreePool(file_info
);
1239 /* And rename the file */
1240 StrCpy(file_info
->FileName
, entry
->next_name
);
1241 r
= uefi_call_wrapper(handle
->SetInfo
, 4, handle
, &EfiFileInfoGuid
, file_info_size
, file_info
);
1243 Print(L
"\nFailed to rename '%s' to '%s', ignoring: %r\n", old_path
, entry
->next_name
, r
);
1244 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1248 /* Flush everything to disk, just in case… */
1249 (void) uefi_call_wrapper(handle
->Flush
, 1, handle
);
1251 /* Let's tell the OS that we renamed this file, so that it knows what to rename to the counter-less name on
1253 new_path
= PoolPrint(L
"%s\\%s", entry
->path
, entry
->next_name
);
1254 efivar_set(L
"LoaderBootCountPath", new_path
, FALSE
);
1256 /* If the file we just renamed is the loader path, then let's update that. */
1257 if (StrCmp(entry
->loader
, old_path
) == 0) {
1258 FreePool(entry
->loader
);
1259 entry
->loader
= new_path
;
1264 static VOID
config_entry_add_from_file(
1270 CHAR16
*loaded_image_path
) {
1277 _cleanup_freepool_ CHAR16
*initrd
= NULL
;
1279 entry
= AllocatePool(sizeof(ConfigEntry
));
1281 *entry
= (ConfigEntry
) {
1282 .tries_done
= (UINTN
) -1,
1283 .tries_left
= (UINTN
) -1,
1286 while ((line
= line_get_key_value(content
, (CHAR8
*)" \t", &pos
, &key
, &value
))) {
1287 if (strcmpa((CHAR8
*)"title", key
) == 0) {
1288 FreePool(entry
->title
);
1289 entry
->title
= stra_to_str(value
);
1293 if (strcmpa((CHAR8
*)"version", key
) == 0) {
1294 FreePool(entry
->version
);
1295 entry
->version
= stra_to_str(value
);
1299 if (strcmpa((CHAR8
*)"machine-id", key
) == 0) {
1300 FreePool(entry
->machine_id
);
1301 entry
->machine_id
= stra_to_str(value
);
1305 if (strcmpa((CHAR8
*)"linux", key
) == 0) {
1306 FreePool(entry
->loader
);
1307 entry
->type
= LOADER_LINUX
;
1308 entry
->loader
= stra_to_path(value
);
1313 if (strcmpa((CHAR8
*)"efi", key
) == 0) {
1314 entry
->type
= LOADER_EFI
;
1315 FreePool(entry
->loader
);
1316 entry
->loader
= stra_to_path(value
);
1318 /* do not add an entry for ourselves */
1319 if (StriCmp(entry
->loader
, loaded_image_path
) == 0) {
1320 entry
->type
= LOADER_UNDEFINED
;
1326 if (strcmpa((CHAR8
*)"architecture", key
) == 0) {
1327 /* do not add an entry for an EFI image of architecture not matching with that of the image */
1328 if (strcmpa((CHAR8
*)EFI_MACHINE_TYPE_NAME
, value
) != 0) {
1329 entry
->type
= LOADER_UNDEFINED
;
1335 if (strcmpa((CHAR8
*)"initrd", key
) == 0) {
1336 _cleanup_freepool_ CHAR16
*new = NULL
;
1338 new = stra_to_path(value
);
1342 s
= PoolPrint(L
"%s initrd=%s", initrd
, new);
1346 initrd
= PoolPrint(L
"initrd=%s", new);
1351 if (strcmpa((CHAR8
*)"options", key
) == 0) {
1352 _cleanup_freepool_ CHAR16
*new = NULL
;
1354 new = stra_to_str(value
);
1355 if (entry
->options
) {
1358 s
= PoolPrint(L
"%s %s", entry
->options
, new);
1359 FreePool(entry
->options
);
1362 entry
->options
= new;
1370 if (entry
->type
== LOADER_UNDEFINED
) {
1371 config_entry_free(entry
);
1375 /* add initrd= to options */
1376 if (entry
->type
== LOADER_LINUX
&& initrd
) {
1377 if (entry
->options
) {
1380 s
= PoolPrint(L
"%s %s", initrd
, entry
->options
);
1381 FreePool(entry
->options
);
1384 entry
->options
= initrd
;
1389 entry
->device
= device
;
1390 entry
->id
= StrDuplicate(file
);
1391 len
= StrLen(entry
->id
);
1392 /* remove ".conf" */
1394 entry
->id
[len
- 5] = '\0';
1397 config_add_entry(config
, entry
);
1399 config_entry_parse_tries(entry
, path
, file
, L
".conf");
1402 static VOID
config_load_defaults(Config
*config
, EFI_FILE
*root_dir
) {
1403 _cleanup_freepool_ CHAR8
*content
= NULL
;
1407 *config
= (Config
) {
1409 .auto_entries
= TRUE
,
1410 .auto_firmware
= TRUE
,
1413 err
= file_read(root_dir
, L
"\\loader\\loader.conf", 0, 0, &content
, NULL
);
1414 if (!EFI_ERROR(err
))
1415 config_defaults_load_from_file(config
, content
);
1417 err
= efivar_get_int(L
"LoaderConfigTimeout", &sec
);
1418 if (!EFI_ERROR(err
)) {
1419 config
->timeout_sec_efivar
= sec
> INTN_MAX
? INTN_MAX
: sec
;
1420 config
->timeout_sec
= sec
;
1422 config
->timeout_sec_efivar
= -1;
1424 err
= efivar_get_int(L
"LoaderConfigTimeoutOneShot", &sec
);
1425 if (!EFI_ERROR(err
)) {
1426 /* Unset variable now, after all it's "one shot". */
1427 (void) efivar_set(L
"LoaderConfigTimeoutOneShot", NULL
, TRUE
);
1429 config
->timeout_sec
= sec
;
1430 config
->force_menu
= TRUE
; /* force the menu when this is set */
1434 static VOID
config_load_entries(
1438 CHAR16
*loaded_image_path
) {
1440 EFI_FILE_HANDLE entries_dir
;
1443 err
= uefi_call_wrapper(root_dir
->Open
, 5, root_dir
, &entries_dir
, L
"\\loader\\entries", EFI_FILE_MODE_READ
, 0ULL);
1444 if (!EFI_ERROR(err
)) {
1449 _cleanup_freepool_ CHAR8
*content
= NULL
;
1452 bufsize
= sizeof(buf
);
1453 err
= uefi_call_wrapper(entries_dir
->Read
, 3, entries_dir
, &bufsize
, buf
);
1454 if (bufsize
== 0 || EFI_ERROR(err
))
1457 f
= (EFI_FILE_INFO
*) buf
;
1458 if (f
->FileName
[0] == '.')
1460 if (f
->Attribute
& EFI_FILE_DIRECTORY
)
1463 len
= StrLen(f
->FileName
);
1466 if (StriCmp(f
->FileName
+ len
- 5, L
".conf") != 0)
1468 if (StrnCmp(f
->FileName
, L
"auto-", 5) == 0)
1471 err
= file_read(entries_dir
, f
->FileName
, 0, 0, &content
, NULL
);
1472 if (!EFI_ERROR(err
))
1473 config_entry_add_from_file(config
, device
, L
"\\loader\\entries", f
->FileName
, content
, loaded_image_path
);
1475 uefi_call_wrapper(entries_dir
->Close
, 1, entries_dir
);
1479 static INTN
config_entry_compare(ConfigEntry
*a
, ConfigEntry
*b
) {
1482 /* Order entries that have no tries left to the end of the list */
1483 if (a
->tries_left
!= 0 && b
->tries_left
== 0)
1485 if (a
->tries_left
== 0 && b
->tries_left
!= 0)
1488 r
= str_verscmp(a
->id
, b
->id
);
1492 if (a
->tries_left
== (UINTN
) -1 ||
1493 b
->tries_left
== (UINTN
) -1)
1496 /* If both items have boot counting, and otherwise are identical, put the entry with more tries left first */
1497 if (a
->tries_left
> b
->tries_left
)
1499 if (a
->tries_left
< b
->tries_left
)
1502 /* If they have the same number of tries left, then let the one win which was tried fewer times so far */
1503 if (a
->tries_done
< b
->tries_done
)
1505 if (a
->tries_done
> b
->tries_done
)
1511 static VOID
config_sort_entries(Config
*config
) {
1514 for (i
= 1; i
< config
->entry_count
; i
++) {
1519 for (k
= 0; k
< config
->entry_count
- i
; k
++) {
1522 if (config_entry_compare(config
->entries
[k
], config
->entries
[k
+1]) <= 0)
1525 entry
= config
->entries
[k
];
1526 config
->entries
[k
] = config
->entries
[k
+1];
1527 config
->entries
[k
+1] = entry
;
1535 static INTN
config_entry_find(Config
*config
, CHAR16
*id
) {
1538 for (i
= 0; i
< config
->entry_count
; i
++)
1539 if (StrCmp(config
->entries
[i
]->id
, id
) == 0)
1545 static VOID
config_default_entry_select(Config
*config
) {
1546 _cleanup_freepool_ CHAR16
*entry_oneshot
= NULL
, *entry_default
= NULL
;
1551 * The EFI variable to specify a boot entry for the next, and only the
1552 * next reboot. The variable is always cleared directly after it is read.
1554 err
= efivar_get(L
"LoaderEntryOneShot", &entry_oneshot
);
1555 if (!EFI_ERROR(err
)) {
1557 config
->entry_oneshot
= StrDuplicate(entry_oneshot
);
1558 efivar_set(L
"LoaderEntryOneShot", NULL
, TRUE
);
1560 i
= config_entry_find(config
, entry_oneshot
);
1562 config
->idx_default
= i
;
1568 * The EFI variable to select the default boot entry overrides the
1569 * configured pattern. The variable can be set and cleared by pressing
1570 * the 'd' key in the loader selection menu, the entry is marked with
1573 err
= efivar_get(L
"LoaderEntryDefault", &entry_default
);
1574 if (!EFI_ERROR(err
)) {
1576 i
= config_entry_find(config
, entry_default
);
1578 config
->idx_default
= i
;
1579 config
->idx_default_efivar
= i
;
1583 config
->idx_default_efivar
= -1;
1585 if (config
->entry_count
== 0)
1589 * Match the pattern from the end of the list to the start, find last
1590 * entry (largest number) matching the given pattern.
1592 if (config
->entry_default_pattern
) {
1593 i
= config
->entry_count
;
1595 if (config
->entries
[i
]->no_autoselect
)
1597 if (MetaiMatch(config
->entries
[i
]->id
, config
->entry_default_pattern
)) {
1598 config
->idx_default
= i
;
1604 /* select the last suitable entry */
1605 i
= config
->entry_count
;
1607 if (config
->entries
[i
]->no_autoselect
)
1609 config
->idx_default
= i
;
1613 /* no entry found */
1614 config
->idx_default
= -1;
1617 static BOOLEAN
find_nonunique(ConfigEntry
**entries
, UINTN entry_count
) {
1618 BOOLEAN non_unique
= FALSE
;
1621 for (i
= 0; i
< entry_count
; i
++)
1622 entries
[i
]->non_unique
= FALSE
;
1624 for (i
= 0; i
< entry_count
; i
++)
1625 for (k
= 0; k
< entry_count
; k
++) {
1628 if (StrCmp(entries
[i
]->title_show
, entries
[k
]->title_show
) != 0)
1631 non_unique
= entries
[i
]->non_unique
= entries
[k
]->non_unique
= TRUE
;
1637 /* generate a unique title, avoiding non-distinguishable menu entries */
1638 static VOID
config_title_generate(Config
*config
) {
1642 for (i
= 0; i
< config
->entry_count
; i
++) {
1645 FreePool(config
->entries
[i
]->title_show
);
1646 title
= config
->entries
[i
]->title
;
1648 title
= config
->entries
[i
]->id
;
1649 config
->entries
[i
]->title_show
= StrDuplicate(title
);
1652 if (!find_nonunique(config
->entries
, config
->entry_count
))
1655 /* add version to non-unique titles */
1656 for (i
= 0; i
< config
->entry_count
; i
++) {
1659 if (!config
->entries
[i
]->non_unique
)
1661 if (!config
->entries
[i
]->version
)
1664 s
= PoolPrint(L
"%s (%s)", config
->entries
[i
]->title_show
, config
->entries
[i
]->version
);
1665 FreePool(config
->entries
[i
]->title_show
);
1666 config
->entries
[i
]->title_show
= s
;
1669 if (!find_nonunique(config
->entries
, config
->entry_count
))
1672 /* add machine-id to non-unique titles */
1673 for (i
= 0; i
< config
->entry_count
; i
++) {
1675 _cleanup_freepool_ CHAR16
*m
= NULL
;
1677 if (!config
->entries
[i
]->non_unique
)
1679 if (!config
->entries
[i
]->machine_id
)
1682 m
= StrDuplicate(config
->entries
[i
]->machine_id
);
1684 s
= PoolPrint(L
"%s (%s)", config
->entries
[i
]->title_show
, m
);
1685 FreePool(config
->entries
[i
]->title_show
);
1686 config
->entries
[i
]->title_show
= s
;
1689 if (!find_nonunique(config
->entries
, config
->entry_count
))
1692 /* add file name to non-unique titles */
1693 for (i
= 0; i
< config
->entry_count
; i
++) {
1696 if (!config
->entries
[i
]->non_unique
)
1698 s
= PoolPrint(L
"%s (%s)", config
->entries
[i
]->title_show
, config
->entries
[i
]->id
);
1699 FreePool(config
->entries
[i
]->title_show
);
1700 config
->entries
[i
]->title_show
= s
;
1701 config
->entries
[i
]->non_unique
= FALSE
;
1705 static BOOLEAN
config_entry_add_call(
1709 EFI_STATUS (*call
)(VOID
)) {
1713 entry
= AllocatePool(sizeof(ConfigEntry
));
1714 *entry
= (ConfigEntry
) {
1715 .id
= StrDuplicate(id
),
1716 .title
= StrDuplicate(title
),
1718 .no_autoselect
= TRUE
,
1719 .tries_done
= (UINTN
) -1,
1720 .tries_left
= (UINTN
) -1,
1723 config_add_entry(config
, entry
);
1727 static ConfigEntry
*config_entry_add_loader(
1730 enum loader_type type
,
1738 entry
= AllocatePool(sizeof(ConfigEntry
));
1739 *entry
= (ConfigEntry
) {
1741 .title
= StrDuplicate(title
),
1743 .loader
= StrDuplicate(loader
),
1744 .id
= StrDuplicate(id
),
1746 .tries_done
= (UINTN
) -1,
1747 .tries_left
= (UINTN
) -1,
1752 config_add_entry(config
, entry
);
1756 static BOOLEAN
config_entry_add_loader_auto(
1760 CHAR16
*loaded_image_path
,
1766 EFI_FILE_HANDLE handle
;
1770 if (!config
->auto_entries
)
1773 /* do not add an entry for ourselves */
1774 if (loaded_image_path
) {
1776 _cleanup_freepool_ CHAR8
*content
= NULL
;
1778 if (StriCmp(loader
, loaded_image_path
) == 0)
1781 /* look for systemd-boot magic string */
1782 err
= file_read(root_dir
, loader
, 0, 100*1024, &content
, &len
);
1783 if (!EFI_ERROR(err
)) {
1784 CHAR8
*start
= content
;
1785 CHAR8
*last
= content
+ len
- sizeof(magic
) - 1;
1787 for (; start
<= last
; start
++)
1788 if (start
[0] == magic
[0] && CompareMem(start
, magic
, sizeof(magic
) - 1) == 0)
1793 /* check existence */
1794 err
= uefi_call_wrapper(root_dir
->Open
, 5, root_dir
, &handle
, loader
, EFI_FILE_MODE_READ
, 0ULL);
1797 uefi_call_wrapper(handle
->Close
, 1, handle
);
1799 entry
= config_entry_add_loader(config
, device
, LOADER_UNDEFINED
, id
, key
, title
, loader
);
1803 /* do not boot right away into auto-detected entries */
1804 entry
->no_autoselect
= TRUE
;
1809 static VOID
config_entry_add_osx(Config
*config
) {
1811 UINTN handle_count
= 0;
1812 _cleanup_freepool_ EFI_HANDLE
*handles
= NULL
;
1814 if (!config
->auto_entries
)
1817 err
= LibLocateHandle(ByProtocol
, &FileSystemProtocol
, NULL
, &handle_count
, &handles
);
1818 if (!EFI_ERROR(err
)) {
1821 for (i
= 0; i
< handle_count
; i
++) {
1825 root
= LibOpenRoot(handles
[i
]);
1828 found
= config_entry_add_loader_auto(config
, handles
[i
], root
, NULL
, L
"auto-osx", 'a', L
"macOS",
1829 L
"\\System\\Library\\CoreServices\\boot.efi");
1830 uefi_call_wrapper(root
->Close
, 1, root
);
1837 static VOID
config_entry_add_linux(
1839 EFI_LOADED_IMAGE
*loaded_image
,
1840 EFI_FILE
*root_dir
) {
1842 EFI_FILE_HANDLE linux_dir
;
1846 err
= uefi_call_wrapper(root_dir
->Open
, 5, root_dir
, &linux_dir
, L
"\\EFI\\Linux", EFI_FILE_MODE_READ
, 0ULL);
1852 UINTN bufsize
= sizeof buf
;
1854 CHAR8
*sections
[] = {
1856 (UINT8
*)".cmdline",
1859 UINTN offs
[ELEMENTSOF(sections
)-1] = {};
1860 UINTN szs
[ELEMENTSOF(sections
)-1] = {};
1861 UINTN addrs
[ELEMENTSOF(sections
)-1] = {};
1862 CHAR8
*content
= NULL
;
1867 CHAR16
*os_name
= NULL
;
1868 CHAR16
*os_id
= NULL
;
1869 CHAR16
*os_version
= NULL
;
1870 CHAR16
*os_build
= NULL
;
1872 err
= uefi_call_wrapper(linux_dir
->Read
, 3, linux_dir
, &bufsize
, buf
);
1873 if (bufsize
== 0 || EFI_ERROR(err
))
1876 f
= (EFI_FILE_INFO
*) buf
;
1877 if (f
->FileName
[0] == '.')
1879 if (f
->Attribute
& EFI_FILE_DIRECTORY
)
1881 len
= StrLen(f
->FileName
);
1884 if (StriCmp(f
->FileName
+ len
- 4, L
".efi") != 0)
1887 /* look for .osrel and .cmdline sections in the .efi binary */
1888 err
= pe_file_locate_sections(linux_dir
, f
->FileName
, sections
, addrs
, offs
, szs
);
1892 err
= file_read(linux_dir
, f
->FileName
, offs
[0], szs
[0], &content
, NULL
);
1896 /* read properties from the embedded os-release file */
1897 while ((line
= line_get_key_value(content
, (CHAR8
*)"=", &pos
, &key
, &value
))) {
1898 if (strcmpa((CHAR8
*)"PRETTY_NAME", key
) == 0) {
1900 os_name
= stra_to_str(value
);
1904 if (strcmpa((CHAR8
*)"ID", key
) == 0) {
1906 os_id
= stra_to_str(value
);
1910 if (strcmpa((CHAR8
*)"VERSION_ID", key
) == 0) {
1911 FreePool(os_version
);
1912 os_version
= stra_to_str(value
);
1916 if (strcmpa((CHAR8
*)"BUILD_ID", key
) == 0) {
1918 os_build
= stra_to_str(value
);
1923 if (os_name
&& os_id
&& (os_version
|| os_build
)) {
1924 _cleanup_freepool_ CHAR16
*conf
= NULL
, *path
= NULL
;
1926 conf
= PoolPrint(L
"%s-%s", os_id
, os_version
? : os_build
);
1927 path
= PoolPrint(L
"\\EFI\\Linux\\%s", f
->FileName
);
1929 entry
= config_entry_add_loader(config
, loaded_image
->DeviceHandle
, LOADER_LINUX
, conf
, 'l', os_name
, path
);
1934 /* read the embedded cmdline file */
1935 err
= file_read(linux_dir
, f
->FileName
, offs
[1], szs
[1], &content
, NULL
);
1936 if (!EFI_ERROR(err
)) {
1938 /* chomp the newline */
1939 if (content
[szs
[1]-1] == '\n')
1940 content
[szs
[1]-1] = '\0';
1942 entry
->options
= stra_to_str(content
);
1945 config_entry_parse_tries(entry
, L
"\\EFI\\Linux", f
->FileName
, L
".efi");
1950 FreePool(os_version
);
1955 uefi_call_wrapper(linux_dir
->Close
, 1, linux_dir
);
1958 static EFI_STATUS
image_start(
1959 EFI_HANDLE parent_image
,
1960 const Config
*config
,
1961 const ConfigEntry
*entry
) {
1964 _cleanup_freepool_ EFI_DEVICE_PATH
*path
= NULL
;
1968 path
= FileDevicePath(entry
->device
, entry
->loader
);
1970 Print(L
"Error getting device path.");
1971 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1972 return EFI_INVALID_PARAMETER
;
1975 err
= uefi_call_wrapper(BS
->LoadImage
, 6, FALSE
, parent_image
, path
, NULL
, 0, &image
);
1976 if (EFI_ERROR(err
)) {
1977 Print(L
"Error loading %s: %r", entry
->loader
, err
);
1978 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1982 if (config
->options_edit
)
1983 options
= config
->options_edit
;
1984 else if (entry
->options
)
1985 options
= entry
->options
;
1989 EFI_LOADED_IMAGE
*loaded_image
;
1991 err
= uefi_call_wrapper(BS
->OpenProtocol
, 6, image
, &LoadedImageProtocol
, (VOID
**)&loaded_image
,
1992 parent_image
, NULL
, EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
1993 if (EFI_ERROR(err
)) {
1994 Print(L
"Error getting LoadedImageProtocol handle: %r", err
);
1995 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1998 loaded_image
->LoadOptions
= options
;
1999 loaded_image
->LoadOptionsSize
= (StrLen(loaded_image
->LoadOptions
)+1) * sizeof(CHAR16
);
2002 /* Try to log any options to the TPM, especially to catch manually edited options */
2003 err
= tpm_log_event(SD_TPM_PCR
,
2004 (EFI_PHYSICAL_ADDRESS
) (UINTN
) loaded_image
->LoadOptions
,
2005 loaded_image
->LoadOptionsSize
, loaded_image
->LoadOptions
);
2006 if (EFI_ERROR(err
)) {
2007 Print(L
"Unable to add image options measurement: %r", err
);
2008 uefi_call_wrapper(BS
->Stall
, 1, 200 * 1000);
2013 efivar_set_time_usec(L
"LoaderTimeExecUSec", 0);
2014 err
= uefi_call_wrapper(BS
->StartImage
, 3, image
, NULL
, NULL
);
2016 uefi_call_wrapper(BS
->UnloadImage
, 1, image
);
2020 static EFI_STATUS
reboot_into_firmware(VOID
) {
2021 _cleanup_freepool_ CHAR8
*b
= NULL
;
2026 osind
= EFI_OS_INDICATIONS_BOOT_TO_FW_UI
;
2028 err
= efivar_get_raw(&global_guid
, L
"OsIndications", &b
, &size
);
2029 if (!EFI_ERROR(err
))
2030 osind
|= (UINT64
)*b
;
2032 err
= efivar_set_raw(&global_guid
, L
"OsIndications", &osind
, sizeof(UINT64
), TRUE
);
2036 err
= uefi_call_wrapper(RT
->ResetSystem
, 4, EfiResetCold
, EFI_SUCCESS
, 0, NULL
);
2037 Print(L
"Error calling ResetSystem: %r", err
);
2038 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
2042 static VOID
config_free(Config
*config
) {
2045 for (i
= 0; i
< config
->entry_count
; i
++)
2046 config_entry_free(config
->entries
[i
]);
2047 FreePool(config
->entries
);
2048 FreePool(config
->entry_default_pattern
);
2049 FreePool(config
->options_edit
);
2050 FreePool(config
->entry_oneshot
);
2053 static VOID
config_write_entries_to_variable(Config
*config
) {
2054 _cleanup_freepool_ CHAR16
*buffer
= NULL
;
2058 for (i
= 0; i
< config
->entry_count
; i
++)
2059 sz
+= StrLen(config
->entries
[i
]->id
) + 1;
2061 p
= buffer
= AllocatePool(sz
* sizeof(CHAR16
));
2063 for (i
= 0; i
< config
->entry_count
; i
++) {
2066 l
= StrLen(config
->entries
[i
]->id
) + 1;
2067 CopyMem(p
, config
->entries
[i
]->id
, l
* sizeof(CHAR16
));
2072 /* Store the full list of discovered entries. */
2073 (void) efivar_set_raw(&loader_guid
, L
"LoaderEntries", buffer
, (UINT8
*) p
- (UINT8
*) buffer
, FALSE
);
2076 EFI_STATUS
efi_main(EFI_HANDLE image
, EFI_SYSTEM_TABLE
*sys_table
) {
2077 static const UINT64 loader_features
=
2078 (1ULL << 0) | /* I honour the LoaderConfigTimeout variable */
2079 (1ULL << 1) | /* I honour the LoaderConfigTimeoutOneShot variable */
2080 (1ULL << 2) | /* I honour the LoaderEntryDefault variable */
2081 (1ULL << 3) | /* I honour the LoaderEntryOneShot variable */
2082 (1ULL << 4) | /* I support boot counting */
2085 _cleanup_freepool_ CHAR16
*infostr
= NULL
, *typestr
= NULL
;
2088 EFI_LOADED_IMAGE
*loaded_image
;
2090 CHAR16
*loaded_image_path
;
2094 BOOLEAN menu
= FALSE
;
2097 InitializeLib(image
, sys_table
);
2098 init_usec
= time_usec();
2099 efivar_set_time_usec(L
"LoaderTimeInitUSec", init_usec
);
2100 efivar_set(L
"LoaderInfo", L
"systemd-boot " GIT_VERSION
, FALSE
);
2102 infostr
= PoolPrint(L
"%s %d.%02d", ST
->FirmwareVendor
, ST
->FirmwareRevision
>> 16, ST
->FirmwareRevision
& 0xffff);
2103 efivar_set(L
"LoaderFirmwareInfo", infostr
, FALSE
);
2105 typestr
= PoolPrint(L
"UEFI %d.%02d", ST
->Hdr
.Revision
>> 16, ST
->Hdr
.Revision
& 0xffff);
2106 efivar_set(L
"LoaderFirmwareType", typestr
, FALSE
);
2108 (void) efivar_set_raw(&loader_guid
, L
"LoaderFeatures", &loader_features
, sizeof(loader_features
), FALSE
);
2110 err
= uefi_call_wrapper(BS
->OpenProtocol
, 6, image
, &LoadedImageProtocol
, (VOID
**)&loaded_image
,
2111 image
, NULL
, EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
2112 if (EFI_ERROR(err
)) {
2113 Print(L
"Error getting a LoadedImageProtocol handle: %r ", err
);
2114 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
2118 /* export the device path this image is started from */
2119 if (disk_get_part_uuid(loaded_image
->DeviceHandle
, uuid
) == EFI_SUCCESS
)
2120 efivar_set(L
"LoaderDevicePartUUID", uuid
, FALSE
);
2122 root_dir
= LibOpenRoot(loaded_image
->DeviceHandle
);
2124 Print(L
"Unable to open root directory: %r ", err
);
2125 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
2126 return EFI_LOAD_ERROR
;
2129 if (secure_boot_enabled() && shim_loaded()) {
2130 err
= security_policy_install();
2131 if (EFI_ERROR(err
)) {
2132 Print(L
"Error installing security policy: %r ", err
);
2133 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
2138 /* the filesystem path to this image, to prevent adding ourselves to the menu */
2139 loaded_image_path
= DevicePathToStr(loaded_image
->FilePath
);
2140 efivar_set(L
"LoaderImageIdentifier", loaded_image_path
, FALSE
);
2142 config_load_defaults(&config
, root_dir
);
2144 /* scan /EFI/Linux/ directory */
2145 config_entry_add_linux(&config
, loaded_image
, root_dir
);
2147 /* scan /loader/entries/\*.conf files */
2148 config_load_entries(&config
, loaded_image
->DeviceHandle
, root_dir
, loaded_image_path
);
2150 /* sort entries after version number */
2151 config_sort_entries(&config
);
2153 /* if we find some well-known loaders, add them to the end of the list */
2154 config_entry_add_loader_auto(&config
, loaded_image
->DeviceHandle
, root_dir
, NULL
,
2155 L
"auto-windows", 'w', L
"Windows Boot Manager", L
"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
2156 config_entry_add_loader_auto(&config
, loaded_image
->DeviceHandle
, root_dir
, NULL
,
2157 L
"auto-efi-shell", 's', L
"EFI Shell", L
"\\shell" EFI_MACHINE_TYPE_NAME
".efi");
2158 config_entry_add_loader_auto(&config
, loaded_image
->DeviceHandle
, root_dir
, loaded_image_path
,
2159 L
"auto-efi-default", '\0', L
"EFI Default Loader", L
"\\EFI\\Boot\\boot" EFI_MACHINE_TYPE_NAME
".efi");
2160 config_entry_add_osx(&config
);
2162 if (config
.auto_firmware
&& efivar_get_raw(&global_guid
, L
"OsIndicationsSupported", &b
, &size
) == EFI_SUCCESS
) {
2163 UINT64 osind
= (UINT64
)*b
;
2165 if (osind
& EFI_OS_INDICATIONS_BOOT_TO_FW_UI
)
2166 config_entry_add_call(&config
, L
"auto-reboot-to-firmware-setup", L
"Reboot Into Firmware Interface", reboot_into_firmware
);
2170 if (config
.entry_count
== 0) {
2171 Print(L
"No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
2172 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
2176 config_write_entries_to_variable(&config
);
2178 config_title_generate(&config
);
2180 /* select entry by configured pattern or EFI LoaderDefaultEntry= variable */
2181 config_default_entry_select(&config
);
2183 /* if no configured entry to select from was found, enable the menu */
2184 if (config
.idx_default
== -1) {
2185 config
.idx_default
= 0;
2186 if (config
.timeout_sec
== 0)
2187 config
.timeout_sec
= 10;
2190 /* select entry or show menu when key is pressed or timeout is set */
2191 if (config
.force_menu
|| config
.timeout_sec
> 0)
2196 err
= console_key_read(&key
, FALSE
);
2197 if (!EFI_ERROR(err
)) {
2200 /* find matching key in config entries */
2201 idx
= entry_lookup_key(&config
, config
.idx_default
, KEYCHAR(key
));
2203 config
.idx_default
= idx
;
2212 entry
= config
.entries
[config
.idx_default
];
2214 efivar_set_time_usec(L
"LoaderTimeMenuUSec", 0);
2215 uefi_call_wrapper(BS
->SetWatchdogTimer
, 4, 0, 0x10000, 0, NULL
);
2216 if (!menu_run(&config
, &entry
, loaded_image_path
))
2220 /* run special entry like "reboot" */
2226 config_entry_bump_counters(entry
, root_dir
);
2228 /* export the selected boot entry to the system */
2229 efivar_set(L
"LoaderEntrySelected", entry
->id
, FALSE
);
2231 uefi_call_wrapper(BS
->SetWatchdogTimer
, 4, 5 * 60, 0x10000, 0, NULL
);
2232 err
= image_start(image
, &config
, entry
);
2233 if (EFI_ERROR(err
)) {
2234 graphics_mode(FALSE
);
2235 Print(L
"\nFailed to execute %s (%s): %r\n", entry
->title
, entry
->loader
, err
);
2236 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
2241 config
.timeout_sec
= 0;
2245 FreePool(loaded_image_path
);
2246 config_free(&config
);
2247 uefi_call_wrapper(root_dir
->Close
, 1, root_dir
);
2248 uefi_call_wrapper(BS
->CloseProtocol
, 4, image
, &LoadedImageProtocol
, image
, NULL
);