1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; either version 2.1 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * Copyright (C) 2012-2015 Kay Sievers <kay@vrfy.org>
15 * Copyright (C) 2012-2015 Harald Hoyer <harald@redhat.com>
27 #ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI
28 #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL
31 /* magic string to find in the binary image */
32 static const char __attribute__((used
)) magic
[] = "#### LoaderInfo: systemd-boot " VERSION
" ####";
34 static const EFI_GUID global_guid
= EFI_GLOBAL_VARIABLE
;
49 enum loader_type type
;
54 EFI_STATUS (*call
)(VOID
);
55 BOOLEAN no_autoselect
;
60 ConfigEntry
**entries
;
63 INTN idx_default_efivar
;
65 UINTN timeout_sec_config
;
66 INTN timeout_sec_efivar
;
67 CHAR16
*entry_default_pattern
;
68 CHAR16
*entry_oneshot
;
73 static VOID
cursor_left(UINTN
*cursor
, UINTN
*first
)
77 else if ((*first
) > 0)
81 static VOID
cursor_right(UINTN
*cursor
, UINTN
*first
, UINTN x_max
, UINTN len
)
83 if ((*cursor
)+1 < x_max
)
85 else if ((*first
) + (*cursor
) < len
)
89 static BOOLEAN
line_edit(CHAR16
*line_in
, CHAR16
**line_out
, UINTN x_max
, UINTN y_pos
) {
102 size
= StrLen(line_in
) + 1024;
103 line
= AllocatePool(size
* sizeof(CHAR16
));
104 StrCpy(line
, line_in
);
106 print
= AllocatePool((x_max
+1) * sizeof(CHAR16
));
108 uefi_call_wrapper(ST
->ConOut
->EnableCursor
, 2, ST
->ConOut
, TRUE
);
123 CopyMem(print
, line
+ first
, i
* sizeof(CHAR16
));
124 while (clear
> 0 && i
< x_max
-1) {
130 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_pos
);
131 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, print
);
132 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
134 err
= console_key_read(&key
, TRUE
);
139 case KEYPRESS(0, SCAN_ESC
, 0):
140 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'c'):
141 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'g'):
142 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('c')):
143 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('g')):
147 case KEYPRESS(0, SCAN_HOME
, 0):
148 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'a'):
149 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('a')):
150 /* beginning-of-line */
155 case KEYPRESS(0, SCAN_END
, 0):
156 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'e'):
157 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('e')):
159 cursor
= len
- first
;
160 if (cursor
+1 >= x_max
) {
162 first
= len
- (x_max
-1);
166 case KEYPRESS(0, SCAN_DOWN
, 0):
167 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'f'):
168 case KEYPRESS(EFI_CONTROL_PRESSED
, SCAN_RIGHT
, 0):
170 while (line
[first
+ cursor
] && line
[first
+ cursor
] == ' ')
171 cursor_right(&cursor
, &first
, x_max
, len
);
172 while (line
[first
+ cursor
] && line
[first
+ cursor
] != ' ')
173 cursor_right(&cursor
, &first
, x_max
, len
);
174 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
177 case KEYPRESS(0, SCAN_UP
, 0):
178 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'b'):
179 case KEYPRESS(EFI_CONTROL_PRESSED
, SCAN_LEFT
, 0):
181 if ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] == ' ') {
182 cursor_left(&cursor
, &first
);
183 while ((first
+ cursor
) > 0 && line
[first
+ cursor
] == ' ')
184 cursor_left(&cursor
, &first
);
186 while ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] != ' ')
187 cursor_left(&cursor
, &first
);
188 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
191 case KEYPRESS(0, SCAN_RIGHT
, 0):
192 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'f'):
193 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('f')):
195 if (first
+ cursor
== len
)
197 cursor_right(&cursor
, &first
, x_max
, len
);
198 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
201 case KEYPRESS(0, SCAN_LEFT
, 0):
202 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'b'):
203 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('b')):
205 cursor_left(&cursor
, &first
);
206 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
209 case KEYPRESS(EFI_ALT_PRESSED
, 0, 'd'):
212 for (i
= first
+ cursor
; i
< len
&& line
[i
] == ' '; i
++)
214 for (; i
< len
&& line
[i
] != ' '; i
++)
217 for (i
= first
+ cursor
; i
+ clear
< len
; i
++)
218 line
[i
] = line
[i
+ clear
];
223 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'w'):
224 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('w')):
225 case KEYPRESS(EFI_ALT_PRESSED
, 0, CHAR_BACKSPACE
):
226 /* backward-kill-word */
228 if ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] == ' ') {
229 cursor_left(&cursor
, &first
);
231 while ((first
+ cursor
) > 0 && line
[first
+ cursor
] == ' ') {
232 cursor_left(&cursor
, &first
);
236 while ((first
+ cursor
) > 0 && line
[first
+ cursor
-1] != ' ') {
237 cursor_left(&cursor
, &first
);
240 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, cursor
, y_pos
);
242 for (i
= first
+ cursor
; i
+ clear
< len
; i
++)
243 line
[i
] = line
[i
+ clear
];
248 case KEYPRESS(0, SCAN_DELETE
, 0):
249 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'd'):
250 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('d')):
253 if (first
+ cursor
== len
)
255 for (i
= first
+ cursor
; i
< len
; i
++)
261 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'k'):
262 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('k')):
264 line
[first
+ cursor
] = '\0';
265 clear
= len
- (first
+ cursor
);
266 len
= first
+ cursor
;
269 case KEYPRESS(0, 0, CHAR_LINEFEED
):
270 case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN
):
271 if (StrCmp(line
, line_in
) != 0) {
279 case KEYPRESS(0, 0, CHAR_BACKSPACE
):
282 if (first
== 0 && cursor
== 0)
284 for (i
= first
+ cursor
-1; i
< len
; i
++)
290 if (cursor
> 0 || first
== 0)
292 /* show full line if it fits */
298 /* jump left to see what we delete */
308 case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
309 case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
312 for (i
= len
; i
> first
+ cursor
; i
--)
314 line
[first
+ cursor
] = KEYCHAR(key
);
317 if (cursor
+1 < x_max
)
319 else if (first
+ cursor
< len
)
325 uefi_call_wrapper(ST
->ConOut
->EnableCursor
, 2, ST
->ConOut
, FALSE
);
331 static UINTN
entry_lookup_key(Config
*config
, UINTN start
, CHAR16 key
) {
337 /* select entry by number key */
338 if (key
>= '1' && key
<= '9') {
340 if (i
> config
->entry_count
)
341 i
= config
->entry_count
;
345 /* find matching key in config entries */
346 for (i
= start
; i
< config
->entry_count
; i
++)
347 if (config
->entries
[i
]->key
== key
)
350 for (i
= 0; i
< start
; i
++)
351 if (config
->entries
[i
]->key
== key
)
357 static VOID
print_status(Config
*config
, EFI_FILE
*root_dir
, CHAR16
*loaded_image_path
) {
367 const EFI_GRAPHICS_OUTPUT_BLT_PIXEL
*pixel
= NULL
;
369 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
370 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
372 Print(L
"systemd-boot version: " VERSION
"\n");
373 Print(L
"architecture: " EFI_MACHINE_TYPE_NAME
"\n");
374 Print(L
"loaded image: %s\n", loaded_image_path
);
375 Print(L
"UEFI specification: %d.%02d\n", ST
->Hdr
.Revision
>> 16, ST
->Hdr
.Revision
& 0xffff);
376 Print(L
"firmware vendor: %s\n", ST
->FirmwareVendor
);
377 Print(L
"firmware version: %d.%02d\n", ST
->FirmwareRevision
>> 16, ST
->FirmwareRevision
& 0xffff);
379 if (uefi_call_wrapper(ST
->ConOut
->QueryMode
, 4, ST
->ConOut
, ST
->ConOut
->Mode
->Mode
, &x
, &y
) == EFI_SUCCESS
)
380 Print(L
"console size: %d x %d\n", x
, y
);
382 if (efivar_get_raw(&global_guid
, L
"SecureBoot", &b
, &size
) == EFI_SUCCESS
) {
383 Print(L
"SecureBoot: %s\n", *b
> 0 ? L
"enabled" : L
"disabled");
387 if (efivar_get_raw(&global_guid
, L
"SetupMode", &b
, &size
) == EFI_SUCCESS
) {
388 Print(L
"SetupMode: %s\n", *b
> 0 ? L
"setup" : L
"user");
392 if (efivar_get_raw(&global_guid
, L
"OsIndicationsSupported", &b
, &size
) == EFI_SUCCESS
) {
393 Print(L
"OsIndicationsSupported: %d\n", (UINT64
)*b
);
398 Print(L
"timeout: %d\n", config
->timeout_sec
);
399 if (config
->timeout_sec_efivar
>= 0)
400 Print(L
"timeout (EFI var): %d\n", config
->timeout_sec_efivar
);
401 Print(L
"timeout (config): %d\n", config
->timeout_sec_config
);
402 if (config
->entry_default_pattern
)
403 Print(L
"default pattern: '%s'\n", config
->entry_default_pattern
);
406 Print(L
"config entry count: %d\n", config
->entry_count
);
407 Print(L
"entry selected idx: %d\n", config
->idx_default
);
408 if (config
->idx_default_efivar
>= 0)
409 Print(L
"entry EFI var idx: %d\n", config
->idx_default_efivar
);
412 if (efivar_get_int(L
"LoaderConfigTimeout", &i
) == EFI_SUCCESS
)
413 Print(L
"LoaderConfigTimeout: %d\n", i
);
414 if (config
->entry_oneshot
)
415 Print(L
"LoaderEntryOneShot: %s\n", config
->entry_oneshot
);
416 if (efivar_get(L
"LoaderDeviceIdentifier", &s
) == EFI_SUCCESS
) {
417 Print(L
"LoaderDeviceIdentifier: %s\n", s
);
420 if (efivar_get(L
"LoaderDevicePartUUID", &s
) == EFI_SUCCESS
) {
421 Print(L
"LoaderDevicePartUUID: %s\n", s
);
424 if (efivar_get(L
"LoaderEntryDefault", &s
) == EFI_SUCCESS
) {
425 Print(L
"LoaderEntryDefault: %s\n", s
);
429 Print(L
"\n--- press key ---\n\n");
430 console_key_read(&key
, TRUE
);
432 for (i
= 0; i
< config
->entry_count
; i
++) {
435 if (key
== KEYPRESS(0, SCAN_ESC
, 0) || key
== KEYPRESS(0, 0, 'q'))
438 entry
= config
->entries
[i
];
441 UINT8
*content
= NULL
;
444 len
= file_read(root_dir
, entry
->splash
, 0, 0, &content
);
447 static const EFI_GRAPHICS_OUTPUT_BLT_PIXEL colors
[] = {
448 { .Red
= 0xff, .Green
= 0xff, .Blue
= 0xff },
449 { .Red
= 0xc0, .Green
= 0xc0, .Blue
= 0xc0 },
450 { .Red
= 0xff, .Green
= 0, .Blue
= 0 },
451 { .Red
= 0, .Green
= 0xff, .Blue
= 0 },
452 { .Red
= 0, .Green
= 0, .Blue
= 0xff },
453 { .Red
= 0, .Green
= 0, .Blue
= 0 },
456 err
= graphics_splash(content
, len
, pixel
);
460 /* 'b' rotates through background colors */
461 console_key_read(&key
, TRUE
);
462 if (key
!= KEYPRESS(0, 0, 'b'))
464 pixel
= &colors
[color
++];
465 if (color
== ELEMENTSOF(colors
))
471 graphics_mode(FALSE
);
472 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
475 Print(L
"config entry: %d/%d\n", i
+1, config
->entry_count
);
477 Print(L
"file '%s'\n", entry
->file
);
478 Print(L
"title show '%s'\n", entry
->title_show
);
480 Print(L
"title '%s'\n", entry
->title
);
482 Print(L
"version '%s'\n", entry
->version
);
483 if (entry
->machine_id
)
484 Print(L
"machine-id '%s'\n", entry
->machine_id
);
486 EFI_DEVICE_PATH
*device_path
;
489 device_path
= DevicePathFromHandle(entry
->device
);
491 str
= DevicePathToStr(device_path
);
492 Print(L
"device handle '%s'\n", str
);
497 Print(L
"loader '%s'\n", entry
->loader
);
499 Print(L
"options '%s'\n", entry
->options
);
501 Print(L
"splash '%s'\n", entry
->splash
);
502 Print(L
"auto-select %s\n", entry
->no_autoselect
? L
"no" : L
"yes");
504 Print(L
"internal call yes\n");
506 Print(L
"\n--- press key ---\n\n");
507 console_key_read(&key
, TRUE
);
510 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
513 static BOOLEAN
menu_run(Config
*config
, ConfigEntry
**chosen_entry
, EFI_FILE
*root_dir
, CHAR16
*loaded_image_path
) {
517 UINTN idx_highlight_prev
;
533 BOOLEAN exit
= FALSE
;
535 BOOLEAN wait
= FALSE
;
537 graphics_mode(FALSE
);
538 uefi_call_wrapper(ST
->ConIn
->Reset
, 2, ST
->ConIn
, FALSE
);
539 uefi_call_wrapper(ST
->ConOut
->EnableCursor
, 2, ST
->ConOut
, FALSE
);
540 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
542 /* draw a single character to make ClearScreen work on some firmware */
543 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, L
" ");
544 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
546 err
= uefi_call_wrapper(ST
->ConOut
->QueryMode
, 4, ST
->ConOut
, ST
->ConOut
->Mode
->Mode
, &x_max
, &y_max
);
547 if (EFI_ERROR(err
)) {
552 /* we check 10 times per second for a keystroke */
553 if (config
->timeout_sec
> 0)
554 timeout_remain
= config
->timeout_sec
* 10;
558 idx_highlight
= config
->idx_default
;
559 idx_highlight_prev
= 0;
561 visible_max
= y_max
- 2;
563 if ((UINTN
)config
->idx_default
>= visible_max
)
564 idx_first
= config
->idx_default
-1;
568 idx_last
= idx_first
+ visible_max
-1;
573 /* length of the longest entry */
575 for (i
= 0; i
< config
->entry_count
; i
++) {
578 entry_len
= StrLen(config
->entries
[i
]->title_show
);
579 if (line_width
< entry_len
)
580 line_width
= entry_len
;
582 if (line_width
> x_max
-6)
583 line_width
= x_max
-6;
585 /* offsets to center the entries on the screen */
586 x_start
= (x_max
- (line_width
)) / 2;
587 if (config
->entry_count
< visible_max
)
588 y_start
= ((visible_max
- config
->entry_count
) / 2) + 1;
592 /* menu entries title lines */
593 lines
= AllocatePool(sizeof(CHAR16
*) * config
->entry_count
);
594 for (i
= 0; i
< config
->entry_count
; i
++) {
597 lines
[i
] = AllocatePool(((x_max
+1) * sizeof(CHAR16
)));
598 for (j
= 0; j
< x_start
; j
++)
601 for (k
= 0; config
->entries
[i
]->title_show
[k
] != '\0' && j
< x_max
; j
++, k
++)
602 lines
[i
][j
] = config
->entries
[i
]->title_show
[k
];
604 for (; j
< x_max
; j
++)
606 lines
[i
][x_max
] = '\0';
610 clearline
= AllocatePool((x_max
+1) * sizeof(CHAR16
));
611 for (i
= 0; i
< x_max
; i
++)
619 for (i
= 0; i
< config
->entry_count
; i
++) {
620 if (i
< idx_first
|| i
> idx_last
)
622 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_start
+ i
- idx_first
);
623 if (i
== idx_highlight
)
624 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
,
625 EFI_BLACK
|EFI_BACKGROUND_LIGHTGRAY
);
627 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
,
628 EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
629 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, lines
[i
]);
630 if ((INTN
)i
== config
->idx_default_efivar
) {
631 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, x_start
-3, y_start
+ i
- idx_first
);
632 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, L
"=>");
636 } else if (highlight
) {
637 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_start
+ idx_highlight_prev
- idx_first
);
638 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
639 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, lines
[idx_highlight_prev
]);
640 if ((INTN
)idx_highlight_prev
== config
->idx_default_efivar
) {
641 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, x_start
-3, y_start
+ idx_highlight_prev
- idx_first
);
642 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, L
"=>");
645 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_start
+ idx_highlight
- idx_first
);
646 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_BLACK
|EFI_BACKGROUND_LIGHTGRAY
);
647 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, lines
[idx_highlight
]);
648 if ((INTN
)idx_highlight
== config
->idx_default_efivar
) {
649 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, x_start
-3, y_start
+ idx_highlight
- idx_first
);
650 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, L
"=>");
655 if (timeout_remain
> 0) {
657 status
= PoolPrint(L
"Boot in %d sec.", (timeout_remain
+ 5) / 10);
660 /* print status at last line of screen */
666 len
= StrLen(status
);
668 x
= (x_max
- len
) / 2;
671 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
672 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_max
-1);
673 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, clearline
+ (x_max
- x
));
674 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, status
);
675 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, clearline
+1 + x
+ len
);
678 err
= console_key_read(&key
, wait
);
679 if (EFI_ERROR(err
)) {
680 /* timeout reached */
681 if (timeout_remain
== 0) {
686 /* sleep and update status */
687 if (timeout_remain
> 0) {
688 uefi_call_wrapper(BS
->Stall
, 1, 100 * 1000);
693 /* timeout disabled, wait for next key */
700 /* clear status after keystroke */
704 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
705 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_max
-1);
706 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, clearline
+1);
709 idx_highlight_prev
= idx_highlight
;
712 case KEYPRESS(0, SCAN_UP
, 0):
713 case KEYPRESS(0, 0, 'k'):
714 if (idx_highlight
> 0)
718 case KEYPRESS(0, SCAN_DOWN
, 0):
719 case KEYPRESS(0, 0, 'j'):
720 if (idx_highlight
< config
->entry_count
-1)
724 case KEYPRESS(0, SCAN_HOME
, 0):
725 case KEYPRESS(EFI_ALT_PRESSED
, 0, '<'):
726 if (idx_highlight
> 0) {
732 case KEYPRESS(0, SCAN_END
, 0):
733 case KEYPRESS(EFI_ALT_PRESSED
, 0, '>'):
734 if (idx_highlight
< config
->entry_count
-1) {
736 idx_highlight
= config
->entry_count
-1;
740 case KEYPRESS(0, SCAN_PAGE_UP
, 0):
741 if (idx_highlight
> visible_max
)
742 idx_highlight
-= visible_max
;
747 case KEYPRESS(0, SCAN_PAGE_DOWN
, 0):
748 idx_highlight
+= visible_max
;
749 if (idx_highlight
> config
->entry_count
-1)
750 idx_highlight
= config
->entry_count
-1;
753 case KEYPRESS(0, 0, CHAR_LINEFEED
):
754 case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN
):
758 case KEYPRESS(0, SCAN_F1
, 0):
759 case KEYPRESS(0, 0, 'h'):
760 case KEYPRESS(0, 0, '?'):
761 status
= StrDuplicate(L
"(d)efault, (t/T)timeout, (e)dit, (v)ersion (Q)uit (P)rint (h)elp");
764 case KEYPRESS(0, 0, 'Q'):
769 case KEYPRESS(0, 0, 'd'):
770 if (config
->idx_default_efivar
!= (INTN
)idx_highlight
) {
771 /* store the selected entry in a persistent EFI variable */
772 efivar_set(L
"LoaderEntryDefault", config
->entries
[idx_highlight
]->file
, TRUE
);
773 config
->idx_default_efivar
= idx_highlight
;
774 status
= StrDuplicate(L
"Default boot entry selected.");
776 /* clear the default entry EFI variable */
777 efivar_set(L
"LoaderEntryDefault", NULL
, TRUE
);
778 config
->idx_default_efivar
= -1;
779 status
= StrDuplicate(L
"Default boot entry cleared.");
784 case KEYPRESS(0, 0, '-'):
785 case KEYPRESS(0, 0, 'T'):
786 if (config
->timeout_sec_efivar
> 0) {
787 config
->timeout_sec_efivar
--;
788 efivar_set_int(L
"LoaderConfigTimeout", config
->timeout_sec_efivar
, TRUE
);
789 if (config
->timeout_sec_efivar
> 0)
790 status
= PoolPrint(L
"Menu timeout set to %d sec.", config
->timeout_sec_efivar
);
792 status
= StrDuplicate(L
"Menu disabled. Hold down key at bootup to show menu.");
793 } else if (config
->timeout_sec_efivar
<= 0){
794 config
->timeout_sec_efivar
= -1;
795 efivar_set(L
"LoaderConfigTimeout", NULL
, TRUE
);
796 if (config
->timeout_sec_config
> 0)
797 status
= PoolPrint(L
"Menu timeout of %d sec is defined by configuration file.",
798 config
->timeout_sec_config
);
800 status
= StrDuplicate(L
"Menu disabled. Hold down key at bootup to show menu.");
804 case KEYPRESS(0, 0, '+'):
805 case KEYPRESS(0, 0, 't'):
806 if (config
->timeout_sec_efivar
== -1 && config
->timeout_sec_config
== 0)
807 config
->timeout_sec_efivar
++;
808 config
->timeout_sec_efivar
++;
809 efivar_set_int(L
"LoaderConfigTimeout", config
->timeout_sec_efivar
, TRUE
);
810 if (config
->timeout_sec_efivar
> 0)
811 status
= PoolPrint(L
"Menu timeout set to %d sec.",
812 config
->timeout_sec_efivar
);
814 status
= StrDuplicate(L
"Menu disabled. Hold down key at bootup to show menu.");
817 case KEYPRESS(0, 0, 'e'):
818 /* only the options of configured entries can be edited */
819 if (config
->entries
[idx_highlight
]->type
== LOADER_UNDEFINED
)
821 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_LIGHTGRAY
|EFI_BACKGROUND_BLACK
);
822 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_max
-1);
823 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, clearline
+1);
824 if (line_edit(config
->entries
[idx_highlight
]->options
, &config
->options_edit
, x_max
-1, y_max
-1))
826 uefi_call_wrapper(ST
->ConOut
->SetCursorPosition
, 3, ST
->ConOut
, 0, y_max
-1);
827 uefi_call_wrapper(ST
->ConOut
->OutputString
, 2, ST
->ConOut
, clearline
+1);
830 case KEYPRESS(0, 0, 'v'):
831 status
= PoolPrint(L
"systemd-boot " VERSION
" (" EFI_MACHINE_TYPE_NAME
"), UEFI Specification %d.%02d, Vendor %s %d.%02d",
832 ST
->Hdr
.Revision
>> 16, ST
->Hdr
.Revision
& 0xffff,
833 ST
->FirmwareVendor
, ST
->FirmwareRevision
>> 16, ST
->FirmwareRevision
& 0xffff);
836 case KEYPRESS(0, 0, 'P'):
837 print_status(config
, root_dir
, loaded_image_path
);
841 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, 'l'):
842 case KEYPRESS(EFI_CONTROL_PRESSED
, 0, CHAR_CTRL('l')):
847 /* jump with a hotkey directly to a matching entry */
848 idx
= entry_lookup_key(config
, idx_highlight
+1, KEYCHAR(key
));
855 if (idx_highlight
> idx_last
) {
856 idx_last
= idx_highlight
;
857 idx_first
= 1 + idx_highlight
- visible_max
;
860 if (idx_highlight
< idx_first
) {
861 idx_first
= idx_highlight
;
862 idx_last
= idx_highlight
+ visible_max
-1;
866 idx_last
= idx_first
+ visible_max
-1;
868 if (!refresh
&& idx_highlight
!= idx_highlight_prev
)
872 *chosen_entry
= config
->entries
[idx_highlight
];
874 for (i
= 0; i
< config
->entry_count
; i
++)
879 uefi_call_wrapper(ST
->ConOut
->SetAttribute
, 2, ST
->ConOut
, EFI_WHITE
|EFI_BACKGROUND_BLACK
);
880 uefi_call_wrapper(ST
->ConOut
->ClearScreen
, 1, ST
->ConOut
);
884 static VOID
config_add_entry(Config
*config
, ConfigEntry
*entry
) {
885 if ((config
->entry_count
& 15) == 0) {
888 i
= config
->entry_count
+ 16;
889 if (config
->entry_count
== 0)
890 config
->entries
= AllocatePool(sizeof(VOID
*) * i
);
892 config
->entries
= ReallocatePool(config
->entries
,
893 sizeof(VOID
*) * config
->entry_count
, sizeof(VOID
*) * i
);
895 config
->entries
[config
->entry_count
++] = entry
;
898 static VOID
config_entry_free(ConfigEntry
*entry
) {
899 FreePool(entry
->title_show
);
900 FreePool(entry
->title
);
901 FreePool(entry
->machine_id
);
902 FreePool(entry
->loader
);
903 FreePool(entry
->options
);
906 static BOOLEAN
is_digit(CHAR16 c
)
908 return (c
>= '0') && (c
<= '9');
911 static UINTN
c_order(CHAR16 c
)
917 else if ((c
>= 'a') && (c
<= 'z'))
923 static INTN
str_verscmp(CHAR16
*s1
, CHAR16
*s2
)
931 while ((*s1
&& !is_digit(*s1
)) || (*s2
&& !is_digit(*s2
))) {
934 order
= c_order(*s1
) - c_order(*s2
);
947 while (is_digit(*s1
) && is_digit(*s2
)) {
963 return StrCmp(os1
, os2
);
966 static CHAR8
*line_get_key_value(CHAR8
*content
, CHAR8
*sep
, UINTN
*pos
, CHAR8
**key_ret
, CHAR8
**value_ret
) {
972 line
= content
+ *pos
;
977 while (line
[linelen
] && !strchra((CHAR8
*)"\n\r", line
[linelen
]))
980 /* move pos to next line */
990 line
[linelen
] = '\0';
992 /* remove leading whitespace */
993 while (strchra((CHAR8
*)" \t", *line
)) {
998 /* remove trailing whitespace */
999 while (linelen
> 0 && strchra(sep
, line
[linelen
-1]))
1001 line
[linelen
] = '\0';
1006 /* split key/value */
1008 while (*value
&& !strchra(sep
, *value
))
1014 while (*value
&& strchra(sep
, *value
))
1018 if (value
[0] == '\"' && line
[linelen
-1] == '\"') {
1020 line
[linelen
-1] = '\0';
1028 static VOID
config_defaults_load_from_file(Config
*config
, CHAR8
*content
) {
1034 while ((line
= line_get_key_value(content
, (CHAR8
*)" \t", &pos
, &key
, &value
))) {
1035 if (strcmpa((CHAR8
*)"timeout", key
) == 0) {
1038 s
= stra_to_str(value
);
1039 config
->timeout_sec_config
= Atoi(s
);
1040 config
->timeout_sec
= config
->timeout_sec_config
;
1045 if (strcmpa((CHAR8
*)"default", key
) == 0) {
1046 FreePool(config
->entry_default_pattern
);
1047 config
->entry_default_pattern
= stra_to_str(value
);
1048 StrLwr(config
->entry_default_pattern
);
1054 static VOID
config_entry_add_from_file(Config
*config
, EFI_HANDLE
*device
, CHAR16
*file
, CHAR8
*content
, CHAR16
*loaded_image_path
) {
1060 CHAR16
*initrd
= NULL
;
1062 entry
= AllocateZeroPool(sizeof(ConfigEntry
));
1065 while ((line
= line_get_key_value(content
, (CHAR8
*)" \t", &pos
, &key
, &value
))) {
1066 if (strcmpa((CHAR8
*)"title", key
) == 0) {
1067 FreePool(entry
->title
);
1068 entry
->title
= stra_to_str(value
);
1072 if (strcmpa((CHAR8
*)"version", key
) == 0) {
1073 FreePool(entry
->version
);
1074 entry
->version
= stra_to_str(value
);
1078 if (strcmpa((CHAR8
*)"machine-id", key
) == 0) {
1079 FreePool(entry
->machine_id
);
1080 entry
->machine_id
= stra_to_str(value
);
1084 if (strcmpa((CHAR8
*)"linux", key
) == 0) {
1085 FreePool(entry
->loader
);
1086 entry
->type
= LOADER_LINUX
;
1087 entry
->loader
= stra_to_path(value
);
1092 if (strcmpa((CHAR8
*)"efi", key
) == 0) {
1093 entry
->type
= LOADER_EFI
;
1094 FreePool(entry
->loader
);
1095 entry
->loader
= stra_to_path(value
);
1097 /* do not add an entry for ourselves */
1098 if (StriCmp(entry
->loader
, loaded_image_path
) == 0) {
1099 entry
->type
= LOADER_UNDEFINED
;
1105 if (strcmpa((CHAR8
*)"architecture", key
) == 0) {
1106 /* do not add an entry for an EFI image of architecture not matching with that of the image */
1107 if (strcmpa((CHAR8
*)EFI_MACHINE_TYPE_NAME
, value
) != 0) {
1108 entry
->type
= LOADER_UNDEFINED
;
1114 if (strcmpa((CHAR8
*)"initrd", key
) == 0) {
1117 new = stra_to_path(value
);
1121 s
= PoolPrint(L
"%s initrd=%s", initrd
, new);
1125 initrd
= PoolPrint(L
"initrd=%s", new);
1130 if (strcmpa((CHAR8
*)"options", key
) == 0) {
1133 new = stra_to_str(value
);
1134 if (entry
->options
) {
1137 s
= PoolPrint(L
"%s %s", entry
->options
, new);
1138 FreePool(entry
->options
);
1141 entry
->options
= new;
1148 if (strcmpa((CHAR8
*)"splash", key
) == 0) {
1149 FreePool(entry
->splash
);
1150 entry
->splash
= stra_to_path(value
);
1155 if (entry
->type
== LOADER_UNDEFINED
) {
1156 config_entry_free(entry
);
1162 /* add initrd= to options */
1163 if (entry
->type
== LOADER_LINUX
&& initrd
) {
1164 if (entry
->options
) {
1167 s
= PoolPrint(L
"%s %s", initrd
, entry
->options
);
1168 FreePool(entry
->options
);
1171 entry
->options
= initrd
;
1177 if (entry
->machine_id
) {
1180 /* append additional options from EFI variables for this machine-id */
1181 var
= PoolPrint(L
"LoaderEntryOptions-%s", entry
->machine_id
);
1185 if (efivar_get(var
, &s
) == EFI_SUCCESS
) {
1186 if (entry
->options
) {
1189 s2
= PoolPrint(L
"%s %s", entry
->options
, s
);
1190 FreePool(entry
->options
);
1191 entry
->options
= s2
;
1198 var
= PoolPrint(L
"LoaderEntryOptionsOneShot-%s", entry
->machine_id
);
1202 if (efivar_get(var
, &s
) == EFI_SUCCESS
) {
1203 if (entry
->options
) {
1206 s2
= PoolPrint(L
"%s %s", entry
->options
, s
);
1207 FreePool(entry
->options
);
1208 entry
->options
= s2
;
1211 efivar_set(var
, NULL
, TRUE
);
1217 entry
->device
= device
;
1218 entry
->file
= StrDuplicate(file
);
1219 len
= StrLen(entry
->file
);
1220 /* remove ".conf" */
1222 entry
->file
[len
- 5] = '\0';
1223 StrLwr(entry
->file
);
1225 config_add_entry(config
, entry
);
1228 static VOID
config_load(Config
*config
, EFI_HANDLE
*device
, EFI_FILE
*root_dir
, CHAR16
*loaded_image_path
) {
1229 EFI_FILE_HANDLE entries_dir
;
1231 CHAR8
*content
= NULL
;
1236 len
= file_read(root_dir
, L
"\\loader\\loader.conf", 0, 0, &content
);
1238 config_defaults_load_from_file(config
, content
);
1241 err
= efivar_get_int(L
"LoaderConfigTimeout", &sec
);
1242 if (!EFI_ERROR(err
)) {
1243 config
->timeout_sec_efivar
= sec
;
1244 config
->timeout_sec
= sec
;
1246 config
->timeout_sec_efivar
= -1;
1248 err
= uefi_call_wrapper(root_dir
->Open
, 5, root_dir
, &entries_dir
, L
"\\loader\\entries", EFI_FILE_MODE_READ
, 0ULL);
1249 if (!EFI_ERROR(err
)) {
1254 CHAR8
*content
= NULL
;
1257 bufsize
= sizeof(buf
);
1258 err
= uefi_call_wrapper(entries_dir
->Read
, 3, entries_dir
, &bufsize
, buf
);
1259 if (bufsize
== 0 || EFI_ERROR(err
))
1262 f
= (EFI_FILE_INFO
*) buf
;
1263 if (f
->FileName
[0] == '.')
1265 if (f
->Attribute
& EFI_FILE_DIRECTORY
)
1267 len
= StrLen(f
->FileName
);
1270 if (StriCmp(f
->FileName
+ len
- 5, L
".conf") != 0)
1273 len
= file_read(entries_dir
, f
->FileName
, 0, 0, &content
);
1275 config_entry_add_from_file(config
, device
, f
->FileName
, content
, loaded_image_path
);
1278 uefi_call_wrapper(entries_dir
->Close
, 1, entries_dir
);
1281 /* sort entries after version number */
1282 for (i
= 1; i
< config
->entry_count
; i
++) {
1287 for (k
= 0; k
< config
->entry_count
- i
; k
++) {
1290 if (str_verscmp(config
->entries
[k
]->file
, config
->entries
[k
+1]->file
) <= 0)
1292 entry
= config
->entries
[k
];
1293 config
->entries
[k
] = config
->entries
[k
+1];
1294 config
->entries
[k
+1] = entry
;
1302 static VOID
config_default_entry_select(Config
*config
) {
1308 * The EFI variable to specify a boot entry for the next, and only the
1309 * next reboot. The variable is always cleared directly after it is read.
1311 err
= efivar_get(L
"LoaderEntryOneShot", &var
);
1312 if (!EFI_ERROR(err
)) {
1313 BOOLEAN found
= FALSE
;
1315 for (i
= 0; i
< config
->entry_count
; i
++) {
1316 if (StrCmp(config
->entries
[i
]->file
, var
) == 0) {
1317 config
->idx_default
= i
;
1323 config
->entry_oneshot
= StrDuplicate(var
);
1324 efivar_set(L
"LoaderEntryOneShot", NULL
, TRUE
);
1331 * The EFI variable to select the default boot entry overrides the
1332 * configured pattern. The variable can be set and cleared by pressing
1333 * the 'd' key in the loader selection menu, the entry is marked with
1336 err
= efivar_get(L
"LoaderEntryDefault", &var
);
1337 if (!EFI_ERROR(err
)) {
1338 BOOLEAN found
= FALSE
;
1340 for (i
= 0; i
< config
->entry_count
; i
++) {
1341 if (StrCmp(config
->entries
[i
]->file
, var
) == 0) {
1342 config
->idx_default
= i
;
1343 config
->idx_default_efivar
= i
;
1352 config
->idx_default_efivar
= -1;
1354 if (config
->entry_count
== 0)
1358 * Match the pattern from the end of the list to the start, find last
1359 * entry (largest number) matching the given pattern.
1361 if (config
->entry_default_pattern
) {
1362 i
= config
->entry_count
;
1364 if (config
->entries
[i
]->no_autoselect
)
1366 if (MetaiMatch(config
->entries
[i
]->file
, config
->entry_default_pattern
)) {
1367 config
->idx_default
= i
;
1373 /* select the last suitable entry */
1374 i
= config
->entry_count
;
1376 if (config
->entries
[i
]->no_autoselect
)
1378 config
->idx_default
= i
;
1382 /* no entry found */
1383 config
->idx_default
= -1;
1386 /* generate a unique title, avoiding non-distinguishable menu entries */
1387 static VOID
config_title_generate(Config
*config
) {
1392 for (i
= 0; i
< config
->entry_count
; i
++) {
1395 FreePool(config
->entries
[i
]->title_show
);
1396 title
= config
->entries
[i
]->title
;
1398 title
= config
->entries
[i
]->file
;
1399 config
->entries
[i
]->title_show
= StrDuplicate(title
);
1403 for (i
= 0; i
< config
->entry_count
; i
++) {
1404 for (k
= 0; k
< config
->entry_count
; k
++) {
1407 if (StrCmp(config
->entries
[i
]->title_show
, config
->entries
[k
]->title_show
) != 0)
1411 config
->entries
[i
]->non_unique
= TRUE
;
1412 config
->entries
[k
]->non_unique
= TRUE
;
1418 /* add version to non-unique titles */
1419 for (i
= 0; i
< config
->entry_count
; i
++) {
1422 if (!config
->entries
[i
]->non_unique
)
1424 if (!config
->entries
[i
]->version
)
1427 s
= PoolPrint(L
"%s (%s)", config
->entries
[i
]->title_show
, config
->entries
[i
]->version
);
1428 FreePool(config
->entries
[i
]->title_show
);
1429 config
->entries
[i
]->title_show
= s
;
1430 config
->entries
[i
]->non_unique
= FALSE
;
1434 for (i
= 0; i
< config
->entry_count
; i
++) {
1435 for (k
= 0; k
< config
->entry_count
; k
++) {
1438 if (StrCmp(config
->entries
[i
]->title_show
, config
->entries
[k
]->title_show
) != 0)
1442 config
->entries
[i
]->non_unique
= TRUE
;
1443 config
->entries
[k
]->non_unique
= TRUE
;
1449 /* add machine-id to non-unique titles */
1450 for (i
= 0; i
< config
->entry_count
; i
++) {
1454 if (!config
->entries
[i
]->non_unique
)
1456 if (!config
->entries
[i
]->machine_id
)
1459 m
= StrDuplicate(config
->entries
[i
]->machine_id
);
1461 s
= PoolPrint(L
"%s (%s)", config
->entries
[i
]->title_show
, m
);
1462 FreePool(config
->entries
[i
]->title_show
);
1463 config
->entries
[i
]->title_show
= s
;
1464 config
->entries
[i
]->non_unique
= FALSE
;
1469 for (i
= 0; i
< config
->entry_count
; i
++) {
1470 for (k
= 0; k
< config
->entry_count
; k
++) {
1473 if (StrCmp(config
->entries
[i
]->title_show
, config
->entries
[k
]->title_show
) != 0)
1477 config
->entries
[i
]->non_unique
= TRUE
;
1478 config
->entries
[k
]->non_unique
= TRUE
;
1484 /* add file name to non-unique titles */
1485 for (i
= 0; i
< config
->entry_count
; i
++) {
1488 if (!config
->entries
[i
]->non_unique
)
1490 s
= PoolPrint(L
"%s (%s)", config
->entries
[i
]->title_show
, config
->entries
[i
]->file
);
1491 FreePool(config
->entries
[i
]->title_show
);
1492 config
->entries
[i
]->title_show
= s
;
1493 config
->entries
[i
]->non_unique
= FALSE
;
1497 static BOOLEAN
config_entry_add_call(Config
*config
, CHAR16
*title
, EFI_STATUS (*call
)(VOID
)) {
1500 entry
= AllocateZeroPool(sizeof(ConfigEntry
));
1501 entry
->title
= StrDuplicate(title
);
1503 entry
->no_autoselect
= TRUE
;
1504 config_add_entry(config
, entry
);
1508 static ConfigEntry
*config_entry_add_loader(Config
*config
, EFI_HANDLE
*device
,
1509 enum loader_type type
,CHAR16
*file
, CHAR16 key
, CHAR16
*title
, CHAR16
*loader
) {
1512 entry
= AllocateZeroPool(sizeof(ConfigEntry
));
1514 entry
->title
= StrDuplicate(title
);
1515 entry
->device
= device
;
1516 entry
->loader
= StrDuplicate(loader
);
1517 entry
->file
= StrDuplicate(file
);
1518 StrLwr(entry
->file
);
1520 config_add_entry(config
, entry
);
1525 static BOOLEAN
config_entry_add_loader_auto(Config
*config
, EFI_HANDLE
*device
, EFI_FILE
*root_dir
, CHAR16
*loaded_image_path
,
1526 CHAR16
*file
, CHAR16 key
, CHAR16
*title
, CHAR16
*loader
) {
1527 EFI_FILE_HANDLE handle
;
1531 /* do not add an entry for ourselves */
1532 if (loaded_image_path
&& StriCmp(loader
, loaded_image_path
) == 0)
1535 /* check existence */
1536 err
= uefi_call_wrapper(root_dir
->Open
, 5, root_dir
, &handle
, loader
, EFI_FILE_MODE_READ
, 0ULL);
1539 uefi_call_wrapper(handle
->Close
, 1, handle
);
1541 entry
= config_entry_add_loader(config
, device
, LOADER_UNDEFINED
, file
, key
, title
, loader
);
1545 /* do not boot right away into auto-detected entries */
1546 entry
->no_autoselect
= TRUE
;
1548 /* export identifiers of automatically added entries */
1549 if (config
->entries_auto
) {
1552 s
= PoolPrint(L
"%s %s", config
->entries_auto
, file
);
1553 FreePool(config
->entries_auto
);
1554 config
->entries_auto
= s
;
1556 config
->entries_auto
= StrDuplicate(file
);
1561 static VOID
config_entry_add_osx(Config
*config
) {
1563 UINTN handle_count
= 0;
1564 EFI_HANDLE
*handles
= NULL
;
1566 err
= LibLocateHandle(ByProtocol
, &FileSystemProtocol
, NULL
, &handle_count
, &handles
);
1567 if (!EFI_ERROR(err
)) {
1570 for (i
= 0; i
< handle_count
; i
++) {
1574 root
= LibOpenRoot(handles
[i
]);
1577 found
= config_entry_add_loader_auto(config
, handles
[i
], root
, NULL
, L
"auto-osx", 'a', L
"OS X",
1578 L
"\\System\\Library\\CoreServices\\boot.efi");
1579 uefi_call_wrapper(root
->Close
, 1, root
);
1588 static VOID
config_entry_add_linux( Config
*config
, EFI_LOADED_IMAGE
*loaded_image
, EFI_FILE
*root_dir
) {
1589 EFI_FILE_HANDLE linux_dir
;
1592 err
= uefi_call_wrapper(root_dir
->Open
, 5, root_dir
, &linux_dir
, L
"\\EFI\\Linux", EFI_FILE_MODE_READ
, 0ULL);
1593 if (!EFI_ERROR(err
)) {
1598 CHAR8
*sections
[] = {
1602 UINTN offs
[ELEMENTSOF(sections
)-1] = {};
1603 UINTN szs
[ELEMENTSOF(sections
)-1] = {};
1604 UINTN addrs
[ELEMENTSOF(sections
)-1] = {};
1605 CHAR8
*content
= NULL
;
1610 CHAR16
*os_name
= NULL
;
1611 CHAR16
*os_id
= NULL
;
1612 CHAR16
*os_version
= NULL
;
1614 bufsize
= sizeof(buf
);
1615 err
= uefi_call_wrapper(linux_dir
->Read
, 3, linux_dir
, &bufsize
, buf
);
1616 if (bufsize
== 0 || EFI_ERROR(err
))
1619 f
= (EFI_FILE_INFO
*) buf
;
1620 if (f
->FileName
[0] == '.')
1622 if (f
->Attribute
& EFI_FILE_DIRECTORY
)
1624 len
= StrLen(f
->FileName
);
1627 if (StriCmp(f
->FileName
+ len
- 4, L
".efi") != 0)
1630 /* look for an .osrel section in the .efi binary */
1631 err
= pefile_locate_sections(linux_dir
, f
->FileName
, sections
, addrs
, offs
, szs
);
1635 len
= file_read(linux_dir
, f
->FileName
, offs
[0], szs
[0], &content
);
1639 /* read properties from the embedded os-release file */
1641 while ((line
= line_get_key_value(content
, (CHAR8
*)"=", &pos
, &key
, &value
))) {
1642 if (strcmpa((CHAR8
*)"PRETTY_NAME", key
) == 0) {
1643 os_name
= stra_to_str(value
);
1647 if (strcmpa((CHAR8
*)"ID", key
) == 0) {
1648 os_id
= stra_to_str(value
);
1652 if (strcmpa((CHAR8
*)"VERSION_ID", key
) == 0) {
1653 os_version
= stra_to_str(value
);
1658 if (os_name
&& os_id
&& os_version
) {
1662 conf
= PoolPrint(L
"%s-%s", os_id
, os_version
);
1663 path
= PoolPrint(L
"\\EFI\\Linux\\%s", f
->FileName
);
1664 config_entry_add_loader(config
, loaded_image
->DeviceHandle
, LOADER_LINUX
, conf
, 'l', os_name
, path
);
1669 FreePool(os_version
);
1674 uefi_call_wrapper(linux_dir
->Close
, 1, linux_dir
);
1678 static EFI_STATUS
image_start(EFI_HANDLE parent_image
, const Config
*config
, const ConfigEntry
*entry
) {
1680 EFI_DEVICE_PATH
*path
;
1684 path
= FileDevicePath(entry
->device
, entry
->loader
);
1686 Print(L
"Error getting device path.");
1687 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1688 return EFI_INVALID_PARAMETER
;
1691 err
= uefi_call_wrapper(BS
->LoadImage
, 6, FALSE
, parent_image
, path
, NULL
, 0, &image
);
1692 if (EFI_ERROR(err
)) {
1693 Print(L
"Error loading %s: %r", entry
->loader
, err
);
1694 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1698 if (config
->options_edit
)
1699 options
= config
->options_edit
;
1700 else if (entry
->options
)
1701 options
= entry
->options
;
1705 EFI_LOADED_IMAGE
*loaded_image
;
1707 err
= uefi_call_wrapper(BS
->OpenProtocol
, 6, image
, &LoadedImageProtocol
, (VOID
**)&loaded_image
,
1708 parent_image
, NULL
, EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
1709 if (EFI_ERROR(err
)) {
1710 Print(L
"Error getting LoadedImageProtocol handle: %r", err
);
1711 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1714 loaded_image
->LoadOptions
= options
;
1715 loaded_image
->LoadOptionsSize
= (StrLen(loaded_image
->LoadOptions
)+1) * sizeof(CHAR16
);
1718 efivar_set_time_usec(L
"LoaderTimeExecUSec", 0);
1719 err
= uefi_call_wrapper(BS
->StartImage
, 3, image
, NULL
, NULL
);
1721 uefi_call_wrapper(BS
->UnloadImage
, 1, image
);
1727 static EFI_STATUS
reboot_into_firmware(VOID
) {
1733 osind
= EFI_OS_INDICATIONS_BOOT_TO_FW_UI
;
1735 err
= efivar_get_raw(&global_guid
, L
"OsIndications", &b
, &size
);
1736 if (!EFI_ERROR(err
))
1737 osind
|= (UINT64
)*b
;
1740 err
= efivar_set_raw(&global_guid
, L
"OsIndications", (CHAR8
*)&osind
, sizeof(UINT64
), TRUE
);
1744 err
= uefi_call_wrapper(RT
->ResetSystem
, 4, EfiResetCold
, EFI_SUCCESS
, 0, NULL
);
1745 Print(L
"Error calling ResetSystem: %r", err
);
1746 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1750 static VOID
config_free(Config
*config
) {
1753 for (i
= 0; i
< config
->entry_count
; i
++)
1754 config_entry_free(config
->entries
[i
]);
1755 FreePool(config
->entries
);
1756 FreePool(config
->entry_default_pattern
);
1757 FreePool(config
->options_edit
);
1758 FreePool(config
->entry_oneshot
);
1759 FreePool(config
->entries_auto
);
1762 EFI_STATUS
efi_main(EFI_HANDLE image
, EFI_SYSTEM_TABLE
*sys_table
) {
1766 EFI_LOADED_IMAGE
*loaded_image
;
1768 CHAR16
*loaded_image_path
;
1769 EFI_DEVICE_PATH
*device_path
;
1773 BOOLEAN menu
= FALSE
;
1775 InitializeLib(image
, sys_table
);
1776 init_usec
= time_usec();
1777 efivar_set_time_usec(L
"LoaderTimeInitUSec", init_usec
);
1778 efivar_set(L
"LoaderInfo", L
"systemd-boot " VERSION
, FALSE
);
1779 s
= PoolPrint(L
"%s %d.%02d", ST
->FirmwareVendor
, ST
->FirmwareRevision
>> 16, ST
->FirmwareRevision
& 0xffff);
1780 efivar_set(L
"LoaderFirmwareInfo", s
, FALSE
);
1782 s
= PoolPrint(L
"UEFI %d.%02d", ST
->Hdr
.Revision
>> 16, ST
->Hdr
.Revision
& 0xffff);
1783 efivar_set(L
"LoaderFirmwareType", s
, FALSE
);
1786 err
= uefi_call_wrapper(BS
->OpenProtocol
, 6, image
, &LoadedImageProtocol
, (VOID
**)&loaded_image
,
1787 image
, NULL
, EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
1788 if (EFI_ERROR(err
)) {
1789 Print(L
"Error getting a LoadedImageProtocol handle: %r ", err
);
1790 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1794 /* export the device path this image is started from */
1795 device_path
= DevicePathFromHandle(loaded_image
->DeviceHandle
);
1798 EFI_DEVICE_PATH
*path
, *paths
;
1800 str
= DevicePathToStr(device_path
);
1801 efivar_set(L
"LoaderDeviceIdentifier", str
, FALSE
);
1804 paths
= UnpackDevicePath(device_path
);
1805 for (path
= paths
; !IsDevicePathEnd(path
); path
= NextDevicePathNode(path
)) {
1806 HARDDRIVE_DEVICE_PATH
*drive
;
1809 if (DevicePathType(path
) != MEDIA_DEVICE_PATH
)
1811 if (DevicePathSubType(path
) != MEDIA_HARDDRIVE_DP
)
1813 drive
= (HARDDRIVE_DEVICE_PATH
*)path
;
1814 if (drive
->SignatureType
!= SIGNATURE_TYPE_GUID
)
1817 GuidToString(uuid
, (EFI_GUID
*)&drive
->Signature
);
1818 efivar_set(L
"LoaderDevicePartUUID", uuid
, FALSE
);
1824 root_dir
= LibOpenRoot(loaded_image
->DeviceHandle
);
1826 Print(L
"Unable to open root directory: %r ", err
);
1827 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1828 return EFI_LOAD_ERROR
;
1832 /* the filesystem path to this image, to prevent adding ourselves to the menu */
1833 loaded_image_path
= DevicePathToStr(loaded_image
->FilePath
);
1834 efivar_set(L
"LoaderImageIdentifier", loaded_image_path
, FALSE
);
1836 /* scan "\loader\entries\*.conf" files */
1837 ZeroMem(&config
, sizeof(Config
));
1838 config_load(&config
, loaded_image
->DeviceHandle
, root_dir
, loaded_image_path
);
1840 /* if we find some well-known loaders, add them to the end of the list */
1841 config_entry_add_linux(&config
, loaded_image
, root_dir
);
1842 config_entry_add_loader_auto(&config
, loaded_image
->DeviceHandle
, root_dir
, loaded_image_path
,
1843 L
"auto-windows", 'w', L
"Windows Boot Manager", L
"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
1844 config_entry_add_loader_auto(&config
, loaded_image
->DeviceHandle
, root_dir
, loaded_image_path
,
1845 L
"auto-efi-shell", 's', L
"EFI Shell", L
"\\shell" EFI_MACHINE_TYPE_NAME
".efi");
1846 config_entry_add_loader_auto(&config
, loaded_image
->DeviceHandle
, root_dir
, loaded_image_path
,
1847 L
"auto-efi-default", '\0', L
"EFI Default Loader", L
"\\EFI\\Boot\\boot" EFI_MACHINE_TYPE_NAME
".efi");
1848 config_entry_add_osx(&config
);
1849 efivar_set(L
"LoaderEntriesAuto", config
.entries_auto
, FALSE
);
1851 if (efivar_get_raw(&global_guid
, L
"OsIndicationsSupported", &b
, &size
) == EFI_SUCCESS
) {
1852 UINT64 osind
= (UINT64
)*b
;
1854 if (osind
& EFI_OS_INDICATIONS_BOOT_TO_FW_UI
)
1855 config_entry_add_call(&config
, L
"Reboot Into Firmware Interface", reboot_into_firmware
);
1859 if (config
.entry_count
== 0) {
1860 Print(L
"No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
1861 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1865 config_title_generate(&config
);
1867 /* select entry by configured pattern or EFI LoaderDefaultEntry= variable*/
1868 config_default_entry_select(&config
);
1870 /* if no configured entry to select from was found, enable the menu */
1871 if (config
.idx_default
== -1) {
1872 config
.idx_default
= 0;
1873 if (config
.timeout_sec
== 0)
1874 config
.timeout_sec
= 10;
1877 /* select entry or show menu when key is pressed or timeout is set */
1878 if (config
.timeout_sec
== 0) {
1881 err
= console_key_read(&key
, FALSE
);
1882 if (!EFI_ERROR(err
)) {
1885 /* find matching key in config entries */
1886 idx
= entry_lookup_key(&config
, config
.idx_default
, KEYCHAR(key
));
1888 config
.idx_default
= idx
;
1898 entry
= config
.entries
[config
.idx_default
];
1900 efivar_set_time_usec(L
"LoaderTimeMenuUSec", 0);
1901 uefi_call_wrapper(BS
->SetWatchdogTimer
, 4, 0, 0x10000, 0, NULL
);
1902 if (!menu_run(&config
, &entry
, root_dir
, loaded_image_path
))
1905 /* run special entry like "reboot" */
1910 } else if (entry
->splash
) {
1911 UINT8
*content
= NULL
;
1914 len
= file_read(root_dir
, entry
->splash
, 0, 0, &content
);
1916 graphics_splash(content
, len
, NULL
);
1921 /* export the selected boot entry to the system */
1922 efivar_set(L
"LoaderEntrySelected", entry
->file
, FALSE
);
1924 uefi_call_wrapper(BS
->SetWatchdogTimer
, 4, 5 * 60, 0x10000, 0, NULL
);
1925 err
= image_start(image
, &config
, entry
);
1926 if (EFI_ERROR(err
)) {
1927 graphics_mode(FALSE
);
1928 Print(L
"\nFailed to execute %s (%s): %r\n", entry
->title
, entry
->loader
, err
);
1929 uefi_call_wrapper(BS
->Stall
, 1, 3 * 1000 * 1000);
1934 config
.timeout_sec
= 0;
1938 FreePool(loaded_image_path
);
1939 config_free(&config
);
1940 uefi_call_wrapper(root_dir
->Close
, 1, root_dir
);
1941 uefi_call_wrapper(BS
->CloseProtocol
, 4, image
, &LoadedImageProtocol
, image
, NULL
);