]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/boot/efi/boot.c
boot: add hotkeys for rebooting/powering off the system from the boot menu
[thirdparty/systemd.git] / src / boot / efi / boot.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
0fa2cac4 2
f7bc0fb3 3#include "bcd.h"
18fde343 4#include "bootspec-fundamental.h"
0fa2cac4 5#include "console.h"
e6d7dc9a 6#include "device-path-util.h"
6e86342b 7#include "devicetree.h"
2553a548 8#include "drivers.h"
c4964512 9#include "efivars-fundamental.h"
cf0fbc49 10#include "graphics.h"
acdf7d15 11#include "initrd.h"
0fa2cac4 12#include "linux.h"
92ed3bb4 13#include "measure.h"
0a1d8ac7 14#include "part-discovery.h"
d4cbada2 15#include "pe.h"
5080a60a
JJ
16#include "proto/block-io.h"
17#include "proto/device-path.h"
18#include "proto/simple-text-io.h"
e4dcf7aa 19#include "random-seed.h"
ce0f078f 20#include "secure-boot.h"
b2bb40ce 21#include "shim.h"
1e66a233 22#include "ticks.h"
d4cbada2 23#include "util.h"
2afeaf16 24#include "version.h"
5080a60a 25#include "vmm.h"
0fa2cac4 26
81d9db68
LP
27/* Magic string for recognizing our own binaries */
28_used_ _section_(".sdmagic") static const char magic[] =
29 "#### LoaderInfo: systemd-boot " GIT_VERSION " ####";
0fa2cac4 30
ad102dd0
JJ
31/* Makes systemd-boot available from \EFI\Linux\ for testing purposes. */
32_used_ _section_(".osrel") static const char osrel[] =
33 "ID=systemd-boot\n"
34 "VERSION=\"" GIT_VERSION "\"\n"
35 "NAME=\"systemd-boot " GIT_VERSION "\"\n";
36
e987d54b 37typedef enum LoaderType {
0fa2cac4 38 LOADER_UNDEFINED,
b00e37da 39 LOADER_AUTO,
0fa2cac4 40 LOADER_EFI,
362c6229
LP
41 LOADER_LINUX, /* Boot loader spec type #1 entries */
42 LOADER_UNIFIED_LINUX, /* Boot loader spec type #2 entries */
e6b0cfad 43 LOADER_SECURE_BOOT_KEYS,
e987d54b
LP
44 _LOADER_TYPE_MAX,
45} LoaderType;
0fa2cac4
KS
46
47typedef struct {
3639d1b0
JJ
48 char16_t *id; /* The unique identifier for this entry (typically the filename of the file defining the entry) */
49 char16_t *title_show; /* The string to actually display (this is made unique before showing) */
50 char16_t *title; /* The raw (human readable) title string of the entry (not necessarily unique) */
51 char16_t *sort_key; /* The string to use as primary sort key, usually ID= from os-release, possibly suffixed */
52 char16_t *version; /* The raw (human readable) version string of the entry */
53 char16_t *machine_id;
0fa2cac4 54 EFI_HANDLE *device;
e987d54b 55 LoaderType type;
3639d1b0
JJ
56 char16_t *loader;
57 char16_t *devicetree;
58 char16_t *options;
59 char16_t **initrd;
60 char16_t key;
70cd15e9 61 EFI_STATUS (*call)(void);
15338195
JJ
62 int tries_done;
63 int tries_left;
3639d1b0
JJ
64 char16_t *path;
65 char16_t *current_name;
66 char16_t *next_name;
0fa2cac4
KS
67} ConfigEntry;
68
69typedef struct {
70 ConfigEntry **entries;
3a59c55f 71 size_t n_entries;
dede50a7
JJ
72 size_t idx_default;
73 size_t idx_default_efivar;
db4122d1
JJ
74 uint32_t timeout_sec; /* Actual timeout used (efi_main() override > efivar > config). */
75 uint32_t timeout_sec_config;
76 uint32_t timeout_sec_efivar;
3639d1b0
JJ
77 char16_t *entry_default_config;
78 char16_t *entry_default_efivar;
79 char16_t *entry_oneshot;
80 char16_t *entry_saved;
e5a1b8f9
JJ
81 bool editor;
82 bool auto_entries;
83 bool auto_firmware;
84 bool reboot_for_bitlocker;
e6b0cfad 85 secure_boot_enroll secure_boot_enroll;
e5a1b8f9
JJ
86 bool force_menu;
87 bool use_saved_entry;
88 bool use_saved_entry_efivar;
89 bool beep;
db4122d1
JJ
90 int64_t console_mode;
91 int64_t console_mode_efivar;
0fa2cac4
KS
92} Config;
93
52b6b356
JJ
94/* These values have been chosen so that the transitions the user sees could
95 * employ unsigned over-/underflow like this:
96 * efivar unset ↔ force menu ↔ no timeout/skip menu ↔ 1 s ↔ 2 s ↔ … */
97enum {
98 TIMEOUT_MIN = 1,
99 TIMEOUT_MAX = UINT32_MAX - 2U,
100 TIMEOUT_UNSET = UINT32_MAX - 1U,
101 TIMEOUT_MENU_FORCE = UINT32_MAX,
102 TIMEOUT_MENU_HIDDEN = 0,
103 TIMEOUT_TYPE_MAX = UINT32_MAX,
104};
105
fb90100c
JJ
106enum {
107 IDX_MAX = INT16_MAX,
108 IDX_INVALID,
109};
110
dede50a7 111static void cursor_left(size_t *cursor, size_t *first) {
508df915
JJ
112 assert(cursor);
113 assert(first);
114
0fa2cac4
KS
115 if ((*cursor) > 0)
116 (*cursor)--;
117 else if ((*first) > 0)
118 (*first)--;
119}
120
dede50a7 121static void cursor_right(size_t *cursor, size_t *first, size_t x_max, size_t len) {
508df915
JJ
122 assert(cursor);
123 assert(first);
124
0fa2cac4
KS
125 if ((*cursor)+1 < x_max)
126 (*cursor)++;
127 else if ((*first) + (*cursor) < len)
128 (*first)++;
129}
130
dede50a7 131static bool line_edit(char16_t **line_in, size_t x_max, size_t y_pos) {
3639d1b0 132 _cleanup_free_ char16_t *line = NULL, *print = NULL;
dede50a7 133 size_t size, len, first = 0, cursor = 0, clear = 0;
0fa2cac4 134
5c330b36 135 assert(line_in);
508df915 136
5f497472 137 len = strlen16(*line_in);
ad9962bb 138 size = len + 1024;
3639d1b0
JJ
139 line = xnew(char16_t, size);
140 print = xnew(char16_t, x_max + 1);
08ed842b 141 strcpy16(line, strempty(*line_in));
0fa2cac4 142
ad9962bb 143 for (;;) {
0fa2cac4 144 EFI_STATUS err;
db4122d1 145 uint64_t key;
5080a60a 146 size_t j, cursor_color = EFI_TEXT_ATTR_SWAP(COLOR_EDIT);
0fa2cac4 147
2e65d610 148 j = MIN(len - first, x_max);
3639d1b0 149 memcpy(print, line + first, j * sizeof(char16_t));
2e65d610 150 while (clear > 0 && j < x_max) {
0fa2cac4 151 clear--;
258f0970 152 print[j++] = ' ';
0fa2cac4 153 }
258f0970 154 print[j] = '\0';
0fa2cac4 155
2e65d610 156 /* See comment at edit_line() call site for why we start at 1. */
e313e934 157 print_at(1, y_pos, COLOR_EDIT, print);
0fa2cac4 158
2e65d610
JJ
159 if (!print[cursor])
160 print[cursor] = ' ';
161 print[cursor+1] = '\0';
162 do {
163 print_at(cursor + 1, y_pos, cursor_color, print + cursor);
5080a60a 164 cursor_color = EFI_TEXT_ATTR_SWAP(cursor_color);
2e65d610
JJ
165
166 err = console_key_read(&key, 750 * 1000);
1cb5d785 167 if (!IN_SET(err, EFI_SUCCESS, EFI_TIMEOUT, EFI_NOT_READY))
e5a1b8f9 168 return false;
1cb5d785 169
2e65d610 170 print_at(cursor + 1, y_pos, COLOR_EDIT, print + cursor);
2a5e4fe4 171 } while (err != EFI_SUCCESS);
0fa2cac4
KS
172
173 switch (key) {
174 case KEYPRESS(0, SCAN_ESC, 0):
175 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'):
176 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'):
177 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')):
178 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')):
e5a1b8f9 179 return false;
0fa2cac4
KS
180
181 case KEYPRESS(0, SCAN_HOME, 0):
182 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'):
183 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')):
184 /* beginning-of-line */
185 cursor = 0;
186 first = 0;
187 continue;
188
189 case KEYPRESS(0, SCAN_END, 0):
190 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'):
191 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')):
192 /* end-of-line */
193 cursor = len - first;
194 if (cursor+1 >= x_max) {
195 cursor = x_max-1;
196 first = len - (x_max-1);
197 }
198 continue;
199
200 case KEYPRESS(0, SCAN_DOWN, 0):
201 case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'):
202 case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0):
203 /* forward-word */
977f65f0 204 while (line[first + cursor] == ' ')
0fa2cac4
KS
205 cursor_right(&cursor, &first, x_max, len);
206 while (line[first + cursor] && line[first + cursor] != ' ')
207 cursor_right(&cursor, &first, x_max, len);
0fa2cac4
KS
208 continue;
209
210 case KEYPRESS(0, SCAN_UP, 0):
211 case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'):
212 case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0):
213 /* backward-word */
214 if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
215 cursor_left(&cursor, &first);
216 while ((first + cursor) > 0 && line[first + cursor] == ' ')
217 cursor_left(&cursor, &first);
218 }
219 while ((first + cursor) > 0 && line[first + cursor-1] != ' ')
220 cursor_left(&cursor, &first);
0fa2cac4
KS
221 continue;
222
223 case KEYPRESS(0, SCAN_RIGHT, 0):
224 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'):
225 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')):
226 /* forward-char */
227 if (first + cursor == len)
228 continue;
229 cursor_right(&cursor, &first, x_max, len);
0fa2cac4
KS
230 continue;
231
232 case KEYPRESS(0, SCAN_LEFT, 0):
233 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'):
234 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')):
235 /* backward-char */
236 cursor_left(&cursor, &first);
0fa2cac4
KS
237 continue;
238
230f7820 239 case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_DELETE, 0):
0fa2cac4
KS
240 case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'):
241 /* kill-word */
242 clear = 0;
258f0970 243
dede50a7 244 size_t k;
258f0970 245 for (k = first + cursor; k < len && line[k] == ' '; k++)
0fa2cac4 246 clear++;
258f0970 247 for (; k < len && line[k] != ' '; k++)
0fa2cac4
KS
248 clear++;
249
dede50a7 250 for (size_t i = first + cursor; i + clear < len; i++)
0fa2cac4
KS
251 line[i] = line[i + clear];
252 len -= clear;
253 line[len] = '\0';
254 continue;
255
256 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'):
257 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')):
b3e25576 258 case KEYPRESS(EFI_ALT_PRESSED, 0, '\b'):
0fa2cac4
KS
259 /* backward-kill-word */
260 clear = 0;
261 if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
262 cursor_left(&cursor, &first);
263 clear++;
264 while ((first + cursor) > 0 && line[first + cursor] == ' ') {
265 cursor_left(&cursor, &first);
266 clear++;
267 }
268 }
269 while ((first + cursor) > 0 && line[first + cursor-1] != ' ') {
270 cursor_left(&cursor, &first);
271 clear++;
272 }
0fa2cac4 273
dede50a7 274 for (size_t i = first + cursor; i + clear < len; i++)
0fa2cac4
KS
275 line[i] = line[i + clear];
276 len -= clear;
277 line[len] = '\0';
278 continue;
279
280 case KEYPRESS(0, SCAN_DELETE, 0):
281 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'):
282 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')):
283 if (len == 0)
284 continue;
285 if (first + cursor == len)
286 continue;
dede50a7 287 for (size_t i = first + cursor; i < len; i++)
0fa2cac4
KS
288 line[i] = line[i+1];
289 clear = 1;
290 len--;
291 continue;
292
293 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'):
294 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')):
295 /* kill-line */
296 line[first + cursor] = '\0';
297 clear = len - (first + cursor);
298 len = first + cursor;
299 continue;
300
b3e25576
JJ
301 case KEYPRESS(0, 0, '\n'):
302 case KEYPRESS(0, 0, '\r'):
28fda21c 303 case KEYPRESS(0, SCAN_F3, 0): /* EZpad Mini 4s firmware sends malformed events */
b3e25576 304 case KEYPRESS(0, SCAN_F3, '\r'): /* Teclast X98+ II firmware sends malformed events */
ec436bdf 305 if (!streq16(line, *line_in)) {
0af26643 306 free(*line_in);
5c330b36
JJ
307 *line_in = TAKE_PTR(line);
308 }
e5a1b8f9 309 return true;
0fa2cac4 310
b3e25576 311 case KEYPRESS(0, 0, '\b'):
0fa2cac4
KS
312 if (len == 0)
313 continue;
314 if (first == 0 && cursor == 0)
315 continue;
dede50a7 316 for (size_t i = first + cursor-1; i < len; i++)
0fa2cac4
KS
317 line[i] = line[i+1];
318 clear = 1;
319 len--;
320 if (cursor > 0)
321 cursor--;
322 if (cursor > 0 || first == 0)
323 continue;
324 /* show full line if it fits */
325 if (len < x_max) {
326 cursor = first;
327 first = 0;
328 continue;
329 }
330 /* jump left to see what we delete */
331 if (first > 10) {
332 first -= 10;
333 cursor = 10;
334 } else {
335 cursor = first;
336 first = 0;
337 }
338 continue;
339
340 case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
341 case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
342 if (len+1 == size)
343 continue;
dede50a7 344 for (size_t i = len; i > first + cursor; i--)
0fa2cac4
KS
345 line[i] = line[i-1];
346 line[first + cursor] = KEYCHAR(key);
347 len++;
348 line[len] = '\0';
349 if (cursor+1 < x_max)
350 cursor++;
351 else if (first + cursor < len)
352 first++;
353 continue;
354 }
355 }
0fa2cac4
KS
356}
357
dede50a7 358static size_t entry_lookup_key(Config *config, size_t start, char16_t key) {
508df915
JJ
359 assert(config);
360
0fa2cac4 361 if (key == 0)
fb90100c 362 return IDX_INVALID;
0fa2cac4
KS
363
364 /* select entry by number key */
365 if (key >= '1' && key <= '9') {
dede50a7 366 size_t i = key - '0';
3a59c55f
LP
367 if (i > config->n_entries)
368 i = config->n_entries;
0fa2cac4
KS
369 return i-1;
370 }
371
372 /* find matching key in config entries */
3a59c55f 373 for (size_t i = start; i < config->n_entries; i++)
0fa2cac4
KS
374 if (config->entries[i]->key == key)
375 return i;
376
dede50a7 377 for (size_t i = 0; i < start; i++)
0fa2cac4
KS
378 if (config->entries[i]->key == key)
379 return i;
380
fb90100c 381 return IDX_INVALID;
0fa2cac4
KS
382}
383
e5a1b8f9 384static char16_t *update_timeout_efivar(uint32_t *t, bool inc) {
52b6b356
JJ
385 assert(t);
386
387 switch (*t) {
388 case TIMEOUT_MAX:
389 *t = inc ? TIMEOUT_MAX : (*t - 1);
390 break;
391 case TIMEOUT_UNSET:
392 *t = inc ? TIMEOUT_MENU_FORCE : TIMEOUT_UNSET;
393 break;
394 case TIMEOUT_MENU_FORCE:
395 *t = inc ? TIMEOUT_MENU_HIDDEN : TIMEOUT_UNSET;
396 break;
397 case TIMEOUT_MENU_HIDDEN:
398 *t = inc ? TIMEOUT_MIN : TIMEOUT_MENU_FORCE;
399 break;
400 default:
401 *t += inc ? 1 : -1;
402 }
403
404 switch (*t) {
405 case TIMEOUT_UNSET:
7783ab5d 406 return xstrdup16(u"Menu timeout defined by configuration file.");
52b6b356 407 case TIMEOUT_MENU_FORCE:
7783ab5d 408 return xstrdup16(u"Timeout disabled, menu will always be shown.");
52b6b356 409 case TIMEOUT_MENU_HIDDEN:
7783ab5d 410 return xstrdup16(u"Menu disabled. Hold down key at bootup to show menu.");
52b6b356 411 default:
2f3c3b0b 412 return xasprintf("Menu timeout set to %u s.", *t);
52b6b356
JJ
413 }
414}
415
e5a1b8f9 416static bool unicode_supported(void) {
1f6a5d9a 417 static int cache = -1;
592d5765
JJ
418
419 if (cache < 0)
420 /* Basic unicode box drawing support is mandated by the spec, but it does
421 * not hurt to make sure it works. */
a083aed0 422 cache = ST->ConOut->TestString(ST->ConOut, (char16_t *) u"─") == EFI_SUCCESS;
592d5765
JJ
423
424 return cache;
425}
426
e5a1b8f9 427static bool ps_continue(void) {
9220b2c4
JJ
428 const char16_t *sep = unicode_supported() ? u"───" : u"---";
429 printf("\n%ls Press any key to continue, ESC or q to quit. %ls\n\n", sep, sep);
592d5765 430
db4122d1 431 uint64_t key;
2a5e4fe4
JJ
432 return console_key_read(&key, UINT64_MAX) == EFI_SUCCESS &&
433 !IN_SET(key, KEYPRESS(0, SCAN_ESC, 0), KEYPRESS(0, 0, 'q'), KEYPRESS(0, 0, 'Q'));
1cb5d785
JJ
434}
435
3639d1b0 436static void print_status(Config *config, char16_t *loaded_image_path) {
dede50a7 437 size_t x_max, y_max;
db4122d1 438 uint32_t screen_width = 0, screen_height = 0;
c4964512 439 SecureBootMode secure;
3639d1b0 440 _cleanup_free_ char16_t *device_part_uuid = NULL;
0fa2cac4 441
508df915 442 assert(config);
508df915 443
1b965abc 444 clear_screen(COLOR_NORMAL);
134144ab 445 console_query_mode(&x_max, &y_max);
1926a114 446 query_screen_resolution(&screen_width, &screen_height);
134144ab 447
c4964512 448 secure = secure_boot_mode();
19f08504 449 (void) efivar_get(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", &device_part_uuid);
f6943400 450
9220b2c4
JJ
451 printf(" systemd-boot version: " GIT_VERSION "\n");
452 if (loaded_image_path)
453 printf(" loaded image: %ls\n", loaded_image_path);
454 if (device_part_uuid)
455 printf(" loader partition UUID: %ls\n", device_part_uuid);
456 printf(" architecture: " EFI_MACHINE_TYPE_NAME "\n");
457 printf(" UEFI specification: %u.%02u\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
458 printf(" firmware vendor: %ls\n", ST->FirmwareVendor);
459 printf(" firmware version: %u.%02u\n", ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
460 printf(" OS indications: %#" PRIx64 "\n", get_os_indications_supported());
461 printf(" secure boot: %ls (%ls)\n",
462 yes_no(IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED)),
463 secure_boot_mode_to_string(secure));
464 printf(" shim: %ls\n", yes_no(shim_loaded()));
465 printf(" TPM: %ls\n", yes_no(tpm_present()));
466 printf(" console mode: %i/%" PRIi64 " (%zux%zu @%ux%u)\n",
467 ST->ConOut->Mode->Mode, ST->ConOut->Mode->MaxMode - INT64_C(1),
468 x_max, y_max, screen_width, screen_height);
f6943400 469
1cb5d785
JJ
470 if (!ps_continue())
471 return;
0fa2cac4 472
f6943400
JJ
473 switch (config->timeout_sec_config) {
474 case TIMEOUT_UNSET:
9220b2c4 475 break;
f6943400 476 case TIMEOUT_MENU_FORCE:
9220b2c4
JJ
477 printf(" timeout (config): menu-force\n");
478 break;
f6943400 479 case TIMEOUT_MENU_HIDDEN:
9220b2c4
JJ
480 printf(" timeout (config): menu-hidden\n");
481 break;
e4dcf7aa 482 default:
9220b2c4 483 printf(" timeout (config): %u s\n", config->timeout_sec_config);
e4dcf7aa
LP
484 }
485
f6943400
JJ
486 switch (config->timeout_sec_efivar) {
487 case TIMEOUT_UNSET:
9220b2c4 488 break;
f6943400 489 case TIMEOUT_MENU_FORCE:
9220b2c4
JJ
490 printf(" timeout (EFI var): menu-force\n");
491 break;
f6943400 492 case TIMEOUT_MENU_HIDDEN:
9220b2c4
JJ
493 printf(" timeout (EFI var): menu-hidden\n");
494 break;
f6943400 495 default:
9220b2c4 496 printf(" timeout (EFI var): %u s\n", config->timeout_sec_efivar);
f6943400 497 }
0fa2cac4 498
9220b2c4
JJ
499 if (config->entry_default_config)
500 printf(" default (config): %ls\n", config->entry_default_config);
501 if (config->entry_default_efivar)
502 printf(" default (EFI var): %ls\n", config->entry_default_efivar);
503 if (config->entry_oneshot)
504 printf(" default (one-shot): %ls\n", config->entry_oneshot);
505 if (config->entry_saved)
506 printf(" saved entry: %ls\n", config->entry_saved);
507 printf(" editor: %ls\n", yes_no(config->editor));
508 printf(" auto-entries: %ls\n", yes_no(config->auto_entries));
509 printf(" auto-firmware: %ls\n", yes_no(config->auto_firmware));
510 printf(" beep: %ls\n", yes_no(config->beep));
511 printf(" reboot-for-bitlocker: %ls\n", yes_no(config->reboot_for_bitlocker));
f6943400 512
e6b0cfad
VD
513 switch (config->secure_boot_enroll) {
514 case ENROLL_OFF:
9220b2c4
JJ
515 printf(" secure-boot-enroll: off\n");
516 break;
e6b0cfad 517 case ENROLL_MANUAL:
9220b2c4
JJ
518 printf(" secure-boot-enroll: manual\n");
519 break;
1e8e7f44
JJ
520 case ENROLL_IF_SAFE:
521 printf(" secure-boot-enroll: if-safe\n");
522 break;
e6b0cfad 523 case ENROLL_FORCE:
9220b2c4
JJ
524 printf(" secure-boot-enroll: force\n");
525 break;
e6b0cfad
VD
526 default:
527 assert_not_reached();
528 }
529
f6943400
JJ
530 switch (config->console_mode) {
531 case CONSOLE_MODE_AUTO:
9220b2c4
JJ
532 printf(" console-mode (config): auto\n");
533 break;
f6943400 534 case CONSOLE_MODE_KEEP:
9220b2c4
JJ
535 printf(" console-mode (config): keep\n");
536 break;
f6943400 537 case CONSOLE_MODE_FIRMWARE_MAX:
9220b2c4
JJ
538 printf(" console-mode (config): max\n");
539 break;
f6943400 540 default:
9220b2c4
JJ
541 printf(" console-mode (config): %" PRIi64 "\n", config->console_mode);
542 break;
f6943400 543 }
8ac69471 544
f6943400
JJ
545 /* EFI var console mode is always a concrete value or unset. */
546 if (config->console_mode_efivar != CONSOLE_MODE_KEEP)
9220b2c4 547 printf("console-mode (EFI var): %" PRIi64 "\n", config->console_mode_efivar);
0fa2cac4 548
1cb5d785
JJ
549 if (!ps_continue())
550 return;
0fa2cac4 551
3a59c55f 552 for (size_t i = 0; i < config->n_entries; i++) {
f6943400 553 ConfigEntry *entry = config->entries[i];
9220b2c4
JJ
554 EFI_DEVICE_PATH *dp = NULL;
555 _cleanup_free_ char16_t *dp_str = NULL;
556
557 if (entry->device &&
19f08504 558 BS->HandleProtocol(entry->device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) ==
9220b2c4
JJ
559 EFI_SUCCESS)
560 (void) device_path_to_str(dp, &dp_str);
561
3a59c55f 562 printf(" config entry: %zu/%zu\n", i + 1, config->n_entries);
9220b2c4
JJ
563 printf(" id: %ls\n", entry->id);
564 if (entry->title)
565 printf(" title: %ls\n", entry->title);
566 if (entry->title_show && !streq16(entry->title, entry->title_show))
567 printf(" title show: %ls\n", entry->title_show);
568 if (entry->sort_key)
569 printf(" sort key: %ls\n", entry->sort_key);
570 if (entry->version)
571 printf(" version: %ls\n", entry->version);
572 if (entry->machine_id)
573 printf(" machine-id: %ls\n", entry->machine_id);
574 if (dp_str)
575 printf(" device: %ls\n", dp_str);
576 if (entry->loader)
577 printf(" loader: %ls\n", entry->loader);
acdf7d15 578 STRV_FOREACH(initrd, entry->initrd)
9220b2c4
JJ
579 printf(" initrd: %ls\n", *initrd);
580 if (entry->devicetree)
581 printf(" devicetree: %ls\n", entry->devicetree);
582 if (entry->options)
583 printf(" options: %ls\n", entry->options);
584 printf(" internal call: %ls\n", yes_no(!!entry->call));
585
586 printf("counting boots: %ls\n", yes_no(entry->tries_left >= 0));
15338195 587 if (entry->tries_left >= 0) {
9220b2c4
JJ
588 printf(" tries: %i left, %i done\n", entry->tries_left, entry->tries_done);
589 printf(" current path: %ls\\%ls\n", entry->path, entry->current_name);
590 printf(" next path: %ls\\%ls\n", entry->path, entry->next_name);
0fa2cac4 591 }
f6943400 592
1cb5d785
JJ
593 if (!ps_continue())
594 return;
0fa2cac4 595 }
0fa2cac4
KS
596}
597
e6cab77e 598static EFI_STATUS reboot_into_firmware(void) {
db4122d1 599 uint64_t osind = 0;
e6cab77e
JJ
600 EFI_STATUS err;
601
785b5fcf 602 if (!FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI))
c2c62035 603 return log_error_status(EFI_UNSUPPORTED, "Reboot to firmware interface not supported.");
e6cab77e 604
19f08504 605 (void) efivar_get_uint64_le(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"OsIndications", &osind);
e6cab77e
JJ
606 osind |= EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
607
19f08504 608 err = efivar_set_uint64_le(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"OsIndications", osind, EFI_VARIABLE_NON_VOLATILE);
2a5e4fe4 609 if (err != EFI_SUCCESS)
c2c62035 610 return log_error_status(err, "Error setting OsIndications: %m");
e6cab77e 611
c4ba5fef
JJ
612 RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
613 assert_not_reached();
e6cab77e
JJ
614}
615
e5a1b8f9 616static bool menu_run(
580fe4df
LP
617 Config *config,
618 ConfigEntry **chosen_entry,
3639d1b0 619 char16_t *loaded_image_path) {
580fe4df 620
508df915
JJ
621 assert(config);
622 assert(chosen_entry);
508df915 623
0fa2cac4 624 EFI_STATUS err;
dede50a7
JJ
625 size_t visible_max = 0;
626 size_t idx_highlight = config->idx_default, idx_highlight_prev = 0;
627 size_t idx, idx_first = 0, idx_last = 0;
e5a1b8f9
JJ
628 bool new_mode = true, clear = true;
629 bool refresh = true, highlight = false;
dede50a7 630 size_t x_start = 0, y_start = 0, y_status = 0, x_max, y_max;
3639d1b0
JJ
631 _cleanup_(strv_freep) char16_t **lines = NULL;
632 _cleanup_free_ char16_t *clearline = NULL, *separator = NULL, *status = NULL;
db4122d1
JJ
633 uint32_t timeout_efivar_saved = config->timeout_sec_efivar;
634 uint32_t timeout_remain = config->timeout_sec == TIMEOUT_MENU_FORCE ? 0 : config->timeout_sec;
e5a1b8f9 635 bool exit = false, run = true, firmware_setup = false;
db4122d1 636 int64_t console_mode_initial = ST->ConOut->Mode->Mode, console_mode_efivar_saved = config->console_mode_efivar;
dede50a7 637 size_t default_efivar_saved = config->idx_default_efivar;
0fa2cac4 638
e5a1b8f9
JJ
639 graphics_mode(false);
640 ST->ConIn->Reset(ST->ConIn, false);
641 ST->ConOut->EnableCursor(ST->ConOut, false);
0fa2cac4
KS
642
643 /* draw a single character to make ClearScreen work on some firmware */
9220b2c4 644 ST->ConOut->OutputString(ST->ConOut, (char16_t *) u" ");
68d4b8ac 645
1b965abc
JJ
646 err = console_set_mode(config->console_mode_efivar != CONSOLE_MODE_KEEP ?
647 config->console_mode_efivar : config->console_mode);
2a5e4fe4 648 if (err != EFI_SUCCESS) {
1b965abc 649 clear_screen(COLOR_NORMAL);
c2c62035 650 log_error_status(err, "Error switching console mode: %m");
1b965abc 651 }
0fa2cac4 652
dede50a7 653 size_t line_width = 0, entry_padding = 3;
1b965abc 654 while (!exit) {
db4122d1 655 uint64_t key;
0fa2cac4 656
1b965abc 657 if (new_mode) {
1b965abc 658 console_query_mode(&x_max, &y_max);
0fa2cac4 659
1b965abc
JJ
660 /* account for padding+status */
661 visible_max = y_max - 2;
0fa2cac4 662
1b965abc
JJ
663 /* Drawing entries starts at idx_first until idx_last. We want to make
664 * sure that idx_highlight is centered, but not if we are close to the
665 * beginning/end of the entry list. Otherwise we would have a half-empty
666 * screen. */
3a59c55f 667 if (config->n_entries <= visible_max || idx_highlight <= visible_max / 2)
1b965abc 668 idx_first = 0;
3a59c55f
LP
669 else if (idx_highlight >= config->n_entries - (visible_max / 2))
670 idx_first = config->n_entries - visible_max;
1b965abc
JJ
671 else
672 idx_first = idx_highlight - (visible_max / 2);
673 idx_last = idx_first + visible_max - 1;
674
675 /* length of the longest entry */
592d5765 676 line_width = 0;
3a59c55f 677 for (size_t i = 0; i < config->n_entries; i++)
5f497472 678 line_width = MAX(line_width, strlen16(config->entries[i]->title_show));
1b965abc
JJ
679 line_width = MIN(line_width + 2 * entry_padding, x_max);
680
681 /* offsets to center the entries on the screen */
682 x_start = (x_max - (line_width)) / 2;
3a59c55f
LP
683 if (config->n_entries < visible_max)
684 y_start = ((visible_max - config->n_entries) / 2) + 1;
1b965abc
JJ
685 else
686 y_start = 0;
0fa2cac4 687
1b965abc 688 /* Put status line after the entry list, but give it some breathing room. */
3a59c55f 689 y_status = MIN(y_start + MIN(visible_max, config->n_entries) + 1, y_max - 1);
0fa2cac4 690
9f048123
JJ
691 lines = strv_free(lines);
692 clearline = mfree(clearline);
592d5765 693 separator = mfree(separator);
0fa2cac4 694
1b965abc 695 /* menu entries title lines */
3a59c55f 696 lines = xnew(char16_t *, config->n_entries + 1);
9f048123 697
3a59c55f 698 for (size_t i = 0; i < config->n_entries; i++) {
dede50a7 699 size_t j, padding;
1b965abc 700
3639d1b0 701 lines[i] = xnew(char16_t, line_width + 1);
5f497472 702 padding = (line_width - MIN(strlen16(config->entries[i]->title_show), line_width)) / 2;
1b965abc
JJ
703
704 for (j = 0; j < padding; j++)
705 lines[i][j] = ' ';
706
dede50a7 707 for (size_t k = 0; config->entries[i]->title_show[k] != '\0' && j < line_width; j++, k++)
1b965abc
JJ
708 lines[i][j] = config->entries[i]->title_show[k];
709
710 for (; j < line_width; j++)
711 lines[i][j] = ' ';
712 lines[i][line_width] = '\0';
713 }
3a59c55f 714 lines[config->n_entries] = NULL;
1b965abc 715
3639d1b0
JJ
716 clearline = xnew(char16_t, x_max + 1);
717 separator = xnew(char16_t, x_max + 1);
dede50a7 718 for (size_t i = 0; i < x_max; i++) {
1b965abc 719 clearline[i] = ' ';
592d5765
JJ
720 separator[i] = unicode_supported() ? L'─' : L'-';
721 }
1b965abc 722 clearline[x_max] = 0;
592d5765 723 separator[x_max] = 0;
1b965abc 724
e5a1b8f9
JJ
725 new_mode = false;
726 clear = true;
1b965abc
JJ
727 }
728
729 if (clear) {
730 clear_screen(COLOR_NORMAL);
e5a1b8f9
JJ
731 clear = false;
732 refresh = true;
1b965abc
JJ
733 }
734
735 if (refresh) {
3a59c55f 736 for (size_t i = idx_first; i <= idx_last && i < config->n_entries; i++) {
1ab39cc1 737 print_at(x_start, y_start + i - idx_first,
0b6a4795 738 i == idx_highlight ? COLOR_HIGHLIGHT : COLOR_ENTRY,
c005f437 739 lines[i]);
fb90100c 740 if (i == config->idx_default_efivar)
3639d1b0
JJ
741 print_at(x_start,
742 y_start + i - idx_first,
0b6a4795 743 i == idx_highlight ? COLOR_HIGHLIGHT : COLOR_ENTRY,
a083aed0 744 unicode_supported() ? u" ►" : u"=>");
0fa2cac4 745 }
e5a1b8f9 746 refresh = false;
0fa2cac4 747 } else if (highlight) {
e313e934 748 print_at(x_start, y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, lines[idx_highlight_prev]);
1ab39cc1 749 print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, lines[idx_highlight]);
fb90100c 750 if (idx_highlight_prev == config->idx_default_efivar)
3639d1b0
JJ
751 print_at(x_start,
752 y_start + idx_highlight_prev - idx_first,
753 COLOR_ENTRY,
a083aed0 754 unicode_supported() ? u" ►" : u"=>");
fb90100c 755 if (idx_highlight == config->idx_default_efivar)
3639d1b0
JJ
756 print_at(x_start,
757 y_start + idx_highlight - idx_first,
758 COLOR_HIGHLIGHT,
a083aed0 759 unicode_supported() ? u" ►" : u"=>");
e5a1b8f9 760 highlight = false;
0fa2cac4
KS
761 }
762
763 if (timeout_remain > 0) {
0af26643 764 free(status);
2f3c3b0b 765 status = xasprintf("Boot in %u s.", timeout_remain);
0fa2cac4
KS
766 }
767
0fa2cac4 768 if (status) {
592d5765
JJ
769 /* If we draw the last char of the last line, the screen will scroll and break our
770 * input. Therefore, draw one less character then we could for the status message.
771 * Note that the same does not apply for the separator line as it will never be drawn
772 * on the last line. */
dede50a7
JJ
773 size_t len = strnlen16(status, x_max - 1);
774 size_t x = (x_max - len) / 2;
592d5765
JJ
775 status[len] = '\0';
776 print_at(0, y_status, COLOR_NORMAL, clearline + x_max - x);
12f32748
JJ
777 ST->ConOut->OutputString(ST->ConOut, status);
778 ST->ConOut->OutputString(ST->ConOut, clearline + 1 + x + len);
592d5765
JJ
779
780 len = MIN(MAX(len, line_width) + 2 * entry_padding, x_max);
781 x = (x_max - len) / 2;
782 print_at(x, y_status - 1, COLOR_NORMAL, separator + x_max - len);
783 } else {
784 print_at(0, y_status - 1, COLOR_NORMAL, clearline);
785 print_at(0, y_status, COLOR_NORMAL, clearline + 1); /* See comment above. */
0fa2cac4
KS
786 }
787
cc25bedb
JJ
788 /* Beep several times so that the selected entry can be distinguished. */
789 if (config->beep)
790 beep(idx_highlight + 1);
85d2f13b 791
6ae9a5a0 792 err = console_key_read(&key, timeout_remain > 0 ? 1000 * 1000 : UINT64_MAX);
1cb5d785
JJ
793 if (err == EFI_NOT_READY)
794 /* No input device returned a key, try again. This
795 * normally should not happen. */
796 continue;
e98d271e 797 if (err == EFI_TIMEOUT) {
1cb5d785 798 assert(timeout_remain > 0);
e98d271e 799 timeout_remain--;
0fa2cac4 800 if (timeout_remain == 0) {
e5a1b8f9 801 exit = true;
0fa2cac4
KS
802 break;
803 }
804
e98d271e 805 /* update status */
0fa2cac4 806 continue;
1cb5d785 807 }
2a5e4fe4 808 if (err != EFI_SUCCESS) {
e5a1b8f9 809 exit = true;
1cb5d785
JJ
810 break;
811 }
812
813 timeout_remain = 0;
0fa2cac4
KS
814
815 /* clear status after keystroke */
592d5765 816 status = mfree(status);
0fa2cac4
KS
817
818 idx_highlight_prev = idx_highlight;
819
e6cab77e 820 if (firmware_setup) {
e5a1b8f9 821 firmware_setup = false;
b3e25576 822 if (IN_SET(key, KEYPRESS(0, 0, '\r'), KEYPRESS(0, 0, '\n')))
e6cab77e
JJ
823 reboot_into_firmware();
824 continue;
825 }
826
0fa2cac4
KS
827 switch (key) {
828 case KEYPRESS(0, SCAN_UP, 0):
829 case KEYPRESS(0, 0, 'k'):
8a8e5666 830 case KEYPRESS(0, 0, 'K'):
0fa2cac4
KS
831 if (idx_highlight > 0)
832 idx_highlight--;
833 break;
834
835 case KEYPRESS(0, SCAN_DOWN, 0):
836 case KEYPRESS(0, 0, 'j'):
8a8e5666 837 case KEYPRESS(0, 0, 'J'):
3a59c55f 838 if (idx_highlight < config->n_entries-1)
0fa2cac4
KS
839 idx_highlight++;
840 break;
841
842 case KEYPRESS(0, SCAN_HOME, 0):
843 case KEYPRESS(EFI_ALT_PRESSED, 0, '<'):
844 if (idx_highlight > 0) {
e5a1b8f9 845 refresh = true;
0fa2cac4
KS
846 idx_highlight = 0;
847 }
848 break;
849
850 case KEYPRESS(0, SCAN_END, 0):
851 case KEYPRESS(EFI_ALT_PRESSED, 0, '>'):
3a59c55f 852 if (idx_highlight < config->n_entries-1) {
e5a1b8f9 853 refresh = true;
3a59c55f 854 idx_highlight = config->n_entries-1;
0fa2cac4
KS
855 }
856 break;
857
858 case KEYPRESS(0, SCAN_PAGE_UP, 0):
859 if (idx_highlight > visible_max)
860 idx_highlight -= visible_max;
861 else
862 idx_highlight = 0;
863 break;
864
865 case KEYPRESS(0, SCAN_PAGE_DOWN, 0):
866 idx_highlight += visible_max;
3a59c55f
LP
867 if (idx_highlight > config->n_entries-1)
868 idx_highlight = config->n_entries-1;
0fa2cac4
KS
869 break;
870
b3e25576
JJ
871 case KEYPRESS(0, 0, '\n'):
872 case KEYPRESS(0, 0, '\r'):
28fda21c 873 case KEYPRESS(0, SCAN_F3, 0): /* EZpad Mini 4s firmware sends malformed events */
b3e25576 874 case KEYPRESS(0, SCAN_F3, '\r'): /* Teclast X98+ II firmware sends malformed events */
904ebcb2 875 case KEYPRESS(0, SCAN_RIGHT, 0):
e5a1b8f9 876 exit = true;
0fa2cac4
KS
877 break;
878
879 case KEYPRESS(0, SCAN_F1, 0):
880 case KEYPRESS(0, 0, 'h'):
8a8e5666 881 case KEYPRESS(0, 0, 'H'):
0fa2cac4 882 case KEYPRESS(0, 0, '?'):
e6cab77e 883 /* This must stay below 80 characters! Q/v/Ctrl+l/f deliberately not advertised. */
f6531b11 884 status = xstrdup16(u"(d)efault (t/T)imeout (e)dit (r/R)esolution (p)rint (O)ff re(B)oot (h)elp");
0fa2cac4
KS
885 break;
886
887 case KEYPRESS(0, 0, 'Q'):
e5a1b8f9
JJ
888 exit = true;
889 run = false;
0fa2cac4
KS
890 break;
891
892 case KEYPRESS(0, 0, 'd'):
8a8e5666 893 case KEYPRESS(0, 0, 'D'):
fb90100c 894 if (config->idx_default_efivar != idx_highlight) {
0af26643 895 free(config->entry_default_efivar);
7783ab5d 896 config->entry_default_efivar = xstrdup16(config->entries[idx_highlight]->id);
0fa2cac4 897 config->idx_default_efivar = idx_highlight;
7783ab5d 898 status = xstrdup16(u"Default boot entry selected.");
0fa2cac4 899 } else {
bbe51db6 900 config->entry_default_efivar = mfree(config->entry_default_efivar);
fb90100c 901 config->idx_default_efivar = IDX_INVALID;
7783ab5d 902 status = xstrdup16(u"Default boot entry cleared.");
0fa2cac4 903 }
e5a1b8f9
JJ
904 config->use_saved_entry_efivar = false;
905 refresh = true;
0fa2cac4
KS
906 break;
907
908 case KEYPRESS(0, 0, '-'):
909 case KEYPRESS(0, 0, 'T'):
e5a1b8f9 910 status = update_timeout_efivar(&config->timeout_sec_efivar, false);
0fa2cac4
KS
911 break;
912
913 case KEYPRESS(0, 0, '+'):
914 case KEYPRESS(0, 0, 't'):
e5a1b8f9 915 status = update_timeout_efivar(&config->timeout_sec_efivar, true);
0fa2cac4
KS
916 break;
917
918 case KEYPRESS(0, 0, 'e'):
8a8e5666 919 case KEYPRESS(0, 0, 'E'):
0fa2cac4 920 /* only the options of configured entries can be edited */
c4d281fa
LP
921 if (!config->editor ||
922 !IN_SET(config->entries[idx_highlight]->type, LOADER_EFI, LOADER_LINUX, LOADER_UNIFIED_LINUX)) {
923 status = xstrdup16(u"Entry does not support editing the command line.");
0fa2cac4 924 break;
c4d281fa 925 }
7ffc4f47 926
362c6229
LP
927 /* Unified kernels that are signed as a whole will not accept command line options
928 * when secure boot is enabled unless there is none embedded in the image. Do not try
929 * to pretend we can edit it to only have it be ignored. */
930 if (config->entries[idx_highlight]->type == LOADER_UNIFIED_LINUX &&
7ffc4f47 931 secure_boot_enabled() &&
c4d281fa
LP
932 config->entries[idx_highlight]->options) {
933 status = xstrdup16(u"Entry not editable in SecureBoot mode.");
0fa2cac4 934 break;
c4d281fa 935 }
7ffc4f47 936
e313e934
JJ
937 /* The edit line may end up on the last line of the screen. And even though we're
938 * not telling the firmware to advance the line, it still does in this one case,
939 * causing a scroll to happen that screws with our beautiful boot loader output.
940 * Since we cannot paint the last character of the edit line, we simply start
941 * at x-offset 1 for symmetry. */
1b965abc 942 print_at(1, y_status, COLOR_EDIT, clearline + 2);
5c330b36 943 exit = line_edit(&config->entries[idx_highlight]->options, x_max - 2, y_status);
1b965abc 944 print_at(1, y_status, COLOR_NORMAL, clearline + 2);
0fa2cac4
KS
945 break;
946
947 case KEYPRESS(0, 0, 'v'):
2f3c3b0b
JJ
948 status = xasprintf(
949 "systemd-boot " GIT_VERSION " (" EFI_MACHINE_TYPE_NAME "), "
950 "UEFI Specification %u.%02u, Vendor %ls %u.%02u",
ec4106af
JJ
951 ST->Hdr.Revision >> 16,
952 ST->Hdr.Revision & 0xffff,
953 ST->FirmwareVendor,
954 ST->FirmwareRevision >> 16,
955 ST->FirmwareRevision & 0xffff);
0fa2cac4
KS
956 break;
957
8a8e5666 958 case KEYPRESS(0, 0, 'p'):
0fa2cac4 959 case KEYPRESS(0, 0, 'P'):
7361099e 960 print_status(config, loaded_image_path);
e5a1b8f9 961 clear = true;
0fa2cac4
KS
962 break;
963
964 case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'l'):
965 case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('l')):
e5a1b8f9 966 clear = true;
1b965abc
JJ
967 break;
968
969 case KEYPRESS(0, 0, 'r'):
970 err = console_set_mode(CONSOLE_MODE_NEXT);
2a5e4fe4 971 if (err != EFI_SUCCESS)
2f3c3b0b 972 status = xasprintf_status(err, "Error changing console mode: %m");
1b965abc
JJ
973 else {
974 config->console_mode_efivar = ST->ConOut->Mode->Mode;
2f3c3b0b
JJ
975 status = xasprintf(
976 "Console mode changed to %" PRIi64 ".",
977 config->console_mode_efivar);
1b965abc 978 }
e5a1b8f9 979 new_mode = true;
1b965abc
JJ
980 break;
981
982 case KEYPRESS(0, 0, 'R'):
983 config->console_mode_efivar = CONSOLE_MODE_KEEP;
984 err = console_set_mode(config->console_mode == CONSOLE_MODE_KEEP ?
985 console_mode_initial : config->console_mode);
2a5e4fe4 986 if (err != EFI_SUCCESS)
2f3c3b0b 987 status = xasprintf_status(err, "Error resetting console mode: %m");
1b965abc 988 else
2f3c3b0b
JJ
989 status = xasprintf(
990 "Console mode reset to %s default.",
991 config->console_mode == CONSOLE_MODE_KEEP ?
992 "firmware" :
993 "configuration file");
e5a1b8f9 994 new_mode = true;
0fa2cac4
KS
995 break;
996
e6cab77e
JJ
997 case KEYPRESS(0, 0, 'f'):
998 case KEYPRESS(0, 0, 'F'):
999 case KEYPRESS(0, SCAN_F2, 0): /* Most vendors. */
1000 case KEYPRESS(0, SCAN_F10, 0): /* HP and Lenovo. */
1001 case KEYPRESS(0, SCAN_DELETE, 0): /* Same as F2. */
1002 case KEYPRESS(0, SCAN_ESC, 0): /* HP. */
785b5fcf 1003 if (FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) {
e5a1b8f9 1004 firmware_setup = true;
e6cab77e 1005 /* Let's make sure the user really wants to do this. */
2f3c3b0b 1006 status = xstrdup16(u"Press Enter to reboot into firmware interface.");
e6cab77e 1007 } else
2f3c3b0b 1008 status = xstrdup16(u"Reboot into firmware interface not supported.");
e6cab77e
JJ
1009 break;
1010
f6531b11
LP
1011 case KEYPRESS(0, 0, 'O'): /* Only uppercase, so that it can't be hit so easily fat-fingered, but still works safely over serial */
1012 RT->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
1013 break;
1014
1015 case KEYPRESS(0, 0, 'B'): /* ditto */
1016 RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
1017 break;
1018
0fa2cac4
KS
1019 default:
1020 /* jump with a hotkey directly to a matching entry */
1021 idx = entry_lookup_key(config, idx_highlight+1, KEYCHAR(key));
fb90100c 1022 if (idx == IDX_INVALID)
0fa2cac4
KS
1023 break;
1024 idx_highlight = idx;
e5a1b8f9 1025 refresh = true;
0fa2cac4
KS
1026 }
1027
1028 if (idx_highlight > idx_last) {
1029 idx_last = idx_highlight;
1030 idx_first = 1 + idx_highlight - visible_max;
e5a1b8f9 1031 refresh = true;
56c70b32 1032 } else if (idx_highlight < idx_first) {
0fa2cac4
KS
1033 idx_first = idx_highlight;
1034 idx_last = idx_highlight + visible_max-1;
e5a1b8f9 1035 refresh = true;
0fa2cac4
KS
1036 }
1037
0fa2cac4 1038 if (!refresh && idx_highlight != idx_highlight_prev)
e5a1b8f9 1039 highlight = true;
0fa2cac4
KS
1040 }
1041
1042 *chosen_entry = config->entries[idx_highlight];
1043
52b6b356
JJ
1044 /* Update EFI vars after we left the menu to reduce NVRAM writes. */
1045
bbe51db6 1046 if (default_efivar_saved != config->idx_default_efivar)
19f08504 1047 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderEntryDefault", config->entry_default_efivar, EFI_VARIABLE_NON_VOLATILE);
bbe51db6 1048
1b965abc
JJ
1049 if (console_mode_efivar_saved != config->console_mode_efivar) {
1050 if (config->console_mode_efivar == CONSOLE_MODE_KEEP)
19f08504 1051 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderConfigConsoleMode", NULL, EFI_VARIABLE_NON_VOLATILE);
1b965abc 1052 else
19f08504 1053 efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"LoaderConfigConsoleMode",
1b965abc
JJ
1054 config->console_mode_efivar, EFI_VARIABLE_NON_VOLATILE);
1055 }
1056
52b6b356 1057 if (timeout_efivar_saved != config->timeout_sec_efivar) {
97f077df
JJ
1058 switch (config->timeout_sec_efivar) {
1059 case TIMEOUT_UNSET:
19f08504 1060 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout", NULL, EFI_VARIABLE_NON_VOLATILE);
97f077df
JJ
1061 break;
1062 case TIMEOUT_MENU_FORCE:
19f08504 1063 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout", u"menu-force", EFI_VARIABLE_NON_VOLATILE);
97f077df
JJ
1064 break;
1065 case TIMEOUT_MENU_HIDDEN:
19f08504 1066 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout", u"menu-hidden", EFI_VARIABLE_NON_VOLATILE);
97f077df
JJ
1067 break;
1068 default:
19f08504 1069 efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout",
52b6b356 1070 config->timeout_sec_efivar, EFI_VARIABLE_NON_VOLATILE);
97f077df 1071 }
52b6b356
JJ
1072 }
1073
1b965abc 1074 clear_screen(COLOR_NORMAL);
0fa2cac4
KS
1075 return run;
1076}
1077
70cd15e9 1078static void config_add_entry(Config *config, ConfigEntry *entry) {
508df915
JJ
1079 assert(config);
1080 assert(entry);
1081
fb90100c 1082 /* This is just for paranoia. */
3a59c55f 1083 assert(config->n_entries < IDX_MAX);
fb90100c 1084
3a59c55f 1085 if ((config->n_entries & 15) == 0) {
0af26643 1086 config->entries = xrealloc(
04394aa1 1087 config->entries,
3a59c55f
LP
1088 sizeof(void *) * config->n_entries,
1089 sizeof(void *) * (config->n_entries + 16));
0fa2cac4 1090 }
3a59c55f 1091 config->entries[config->n_entries++] = entry;
0fa2cac4
KS
1092}
1093
70cd15e9 1094static void config_entry_free(ConfigEntry *entry) {
42cf81c2
LP
1095 if (!entry)
1096 return;
1097
0af26643
JJ
1098 free(entry->id);
1099 free(entry->title_show);
1100 free(entry->title);
1101 free(entry->sort_key);
1102 free(entry->version);
1103 free(entry->machine_id);
1104 free(entry->loader);
1105 free(entry->devicetree);
1106 free(entry->options);
acdf7d15 1107 strv_free(entry->initrd);
0af26643
JJ
1108 free(entry->path);
1109 free(entry->current_name);
1110 free(entry->next_name);
1111 free(entry);
0fa2cac4
KS
1112}
1113
cf1ab844 1114static void config_entry_freep(ConfigEntry **entry) {
4ec9d873
JJ
1115 config_entry_free(*entry);
1116}
1117
07d0fde4
JJ
1118static char *line_get_key_value(
1119 char *content,
1120 const char *sep,
dede50a7 1121 size_t *pos,
07d0fde4
JJ
1122 char **key_ret,
1123 char **value_ret) {
580fe4df 1124
07d0fde4 1125 char *line, *value;
dede50a7 1126 size_t linelen;
0fa2cac4 1127
508df915
JJ
1128 assert(content);
1129 assert(sep);
1130 assert(pos);
1131 assert(key_ret);
1132 assert(value_ret);
1133
159636cf
JJ
1134 for (;;) {
1135 line = content + *pos;
1136 if (*line == '\0')
1137 return NULL;
0fa2cac4 1138
159636cf 1139 linelen = 0;
fb67588c 1140 while (line[linelen] && !strchr8("\n\r", line[linelen]))
159636cf 1141 linelen++;
0fa2cac4 1142
159636cf
JJ
1143 /* move pos to next line */
1144 *pos += linelen;
1145 if (content[*pos])
1146 (*pos)++;
0fa2cac4 1147
159636cf
JJ
1148 /* empty line */
1149 if (linelen == 0)
1150 continue;
0fa2cac4 1151
159636cf
JJ
1152 /* terminate line */
1153 line[linelen] = '\0';
0fa2cac4 1154
159636cf 1155 /* remove leading whitespace */
fb67588c 1156 while (strchr8(" \t", *line)) {
159636cf
JJ
1157 line++;
1158 linelen--;
1159 }
0fa2cac4 1160
159636cf 1161 /* remove trailing whitespace */
fb67588c 1162 while (linelen > 0 && strchr8(" \t", line[linelen - 1]))
159636cf
JJ
1163 linelen--;
1164 line[linelen] = '\0';
0fa2cac4 1165
159636cf
JJ
1166 if (*line == '#')
1167 continue;
0fa2cac4 1168
159636cf
JJ
1169 /* split key/value */
1170 value = line;
07d0fde4 1171 while (*value && !strchr8(sep, *value))
159636cf
JJ
1172 value++;
1173 if (*value == '\0')
1174 continue;
1175 *value = '\0';
0fa2cac4 1176 value++;
07d0fde4 1177 while (*value && strchr8(sep, *value))
159636cf 1178 value++;
0fa2cac4 1179
159636cf
JJ
1180 /* unquote */
1181 if (value[0] == '"' && line[linelen - 1] == '"') {
1182 value++;
1183 line[linelen - 1] = '\0';
1184 }
0fa2cac4 1185
159636cf
JJ
1186 *key_ret = line;
1187 *value_ret = value;
1188 return line;
1189 }
0fa2cac4
KS
1190}
1191
07d0fde4
JJ
1192static void config_defaults_load_from_file(Config *config, char *content) {
1193 char *line;
dede50a7 1194 size_t pos = 0;
07d0fde4 1195 char *key, *value;
ec97e40c 1196 EFI_STATUS err;
0fa2cac4 1197
508df915
JJ
1198 assert(config);
1199 assert(content);
1200
07d0fde4
JJ
1201 while ((line = line_get_key_value(content, " \t", &pos, &key, &value))) {
1202 if (streq8(key, "timeout")) {
1203 if (streq8( value, "menu-force"))
52b6b356 1204 config->timeout_sec_config = TIMEOUT_MENU_FORCE;
07d0fde4 1205 else if (streq8(value, "menu-hidden"))
52b6b356
JJ
1206 config->timeout_sec_config = TIMEOUT_MENU_HIDDEN;
1207 else {
1621ab46 1208 uint64_t u;
07d0fde4 1209 if (!parse_number8(value, &u, NULL) || u > TIMEOUT_TYPE_MAX) {
c2c62035 1210 log_error("Error parsing 'timeout' config option: %s", value);
1621ab46
JJ
1211 continue;
1212 }
1213 config->timeout_sec_config = u;
52b6b356 1214 }
0fa2cac4 1215 config->timeout_sec = config->timeout_sec_config;
0fa2cac4
KS
1216 continue;
1217 }
1218
07d0fde4
JJ
1219 if (streq8(key, "default")) {
1220 if (value[0] == '@' && !strcaseeq8(value, "@saved")) {
c2c62035 1221 log_error("Unsupported special entry identifier: %s", value);
ee4fd9cb
JJ
1222 continue;
1223 }
0af26643 1224 free(config->entry_default_config);
aee515bb 1225 config->entry_default_config = xstr8_to_16(value);
0fa2cac4
KS
1226 continue;
1227 }
4db7e6d7 1228
07d0fde4 1229 if (streq8(key, "editor")) {
ec97e40c 1230 err = parse_boolean(value, &config->editor);
2a5e4fe4 1231 if (err != EFI_SUCCESS)
c2c62035 1232 log_error("Error parsing 'editor' config option: %s", value);
7c122dfe 1233 continue;
c1d4e298
JJ
1234 }
1235
07d0fde4 1236 if (streq8(key, "auto-entries")) {
ec97e40c 1237 err = parse_boolean(value, &config->auto_entries);
2a5e4fe4 1238 if (err != EFI_SUCCESS)
c2c62035 1239 log_error("Error parsing 'auto-entries' config option: %s", value);
7c122dfe 1240 continue;
c1d4e298
JJ
1241 }
1242
07d0fde4 1243 if (streq8(key, "auto-firmware")) {
ec97e40c 1244 err = parse_boolean(value, &config->auto_firmware);
2a5e4fe4 1245 if (err != EFI_SUCCESS)
c2c62035 1246 log_error("Error parsing 'auto-firmware' config option: %s", value);
7c122dfe 1247 continue;
4db7e6d7 1248 }
68d4b8ac 1249
07d0fde4 1250 if (streq8(key, "beep")) {
85d2f13b 1251 err = parse_boolean(value, &config->beep);
2a5e4fe4 1252 if (err != EFI_SUCCESS)
c2c62035 1253 log_error("Error parsing 'beep' config option: %s", value);
4d54310d 1254 continue;
85d2f13b
JJ
1255 }
1256
07d0fde4 1257 if (streq8(key, "reboot-for-bitlocker")) {
661615a0 1258 err = parse_boolean(value, &config->reboot_for_bitlocker);
2a5e4fe4 1259 if (err != EFI_SUCCESS)
c2c62035 1260 log_error("Error parsing 'reboot-for-bitlocker' config option: %s", value);
e6b0cfad
VD
1261 }
1262
1263 if (streq8(key, "secure-boot-enroll")) {
1264 if (streq8(value, "manual"))
1265 config->secure_boot_enroll = ENROLL_MANUAL;
1266 else if (streq8(value, "force"))
1267 config->secure_boot_enroll = ENROLL_FORCE;
1e8e7f44
JJ
1268 else if (streq8(value, "if-safe"))
1269 config->secure_boot_enroll = ENROLL_IF_SAFE;
e6b0cfad
VD
1270 else if (streq8(value, "off"))
1271 config->secure_boot_enroll = ENROLL_OFF;
1272 else
c2c62035 1273 log_error("Error parsing 'secure-boot-enroll' config option: %s", value);
661615a0
JJ
1274 continue;
1275 }
1276
07d0fde4
JJ
1277 if (streq8(key, "console-mode")) {
1278 if (streq8(value, "auto"))
134144ab 1279 config->console_mode = CONSOLE_MODE_AUTO;
07d0fde4 1280 else if (streq8(value, "max"))
134144ab 1281 config->console_mode = CONSOLE_MODE_FIRMWARE_MAX;
07d0fde4 1282 else if (streq8(value, "keep"))
134144ab 1283 config->console_mode = CONSOLE_MODE_KEEP;
68d4b8ac 1284 else {
1621ab46 1285 uint64_t u;
07d0fde4 1286 if (!parse_number8(value, &u, NULL) || u > CONSOLE_MODE_RANGE_MAX) {
c2c62035 1287 log_error("Error parsing 'console-mode' config option: %s", value);
1621ab46
JJ
1288 continue;
1289 }
1290 config->console_mode = u;
68d4b8ac 1291 }
68d4b8ac
HDA
1292 continue;
1293 }
0fa2cac4
KS
1294 }
1295}
1296
70cd15e9 1297static void config_entry_parse_tries(
f538cc65 1298 ConfigEntry *entry,
3639d1b0
JJ
1299 const char16_t *path,
1300 const char16_t *file,
1301 const char16_t *suffix) {
f538cc65 1302
508df915
JJ
1303 assert(entry);
1304 assert(path);
1305 assert(file);
15338195 1306 assert(suffix);
508df915 1307
f538cc65
LP
1308 /*
1309 * Parses a suffix of two counters (one going down, one going up) in the form "+LEFT-DONE" from the end of the
1310 * filename (but before the .efi/.conf suffix), where the "-DONE" part is optional and may be left out (in
1311 * which case that counter as assumed to be zero, i.e. the missing part is synonymous to "-0").
1312 *
1313 * Names we grok, and the series they result in:
1314 *
1315 * foobar+3.efi → foobar+2-1.efi → foobar+1-2.efi → foobar+0-3.efi → STOP!
1316 * foobar+4-0.efi → foobar+3-1.efi → foobar+2-2.efi → foobar+1-3.efi → foobar+0-4.efi → STOP!
1317 */
1318
15338195 1319 const char16_t *counter = NULL;
f538cc65 1320 for (;;) {
15338195
JJ
1321 char16_t *plus = strchr16(counter ?: file, '+');
1322 if (plus) {
1323 /* We want the last "+". */
1324 counter = plus + 1;
1325 continue;
1326 }
1327 if (counter)
f538cc65
LP
1328 break;
1329
15338195
JJ
1330 /* No boot counter found. */
1331 return;
1332 }
f538cc65 1333
15338195
JJ
1334 uint64_t tries_left, tries_done = 0;
1335 size_t prefix_len = counter - file;
f538cc65 1336
15338195
JJ
1337 if (!parse_number16(counter, &tries_left, &counter) || tries_left > INT_MAX)
1338 return;
f538cc65 1339
15338195
JJ
1340 /* Parse done counter only if present. */
1341 if (*counter == '-' && (!parse_number16(counter + 1, &tries_done, &counter) || tries_done > INT_MAX))
1342 return;
f538cc65 1343
15338195
JJ
1344 /* Boot counter in the middle of the name? */
1345 if (!streq16(counter, suffix))
1346 return;
f538cc65 1347
15338195
JJ
1348 entry->tries_left = tries_left;
1349 entry->tries_done = tries_done;
7783ab5d
JJ
1350 entry->path = xstrdup16(path);
1351 entry->current_name = xstrdup16(file);
2f3c3b0b
JJ
1352 entry->next_name = xasprintf(
1353 "%.*ls%" PRIu64 "-%" PRIu64 "%ls",
1354 (int) prefix_len,
15338195
JJ
1355 file,
1356 LESS_BY(tries_left, 1u),
1357 MIN(tries_done + 1, (uint64_t) INT_MAX),
1358 suffix);
f538cc65
LP
1359}
1360
66fd078b 1361static EFI_STATUS config_entry_bump_counters(ConfigEntry *entry) {
3639d1b0 1362 _cleanup_free_ char16_t* old_path = NULL, *new_path = NULL;
85eb489e 1363 _cleanup_(file_closep) EFI_FILE *handle = NULL;
93521e55 1364 _cleanup_free_ EFI_FILE_INFO *file_info = NULL;
dede50a7 1365 size_t file_info_size;
12f32748 1366 EFI_STATUS err;
f538cc65 1367
508df915 1368 assert(entry);
508df915 1369
15338195 1370 if (entry->tries_left < 0)
66fd078b 1371 return EFI_SUCCESS;
f538cc65
LP
1372
1373 if (!entry->path || !entry->current_name || !entry->next_name)
66fd078b
JJ
1374 return EFI_SUCCESS;
1375
1376 _cleanup_(file_closep) EFI_FILE *root = NULL;
1377 err = open_volume(entry->device, &root);
1378 if (err != EFI_SUCCESS)
1379 return log_error_status(err, "Error opening entry root path: %m");
f538cc65 1380
2f3c3b0b 1381 old_path = xasprintf("%ls\\%ls", entry->path, entry->current_name);
f538cc65 1382
66fd078b 1383 err = root->Open(root, &handle, old_path, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0ULL);
2a5e4fe4 1384 if (err != EFI_SUCCESS)
66fd078b 1385 return log_error_status(err, "Error opening boot entry: %m");
f538cc65 1386
5fa3e628 1387 err = get_file_info(handle, &file_info, &file_info_size);
2a5e4fe4 1388 if (err != EFI_SUCCESS)
66fd078b 1389 return log_error_status(err, "Error getting boot entry file info: %m");
f538cc65
LP
1390
1391 /* And rename the file */
08ed842b 1392 strcpy16(file_info->FileName, entry->next_name);
19f08504 1393 err = handle->SetInfo(handle, MAKE_GUID_PTR(EFI_FILE_INFO), file_info_size, file_info);
66fd078b
JJ
1394 if (err != EFI_SUCCESS)
1395 return log_error_status(
1396 err, "Failed to rename '%ls' to '%ls', ignoring: %m", old_path, entry->next_name);
f538cc65
LP
1397
1398 /* Flush everything to disk, just in case… */
66fd078b
JJ
1399 err = handle->Flush(handle);
1400 if (err != EFI_SUCCESS)
1401 return log_error_status(err, "Error flushing boot entry file info: %m");
f538cc65
LP
1402
1403 /* Let's tell the OS that we renamed this file, so that it knows what to rename to the counter-less name on
1404 * success */
2f3c3b0b 1405 new_path = xasprintf("%ls\\%ls", entry->path, entry->next_name);
19f08504 1406 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderBootCountPath", new_path, 0);
f538cc65
LP
1407
1408 /* If the file we just renamed is the loader path, then let's update that. */
ec436bdf 1409 if (streq16(entry->loader, old_path)) {
0af26643 1410 free(entry->loader);
c1db999e 1411 entry->loader = TAKE_PTR(new_path);
f538cc65 1412 }
66fd078b
JJ
1413
1414 return EFI_SUCCESS;
f538cc65
LP
1415}
1416
9287b661 1417static void config_entry_add_type1(
580fe4df
LP
1418 Config *config,
1419 EFI_HANDLE *device,
f9f1d01c 1420 EFI_FILE *root_dir,
3639d1b0
JJ
1421 const char16_t *path,
1422 const char16_t *file,
07d0fde4 1423 char *content,
3639d1b0 1424 const char16_t *loaded_image_path) {
580fe4df 1425
4ec9d873 1426 _cleanup_(config_entry_freep) ConfigEntry *entry = NULL;
07d0fde4 1427 char *line;
dede50a7 1428 size_t pos = 0, n_initrd = 0;
07d0fde4 1429 char *key, *value;
f9f1d01c 1430 EFI_STATUS err;
0fa2cac4 1431
508df915
JJ
1432 assert(config);
1433 assert(device);
1434 assert(root_dir);
1435 assert(path);
1436 assert(file);
1437 assert(content);
508df915 1438
da76f890 1439 entry = xnew(ConfigEntry, 1);
f538cc65 1440 *entry = (ConfigEntry) {
15338195
JJ
1441 .tries_done = -1,
1442 .tries_left = -1,
f538cc65 1443 };
0fa2cac4 1444
07d0fde4
JJ
1445 while ((line = line_get_key_value(content, " \t", &pos, &key, &value))) {
1446 if (streq8(key, "title")) {
0af26643 1447 free(entry->title);
aee515bb 1448 entry->title = xstr8_to_16(value);
0fa2cac4
KS
1449 continue;
1450 }
1451
07d0fde4 1452 if (streq8(key, "sort-key")) {
0af26643 1453 free(entry->sort_key);
aee515bb 1454 entry->sort_key = xstr8_to_16(value);
20ec8f53
LP
1455 continue;
1456 }
1457
07d0fde4 1458 if (streq8(key, "version")) {
0af26643 1459 free(entry->version);
aee515bb 1460 entry->version = xstr8_to_16(value);
0fa2cac4
KS
1461 continue;
1462 }
1463
07d0fde4 1464 if (streq8(key, "machine-id")) {
0af26643 1465 free(entry->machine_id);
aee515bb 1466 entry->machine_id = xstr8_to_16(value);
0fa2cac4
KS
1467 continue;
1468 }
1469
07d0fde4 1470 if (streq8(key, "linux")) {
0af26643 1471 free(entry->loader);
0fa2cac4 1472 entry->type = LOADER_LINUX;
7444e106 1473 entry->loader = xstr8_to_path(value);
0fa2cac4
KS
1474 entry->key = 'l';
1475 continue;
1476 }
1477
07d0fde4 1478 if (streq8(key, "efi")) {
0fa2cac4 1479 entry->type = LOADER_EFI;
0af26643 1480 free(entry->loader);
7444e106 1481 entry->loader = xstr8_to_path(value);
0fa2cac4
KS
1482
1483 /* do not add an entry for ourselves */
af7ef648 1484 if (strcaseeq16(entry->loader, loaded_image_path)) {
0fa2cac4
KS
1485 entry->type = LOADER_UNDEFINED;
1486 break;
1487 }
1488 continue;
1489 }
1490
07d0fde4 1491 if (streq8(key, "architecture")) {
0fa2cac4 1492 /* do not add an entry for an EFI image of architecture not matching with that of the image */
07d0fde4 1493 if (!streq8(value, EFI_MACHINE_TYPE_NAME)) {
0fa2cac4
KS
1494 entry->type = LOADER_UNDEFINED;
1495 break;
1496 }
1497 continue;
1498 }
1499
07d0fde4 1500 if (streq8(key, "devicetree")) {
0af26643 1501 free(entry->devicetree);
7444e106 1502 entry->devicetree = xstr8_to_path(value);
6e86342b
ERB
1503 continue;
1504 }
1505
07d0fde4 1506 if (streq8(key, "initrd")) {
0af26643 1507 entry->initrd = xrealloc(
acdf7d15 1508 entry->initrd,
db4122d1
JJ
1509 n_initrd == 0 ? 0 : (n_initrd + 1) * sizeof(uint16_t *),
1510 (n_initrd + 2) * sizeof(uint16_t *));
7444e106 1511 entry->initrd[n_initrd++] = xstr8_to_path(value);
acdf7d15 1512 entry->initrd[n_initrd] = NULL;
0fa2cac4
KS
1513 continue;
1514 }
1515
07d0fde4 1516 if (streq8(key, "options")) {
3639d1b0 1517 _cleanup_free_ char16_t *new = NULL;
0fa2cac4 1518
aee515bb 1519 new = xstr8_to_16(value);
0fa2cac4 1520 if (entry->options) {
2f3c3b0b 1521 char16_t *s = xasprintf("%ls %ls", entry->options, new);
0af26643 1522 free(entry->options);
0fa2cac4 1523 entry->options = s;
c1db999e
LP
1524 } else
1525 entry->options = TAKE_PTR(new);
a42d7cf1 1526
0fa2cac4
KS
1527 continue;
1528 }
0fa2cac4
KS
1529 }
1530
4ec9d873 1531 if (entry->type == LOADER_UNDEFINED)
0fa2cac4 1532 return;
0fa2cac4 1533
f9f1d01c 1534 /* check existence */
85eb489e 1535 _cleanup_(file_closep) EFI_FILE *handle = NULL;
12f32748 1536 err = root_dir->Open(root_dir, &handle, entry->loader, EFI_FILE_MODE_READ, 0ULL);
2a5e4fe4 1537 if (err != EFI_SUCCESS)
f9f1d01c 1538 return;
f9f1d01c 1539
0fa2cac4 1540 entry->device = device;
7783ab5d 1541 entry->id = xstrdup16(file);
fc178cf1 1542 strtolower16(entry->id);
0fa2cac4
KS
1543
1544 config_add_entry(config, entry);
f538cc65 1545
a083aed0 1546 config_entry_parse_tries(entry, path, file, u".conf");
4ec9d873 1547 TAKE_PTR(entry);
0fa2cac4
KS
1548}
1549
97f077df
JJ
1550static EFI_STATUS efivar_get_timeout(const char16_t *var, uint32_t *ret_value) {
1551 _cleanup_free_ char16_t *value = NULL;
1552 EFI_STATUS err;
1553
1554 assert(var);
1555 assert(ret_value);
1556
19f08504 1557 err = efivar_get(MAKE_GUID_PTR(LOADER), var, &value);
97f077df
JJ
1558 if (err != EFI_SUCCESS)
1559 return err;
1560
1561 if (streq16(value, u"menu-force")) {
1562 *ret_value = TIMEOUT_MENU_FORCE;
1563 return EFI_SUCCESS;
1564 }
1565 if (streq16(value, u"menu-hidden")) {
1566 *ret_value = TIMEOUT_MENU_HIDDEN;
1567 return EFI_SUCCESS;
1568 }
1569
1570 uint64_t timeout;
1571 if (!parse_number16(value, &timeout, NULL))
1572 return EFI_INVALID_PARAMETER;
1573
1574 *ret_value = MIN(timeout, TIMEOUT_TYPE_MAX);
1575 return EFI_SUCCESS;
1576}
1577
70cd15e9 1578static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
07d0fde4 1579 _cleanup_free_ char *content = NULL;
dede50a7 1580 size_t value = 0; /* avoid false maybe-uninitialized warning */
540536ff 1581 EFI_STATUS err;
0fa2cac4 1582
508df915
JJ
1583 assert(root_dir);
1584
aec1443a 1585 *config = (Config) {
e5a1b8f9
JJ
1586 .editor = true,
1587 .auto_entries = true,
1588 .auto_firmware = true,
1589 .reboot_for_bitlocker = false,
1e8e7f44 1590 .secure_boot_enroll = ENROLL_IF_SAFE,
fb90100c 1591 .idx_default_efivar = IDX_INVALID,
134144ab 1592 .console_mode = CONSOLE_MODE_KEEP,
1b965abc 1593 .console_mode_efivar = CONSOLE_MODE_KEEP,
52b6b356
JJ
1594 .timeout_sec_config = TIMEOUT_UNSET,
1595 .timeout_sec_efivar = TIMEOUT_UNSET,
aec1443a 1596 };
c1d4e298 1597
a083aed0 1598 err = file_read(root_dir, u"\\loader\\loader.conf", 0, 0, &content, NULL);
2a5e4fe4 1599 if (err == EFI_SUCCESS)
0fa2cac4 1600 config_defaults_load_from_file(config, content);
0fa2cac4 1601
97f077df
JJ
1602 err = efivar_get_timeout(u"LoaderConfigTimeout", &config->timeout_sec_efivar);
1603 if (err == EFI_SUCCESS)
52b6b356 1604 config->timeout_sec = config->timeout_sec_efivar;
97f077df 1605 else if (err != EFI_NOT_FOUND)
c2c62035 1606 log_error_status(err, "Error reading LoaderConfigTimeout EFI variable: %m");
fe2579dd 1607
97f077df 1608 err = efivar_get_timeout(u"LoaderConfigTimeoutOneShot", &config->timeout_sec);
2a5e4fe4 1609 if (err == EFI_SUCCESS) {
fe2579dd 1610 /* Unset variable now, after all it's "one shot". */
19f08504 1611 (void) efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeoutOneShot", NULL, EFI_VARIABLE_NON_VOLATILE);
fe2579dd 1612
e5a1b8f9 1613 config->force_menu = true; /* force the menu when this is set */
97f077df 1614 } else if (err != EFI_NOT_FOUND)
c2c62035 1615 log_error_status(err, "Error reading LoaderConfigTimeoutOneShot EFI variable: %m");
1b965abc 1616
19f08504 1617 err = efivar_get_uint_string(MAKE_GUID_PTR(LOADER), u"LoaderConfigConsoleMode", &value);
2a5e4fe4 1618 if (err == EFI_SUCCESS)
1b965abc 1619 config->console_mode_efivar = value;
bbe51db6 1620
19f08504 1621 err = efivar_get(MAKE_GUID_PTR(LOADER), u"LoaderEntryOneShot", &config->entry_oneshot);
2a5e4fe4 1622 if (err == EFI_SUCCESS)
bbe51db6 1623 /* Unset variable now, after all it's "one shot". */
19f08504 1624 (void) efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderEntryOneShot", NULL, EFI_VARIABLE_NON_VOLATILE);
bbe51db6 1625
19f08504 1626 (void) efivar_get(MAKE_GUID_PTR(LOADER), u"LoaderEntryDefault", &config->entry_default_efivar);
ee4fd9cb 1627
23742af5
JJ
1628 strtolower16(config->entry_default_config);
1629 strtolower16(config->entry_default_efivar);
1630 strtolower16(config->entry_oneshot);
1631 strtolower16(config->entry_saved);
1632
a083aed0
JJ
1633 config->use_saved_entry = streq16(config->entry_default_config, u"@saved");
1634 config->use_saved_entry_efivar = streq16(config->entry_default_efivar, u"@saved");
ee4fd9cb 1635 if (config->use_saved_entry || config->use_saved_entry_efivar)
19f08504 1636 (void) efivar_get(MAKE_GUID_PTR(LOADER), u"LoaderEntryLastBooted", &config->entry_saved);
540536ff
KS
1637}
1638
70cd15e9 1639static void config_load_entries(
580fe4df
LP
1640 Config *config,
1641 EFI_HANDLE *device,
1642 EFI_FILE *root_dir,
3639d1b0 1643 const char16_t *loaded_image_path) {
580fe4df 1644
85eb489e 1645 _cleanup_(file_closep) EFI_FILE *entries_dir = NULL;
93521e55 1646 _cleanup_free_ EFI_FILE_INFO *f = NULL;
dede50a7 1647 size_t f_size = 0;
540536ff 1648 EFI_STATUS err;
0fa2cac4 1649
508df915
JJ
1650 assert(config);
1651 assert(device);
1652 assert(root_dir);
508df915 1653
cf5d9598
LP
1654 /* Adds Boot Loader Type #1 entries (i.e. /loader/entries/….conf) */
1655
a083aed0 1656 err = open_directory(root_dir, u"\\loader\\entries", &entries_dir);
2a5e4fe4 1657 if (err != EFI_SUCCESS)
70d24fed 1658 return;
0fa2cac4 1659
70d24fed 1660 for (;;) {
07d0fde4 1661 _cleanup_free_ char *content = NULL;
70d24fed 1662
5fa3e628 1663 err = readdir(entries_dir, &f, &f_size);
2a5e4fe4 1664 if (err != EFI_SUCCESS || !f)
70d24fed 1665 break;
9d3dec15 1666
70d24fed
LP
1667 if (f->FileName[0] == '.')
1668 continue;
785b5fcf 1669 if (FLAGS_SET(f->Attribute, EFI_FILE_DIRECTORY))
70d24fed 1670 continue;
0fa2cac4 1671
a083aed0 1672 if (!endswith_no_case(f->FileName, u".conf"))
70d24fed 1673 continue;
a083aed0 1674 if (startswith(f->FileName, u"auto-"))
70d24fed
LP
1675 continue;
1676
1677 err = file_read(entries_dir, f->FileName, 0, 0, &content, NULL);
2a5e4fe4 1678 if (err == EFI_SUCCESS)
a083aed0 1679 config_entry_add_type1(config, device, root_dir, u"\\loader\\entries", f->FileName, content, loaded_image_path);
0fa2cac4 1680 }
540536ff
KS
1681}
1682
3d5127c6
JJ
1683static int config_entry_compare(const ConfigEntry *a, const ConfigEntry *b) {
1684 int r;
f538cc65 1685
508df915
JJ
1686 assert(a);
1687 assert(b);
1688
20ec8f53 1689 /* Order entries that have no tries left to the end of the list */
523487f7
ZJS
1690 r = CMP(a->tries_left == 0, b->tries_left == 0);
1691 if (r != 0)
1692 return r;
9818ec8e 1693
20ec8f53
LP
1694 /* If there's a sort key defined for *both* entries, then we do new-style ordering, i.e. by
1695 * sort-key/machine-id/version, with a final fallback to id. If there's no sort key for either, we do
1696 * old-style ordering, i.e. by id only. If one has sort key and the other does not, we put new-style
1697 * before old-style. */
1698 r = CMP(!a->sort_key, !b->sort_key);
1699 if (r != 0) /* one is old-style, one new-style */
1700 return r;
20ec8f53 1701
523487f7 1702 if (a->sort_key && b->sort_key) {
ec436bdf 1703 r = strcmp16(a->sort_key, b->sort_key);
20ec8f53
LP
1704 if (r != 0)
1705 return r;
1706
1707 /* If multiple installations of the same OS are around, group by machine ID */
ec436bdf 1708 r = strcmp16(a->machine_id, b->machine_id);
20ec8f53
LP
1709 if (r != 0)
1710 return r;
1711
1712 /* If the sort key was defined, then order by version now (downwards, putting the newest first) */
1713 r = -strverscmp_improved(a->version, b->version);
1714 if (r != 0)
1715 return r;
1716 }
1717
62a4b584
ZJS
1718 /* Now order by ID. The version is likely part of the ID, thus note that this will generatelly put
1719 * the newer versions earlier. Specifying a sort key explicitly is preferable, because it gives an
1720 * explicit sort order. */
1721 r = -strverscmp_improved(a->id, b->id);
f538cc65
LP
1722 if (r != 0)
1723 return r;
1724
15338195 1725 if (a->tries_left < 0 || b->tries_left < 0)
f538cc65
LP
1726 return 0;
1727
20ec8f53 1728 /* If both items have boot counting, and otherwise are identical, put the entry with more tries left first */
523487f7
ZJS
1729 r = -CMP(a->tries_left, b->tries_left);
1730 if (r != 0)
1731 return r;
f538cc65
LP
1732
1733 /* If they have the same number of tries left, then let the one win which was tried fewer times so far */
523487f7 1734 return CMP(a->tries_done, b->tries_done);
f538cc65
LP
1735}
1736
dede50a7 1737static size_t config_entry_find(Config *config, const char16_t *pattern) {
508df915 1738 assert(config);
bbe51db6 1739
23742af5
JJ
1740 /* We expect pattern and entry IDs to be already case folded. */
1741
1742 if (!pattern)
fb90100c 1743 return IDX_INVALID;
508df915 1744
3a59c55f 1745 for (size_t i = 0; i < config->n_entries; i++)
23742af5 1746 if (efi_fnmatch(pattern, config->entries[i]->id))
e37d30f3 1747 return i;
535610b5 1748
fb90100c 1749 return IDX_INVALID;
535610b5
LP
1750}
1751
70cd15e9 1752static void config_default_entry_select(Config *config) {
dede50a7 1753 size_t i;
0fa2cac4 1754
508df915
JJ
1755 assert(config);
1756
bbe51db6 1757 i = config_entry_find(config, config->entry_oneshot);
fb90100c 1758 if (i != IDX_INVALID) {
bbe51db6
JJ
1759 config->idx_default = i;
1760 return;
0fa2cac4
KS
1761 }
1762
ee4fd9cb 1763 i = config_entry_find(config, config->use_saved_entry_efivar ? config->entry_saved : config->entry_default_efivar);
fb90100c 1764 if (i != IDX_INVALID) {
bbe51db6
JJ
1765 config->idx_default = i;
1766 config->idx_default_efivar = i;
1767 return;
0fa2cac4 1768 }
0fa2cac4 1769
ee4fd9cb
JJ
1770 if (config->use_saved_entry)
1771 /* No need to do the same thing twice. */
fb90100c 1772 i = config->use_saved_entry_efivar ? IDX_INVALID : config_entry_find(config, config->entry_saved);
ee4fd9cb
JJ
1773 else
1774 i = config_entry_find(config, config->entry_default_config);
fb90100c 1775 if (i != IDX_INVALID) {
0c674ce5 1776 config->idx_default = i;
0fa2cac4 1777 return;
0fa2cac4
KS
1778 }
1779
20ec8f53 1780 /* select the first suitable entry */
3a59c55f 1781 for (i = 0; i < config->n_entries; i++) {
b00e37da 1782 if (config->entries[i]->type == LOADER_AUTO || config->entries[i]->call)
0fa2cac4
KS
1783 continue;
1784 config->idx_default = i;
1785 return;
1786 }
1787
163d1ea5
JJ
1788 /* If no configured entry to select from was found, enable the menu. */
1789 config->idx_default = 0;
1790 if (config->timeout_sec == 0)
1791 config->timeout_sec = 10;
0fa2cac4
KS
1792}
1793
3a59c55f 1794static bool entries_unique(ConfigEntry **entries, bool *unique, size_t n_entries) {
e5a1b8f9 1795 bool is_unique = true;
a2fa605a 1796
508df915 1797 assert(entries);
83d905b7 1798 assert(unique);
508df915 1799
3a59c55f
LP
1800 for (size_t i = 0; i < n_entries; i++)
1801 for (size_t k = i + 1; k < n_entries; k++) {
ec436bdf 1802 if (!streq16(entries[i]->title_show, entries[k]->title_show))
a2fa605a
ZJS
1803 continue;
1804
e5a1b8f9 1805 is_unique = unique[i] = unique[k] = false;
a2fa605a
ZJS
1806 }
1807
83d905b7 1808 return is_unique;
a2fa605a
ZJS
1809}
1810
0fa2cac4 1811/* generate a unique title, avoiding non-distinguishable menu entries */
70cd15e9 1812static void config_title_generate(Config *config) {
508df915
JJ
1813 assert(config);
1814
3a59c55f 1815 bool unique[config->n_entries];
83d905b7 1816
0fa2cac4 1817 /* set title */
3a59c55f 1818 for (size_t i = 0; i < config->n_entries; i++) {
83d905b7 1819 assert(!config->entries[i]->title_show);
e5a1b8f9 1820 unique[i] = true;
7783ab5d 1821 config->entries[i]->title_show = xstrdup16(config->entries[i]->title ?: config->entries[i]->id);
0fa2cac4
KS
1822 }
1823
3a59c55f 1824 if (entries_unique(config->entries, unique, config->n_entries))
0fa2cac4
KS
1825 return;
1826
1827 /* add version to non-unique titles */
3a59c55f 1828 for (size_t i = 0; i < config->n_entries; i++) {
83d905b7 1829 if (unique[i])
0fa2cac4 1830 continue;
83d905b7 1831
e5a1b8f9 1832 unique[i] = true;
83d905b7 1833
0fa2cac4
KS
1834 if (!config->entries[i]->version)
1835 continue;
1836
3639d1b0 1837 _cleanup_free_ char16_t *t = config->entries[i]->title_show;
2f3c3b0b 1838 config->entries[i]->title_show = xasprintf("%ls (%ls)", t, config->entries[i]->version);
0fa2cac4
KS
1839 }
1840
3a59c55f 1841 if (entries_unique(config->entries, unique, config->n_entries))
0fa2cac4
KS
1842 return;
1843
1844 /* add machine-id to non-unique titles */
3a59c55f 1845 for (size_t i = 0; i < config->n_entries; i++) {
83d905b7 1846 if (unique[i])
0fa2cac4 1847 continue;
83d905b7 1848
e5a1b8f9 1849 unique[i] = true;
83d905b7 1850
0fa2cac4
KS
1851 if (!config->entries[i]->machine_id)
1852 continue;
1853
3639d1b0 1854 _cleanup_free_ char16_t *t = config->entries[i]->title_show;
2f3c3b0b 1855 config->entries[i]->title_show = xasprintf("%ls (%.8ls)", t, config->entries[i]->machine_id);
0fa2cac4
KS
1856 }
1857
3a59c55f 1858 if (entries_unique(config->entries, unique, config->n_entries))
0fa2cac4
KS
1859 return;
1860
1861 /* add file name to non-unique titles */
3a59c55f 1862 for (size_t i = 0; i < config->n_entries; i++) {
83d905b7 1863 if (unique[i])
0fa2cac4 1864 continue;
83d905b7 1865
3639d1b0 1866 _cleanup_free_ char16_t *t = config->entries[i]->title_show;
2f3c3b0b 1867 config->entries[i]->title_show = xasprintf("%ls (%ls)", t, config->entries[i]->id);
0fa2cac4
KS
1868 }
1869}
1870
e5a1b8f9 1871static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) {
12450f2e 1872 EFI_STATUS err;
6028dd6d 1873 static const char * const sections[] = {
07d0fde4 1874 ".sdmagic",
12450f2e
JJ
1875 NULL
1876 };
dede50a7 1877 size_t offset = 0, size = 0, read;
07d0fde4 1878 _cleanup_free_ char *content = NULL;
12450f2e
JJ
1879
1880 assert(root_dir);
1881 assert(loader_path);
1882
1883 err = pe_file_locate_sections(root_dir, loader_path, sections, &offset, &size);
2a5e4fe4 1884 if (err != EFI_SUCCESS || size != sizeof(magic))
e5a1b8f9 1885 return false;
12450f2e
JJ
1886
1887 err = file_read(root_dir, loader_path, offset, size, &content, &read);
2a5e4fe4 1888 if (err != EFI_SUCCESS || size != read)
e5a1b8f9 1889 return false;
12450f2e 1890
bbc1f2ea 1891 return memcmp(content, magic, sizeof(magic)) == 0;
12450f2e
JJ
1892}
1893
661615a0 1894static ConfigEntry *config_entry_add_loader_auto(
580fe4df
LP
1895 Config *config,
1896 EFI_HANDLE *device,
1897 EFI_FILE *root_dir,
3639d1b0
JJ
1898 const char16_t *loaded_image_path,
1899 const char16_t *id,
1900 char16_t key,
1901 const char16_t *title,
1902 const char16_t *loader) {
580fe4df 1903
508df915
JJ
1904 assert(config);
1905 assert(device);
1906 assert(root_dir);
1907 assert(id);
1908 assert(title);
508df915 1909
c1d4e298 1910 if (!config->auto_entries)
661615a0 1911 return NULL;
c1d4e298 1912
af7ef648 1913 if (!loader) {
a083aed0 1914 loader = u"\\EFI\\BOOT\\BOOT" EFI_MACHINE_TYPE_NAME ".efi";
12450f2e
JJ
1915
1916 /* We are trying to add the default EFI loader here,
1917 * but we do not want to do that if that would be us.
1918 *
1919 * If the default loader is not us, it might be shim. It would
0b75493d 1920 * chainload GRUBX64.EFI in that case, which might be us. */
ec436bdf 1921 if (strcaseeq16(loader, loaded_image_path) ||
12450f2e 1922 is_sd_boot(root_dir, loader) ||
a083aed0 1923 is_sd_boot(root_dir, u"\\EFI\\BOOT\\GRUB" EFI_MACHINE_TYPE_NAME u".EFI"))
661615a0 1924 return NULL;
4c8c9f9f 1925 }
0fa2cac4
KS
1926
1927 /* check existence */
85eb489e 1928 _cleanup_(file_closep) EFI_FILE *handle = NULL;
3639d1b0 1929 EFI_STATUS err = root_dir->Open(root_dir, &handle, (char16_t *) loader, EFI_FILE_MODE_READ, 0ULL);
2a5e4fe4 1930 if (err != EFI_SUCCESS)
661615a0 1931 return NULL;
0fa2cac4 1932
095a153d
JJ
1933 ConfigEntry *entry = xnew(ConfigEntry, 1);
1934 *entry = (ConfigEntry) {
7783ab5d 1935 .id = xstrdup16(id),
095a153d 1936 .type = LOADER_AUTO,
7783ab5d 1937 .title = xstrdup16(title),
095a153d 1938 .device = device,
7783ab5d 1939 .loader = xstrdup16(loader),
095a153d 1940 .key = key,
15338195
JJ
1941 .tries_done = -1,
1942 .tries_left = -1,
095a153d
JJ
1943 };
1944
1945 config_add_entry(config, entry);
1946 return entry;
0fa2cac4
KS
1947}
1948
70cd15e9 1949static void config_entry_add_osx(Config *config) {
0fa2cac4 1950 EFI_STATUS err;
dede50a7 1951 size_t n_handles = 0;
93521e55 1952 _cleanup_free_ EFI_HANDLE *handles = NULL;
0fa2cac4 1953
508df915
JJ
1954 assert(config);
1955
c1d4e298
JJ
1956 if (!config->auto_entries)
1957 return;
1958
19f08504
JJ
1959 err = BS->LocateHandleBuffer(
1960 ByProtocol, MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), NULL, &n_handles, &handles);
b05d69ed 1961 if (err != EFI_SUCCESS)
a45d4f35 1962 return;
0fa2cac4 1963
dede50a7 1964 for (size_t i = 0; i < n_handles; i++) {
f747ca3e
JJ
1965 _cleanup_(file_closep) EFI_FILE *root = NULL;
1966
1967 if (open_volume(handles[i], &root) != EFI_SUCCESS)
a45d4f35
JJ
1968 continue;
1969
1970 if (config_entry_add_loader_auto(
1971 config,
1972 handles[i],
1973 root,
1974 NULL,
a083aed0 1975 u"auto-osx",
a45d4f35 1976 'a',
a083aed0
JJ
1977 u"macOS",
1978 u"\\System\\Library\\CoreServices\\boot.efi"))
a45d4f35 1979 break;
0fa2cac4
KS
1980 }
1981}
1982
5d552524 1983#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
661615a0 1984static EFI_STATUS boot_windows_bitlocker(void) {
93521e55 1985 _cleanup_free_ EFI_HANDLE *handles = NULL;
dede50a7 1986 size_t n_handles;
661615a0
JJ
1987 EFI_STATUS err;
1988
deb5c820
LP
1989 // FIXME: Experimental for now. Should be generalized, and become a per-entry option that can be
1990 // enabled independently of BitLocker, and without a BootXXXX entry pre-existing.
1991
661615a0
JJ
1992 /* BitLocker key cannot be sealed without a TPM present. */
1993 if (!tpm_present())
1994 return EFI_NOT_FOUND;
1995
19f08504
JJ
1996 err = BS->LocateHandleBuffer(
1997 ByProtocol, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), NULL, &n_handles, &handles);
2a5e4fe4 1998 if (err != EFI_SUCCESS)
661615a0
JJ
1999 return err;
2000
2001 /* Look for BitLocker magic string on all block drives. */
e5a1b8f9 2002 bool found = false;
dede50a7 2003 for (size_t i = 0; i < n_handles; i++) {
b30a43df 2004 EFI_BLOCK_IO_PROTOCOL *block_io;
19f08504 2005 err = BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io);
2a5e4fe4 2006 if (err != EFI_SUCCESS || block_io->Media->BlockSize < 512 || block_io->Media->BlockSize > 4096)
661615a0
JJ
2007 continue;
2008
07d0fde4 2009 char buf[4096];
2ebb69bc 2010 err = block_io->ReadBlocks(block_io, block_io->Media->MediaId, 0, sizeof(buf), buf);
2a5e4fe4 2011 if (err != EFI_SUCCESS)
661615a0
JJ
2012 continue;
2013
bbc1f2ea 2014 if (memcmp(buf + 3, "-FVE-FS-", STRLEN("-FVE-FS-")) == 0) {
e5a1b8f9 2015 found = true;
661615a0
JJ
2016 break;
2017 }
2018 }
2019
2020 /* If no BitLocker drive was found, we can just chainload bootmgfw.efi directly. */
2021 if (!found)
2022 return EFI_NOT_FOUND;
2023
93521e55 2024 _cleanup_free_ uint16_t *boot_order = NULL;
dede50a7 2025 size_t boot_order_size;
661615a0
JJ
2026
2027 /* There can be gaps in Boot#### entries. Instead of iterating over the full
db4122d1 2028 * EFI var list or uint16_t namespace, just look for "Windows Boot Manager" in BootOrder. */
19f08504 2029 err = efivar_get_raw(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"BootOrder", (char **) &boot_order, &boot_order_size);
db4122d1 2030 if (err != EFI_SUCCESS || boot_order_size % sizeof(uint16_t) != 0)
661615a0
JJ
2031 return err;
2032
dede50a7 2033 for (size_t i = 0; i < boot_order_size / sizeof(uint16_t); i++) {
07d0fde4 2034 _cleanup_free_ char *buf = NULL;
dede50a7 2035 size_t buf_size;
661615a0 2036
2f3c3b0b 2037 _cleanup_free_ char16_t *name = xasprintf("Boot%04x", boot_order[i]);
19f08504 2038 err = efivar_get_raw(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), name, &buf, &buf_size);
2a5e4fe4 2039 if (err != EFI_SUCCESS)
661615a0
JJ
2040 continue;
2041
2042 /* Boot#### are EFI_LOAD_OPTION. But we really are only interested
2043 * for the description, which is at this offset. */
dede50a7 2044 size_t offset = sizeof(uint32_t) + sizeof(uint16_t);
3639d1b0 2045 if (buf_size < offset + sizeof(char16_t))
661615a0
JJ
2046 continue;
2047
a083aed0 2048 if (streq16((char16_t *) (buf + offset), u"Windows Boot Manager")) {
661615a0 2049 err = efivar_set_raw(
19f08504
JJ
2050 MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE),
2051 u"BootNext",
661615a0
JJ
2052 boot_order + i,
2053 sizeof(boot_order[i]),
2054 EFI_VARIABLE_NON_VOLATILE);
2a5e4fe4 2055 if (err != EFI_SUCCESS)
661615a0 2056 return err;
c4ba5fef
JJ
2057 RT->ResetSystem(EfiResetWarm, EFI_SUCCESS, 0, NULL);
2058 assert_not_reached();
661615a0
JJ
2059 }
2060 }
2061
2062 return EFI_NOT_FOUND;
2063}
5d552524 2064#endif
661615a0 2065
70cd15e9 2066static void config_entry_add_windows(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir) {
77fcf28c 2067#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
07d0fde4 2068 _cleanup_free_ char *bcd = NULL;
3639d1b0 2069 char16_t *title = NULL;
4a59f399 2070 EFI_STATUS err;
dede50a7 2071 size_t len;
4a59f399
JJ
2072
2073 assert(config);
2074 assert(device);
2075 assert(root_dir);
2076
2077 if (!config->auto_entries)
2078 return;
2079
2080 /* Try to find a better title. */
a083aed0 2081 err = file_read(root_dir, u"\\EFI\\Microsoft\\Boot\\BCD", 0, 100*1024, &bcd, &len);
2a5e4fe4 2082 if (err == EFI_SUCCESS)
db4122d1 2083 title = get_bcd_title((uint8_t *) bcd, len);
4a59f399 2084
661615a0 2085 ConfigEntry *e = config_entry_add_loader_auto(config, device, root_dir, NULL,
a083aed0
JJ
2086 u"auto-windows", 'w', title ?: u"Windows Boot Manager",
2087 u"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
661615a0
JJ
2088
2089 if (config->reboot_for_bitlocker)
2090 e->call = boot_windows_bitlocker;
77fcf28c 2091#endif
4a59f399
JJ
2092}
2093
9287b661 2094static void config_entry_add_unified(
580fe4df 2095 Config *config,
c8ad5996 2096 EFI_HANDLE *device,
580fe4df
LP
2097 EFI_FILE *root_dir) {
2098
85eb489e 2099 _cleanup_(file_closep) EFI_FILE *linux_dir = NULL;
93521e55 2100 _cleanup_free_ EFI_FILE_INFO *f = NULL;
dede50a7 2101 size_t f_size = 0;
19c896e9 2102 EFI_STATUS err;
0fa2cac4 2103
cf5d9598
LP
2104 /* Adds Boot Loader Type #2 entries (i.e. /EFI/Linux/….efi) */
2105
508df915
JJ
2106 assert(config);
2107 assert(device);
2108 assert(root_dir);
2109
a083aed0 2110 err = open_directory(root_dir, u"\\EFI\\Linux", &linux_dir);
2a5e4fe4 2111 if (err != EFI_SUCCESS)
12643e7c 2112 return;
0fa2cac4 2113
12643e7c 2114 for (;;) {
65ff3d26
JJ
2115 enum {
2116 SECTION_CMDLINE,
2117 SECTION_OSREL,
2118 _SECTION_MAX,
2119 };
2120
07d0fde4
JJ
2121 static const char * const sections[_SECTION_MAX + 1] = {
2122 [SECTION_CMDLINE] = ".cmdline",
2123 [SECTION_OSREL] = ".osrel",
65ff3d26
JJ
2124 NULL,
2125 };
2126
3639d1b0 2127 _cleanup_free_ char16_t *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
095a153d 2128 *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
3639d1b0 2129 const char16_t *good_name, *good_version, *good_sort_key;
07d0fde4 2130 _cleanup_free_ char *content = NULL;
dede50a7
JJ
2131 size_t offs[_SECTION_MAX] = {}, szs[_SECTION_MAX] = {}, pos = 0;
2132 char *line, *key, *value;
12643e7c 2133
5fa3e628 2134 err = readdir(linux_dir, &f, &f_size);
2a5e4fe4 2135 if (err != EFI_SUCCESS || !f)
12643e7c 2136 break;
0fa2cac4 2137
12643e7c
ZJS
2138 if (f->FileName[0] == '.')
2139 continue;
785b5fcf 2140 if (FLAGS_SET(f->Attribute, EFI_FILE_DIRECTORY))
12643e7c 2141 continue;
a083aed0 2142 if (!endswith_no_case(f->FileName, u".efi"))
12643e7c 2143 continue;
a083aed0 2144 if (startswith(f->FileName, u"auto-"))
65901c0f 2145 continue;
0fa2cac4 2146
12643e7c 2147 /* look for .osrel and .cmdline sections in the .efi binary */
6028dd6d 2148 err = pe_file_locate_sections(linux_dir, f->FileName, sections, offs, szs);
2a5e4fe4 2149 if (err != EFI_SUCCESS || szs[SECTION_OSREL] == 0)
12643e7c 2150 continue;
0fa2cac4 2151
65ff3d26 2152 err = file_read(linux_dir, f->FileName, offs[SECTION_OSREL], szs[SECTION_OSREL], &content, NULL);
2a5e4fe4 2153 if (err != EFI_SUCCESS)
12643e7c 2154 continue;
0fa2cac4 2155
12643e7c 2156 /* read properties from the embedded os-release file */
07d0fde4
JJ
2157 while ((line = line_get_key_value(content, "=", &pos, &key, &value))) {
2158 if (streq8(key, "PRETTY_NAME")) {
0af26643 2159 free(os_pretty_name);
aee515bb 2160 os_pretty_name = xstr8_to_16(value);
10d0024a
SM
2161 continue;
2162 }
2163
07d0fde4 2164 if (streq8(key, "IMAGE_ID")) {
0af26643 2165 free(os_image_id);
aee515bb 2166 os_image_id = xstr8_to_16(value);
18fde343
LP
2167 continue;
2168 }
2169
07d0fde4 2170 if (streq8(key, "NAME")) {
0af26643 2171 free(os_name);
aee515bb 2172 os_name = xstr8_to_16(value);
12643e7c
ZJS
2173 continue;
2174 }
0fa2cac4 2175
07d0fde4 2176 if (streq8(key, "ID")) {
0af26643 2177 free(os_id);
aee515bb 2178 os_id = xstr8_to_16(value);
12643e7c
ZJS
2179 continue;
2180 }
59658d19 2181
07d0fde4 2182 if (streq8(key, "IMAGE_VERSION")) {
0af26643 2183 free(os_image_version);
aee515bb 2184 os_image_version = xstr8_to_16(value);
18fde343
LP
2185 continue;
2186 }
2187
07d0fde4 2188 if (streq8(key, "VERSION")) {
0af26643 2189 free(os_version);
aee515bb 2190 os_version = xstr8_to_16(value);
12643e7c 2191 continue;
0fa2cac4
KS
2192 }
2193
07d0fde4 2194 if (streq8(key, "VERSION_ID")) {
0af26643 2195 free(os_version_id);
aee515bb 2196 os_version_id = xstr8_to_16(value);
10d0024a
SM
2197 continue;
2198 }
2199
07d0fde4 2200 if (streq8(key, "BUILD_ID")) {
0af26643 2201 free(os_build_id);
aee515bb 2202 os_build_id = xstr8_to_16(value);
12643e7c 2203 continue;
0fa2cac4 2204 }
12643e7c
ZJS
2205 }
2206
20ec8f53 2207 if (!bootspec_pick_name_version_sort_key(
18fde343
LP
2208 os_pretty_name,
2209 os_image_id,
2210 os_name,
2211 os_id,
2212 os_image_version,
2213 os_version,
2214 os_version_id,
2215 os_build_id,
2216 &good_name,
20ec8f53
LP
2217 &good_version,
2218 &good_sort_key))
18fde343 2219 continue;
65ff3d26 2220
095a153d
JJ
2221 ConfigEntry *entry = xnew(ConfigEntry, 1);
2222 *entry = (ConfigEntry) {
7783ab5d 2223 .id = xstrdup16(f->FileName),
095a153d 2224 .type = LOADER_UNIFIED_LINUX,
7783ab5d
JJ
2225 .title = xstrdup16(good_name),
2226 .version = xstrdup16(good_version),
095a153d 2227 .device = device,
2f3c3b0b 2228 .loader = xasprintf("\\EFI\\Linux\\%ls", f->FileName),
7783ab5d 2229 .sort_key = xstrdup16(good_sort_key),
095a153d 2230 .key = 'l',
15338195
JJ
2231 .tries_done = -1,
2232 .tries_left = -1,
095a153d 2233 };
18fde343 2234
fc178cf1 2235 strtolower16(entry->id);
095a153d 2236 config_add_entry(config, entry);
a083aed0 2237 config_entry_parse_tries(entry, u"\\EFI\\Linux", f->FileName, u".efi");
18fde343
LP
2238
2239 if (szs[SECTION_CMDLINE] == 0)
2240 continue;
2214cfbf 2241
18fde343 2242 content = mfree(content);
d3a27ed4 2243
18fde343 2244 /* read the embedded cmdline file */
927ebebe
JJ
2245 size_t cmdline_len;
2246 err = file_read(linux_dir, f->FileName, offs[SECTION_CMDLINE], szs[SECTION_CMDLINE], &content, &cmdline_len);
2a5e4fe4 2247 if (err == EFI_SUCCESS) {
927ebebe
JJ
2248 entry->options = xstrn8_to_16(content, cmdline_len);
2249 mangle_stub_cmdline(entry->options);
0fa2cac4 2250 }
0fa2cac4
KS
2251 }
2252}
2253
70cd15e9 2254static void config_load_xbootldr(
fa2a3bbd
LP
2255 Config *config,
2256 EFI_HANDLE *device) {
2257
85eb489e 2258 _cleanup_(file_closep) EFI_FILE *root_dir = NULL;
febe5561 2259 EFI_HANDLE new_device = NULL; /* avoid false maybe-uninitialized warning */
bcbc3e81 2260 EFI_STATUS err;
fa2a3bbd 2261
508df915
JJ
2262 assert(config);
2263 assert(device);
2264
19f08504 2265 err = partition_open(MAKE_GUID_PTR(XBOOTLDR), device, &new_device, &root_dir);
f747ca3e 2266 if (err != EFI_SUCCESS)
fa2a3bbd
LP
2267 return;
2268
9287b661 2269 config_entry_add_unified(config, new_device, root_dir);
fa2a3bbd
LP
2270 config_load_entries(config, new_device, root_dir, NULL);
2271}
2272
acdf7d15
JJ
2273static EFI_STATUS initrd_prepare(
2274 EFI_FILE *root,
2275 const ConfigEntry *entry,
3639d1b0 2276 char16_t **ret_options,
46ce6cf7 2277 void **ret_initrd,
dede50a7 2278 size_t *ret_initrd_size) {
acdf7d15
JJ
2279
2280 assert(root);
2281 assert(entry);
2282 assert(ret_options);
46ce6cf7
JJ
2283 assert(ret_initrd);
2284 assert(ret_initrd_size);
acdf7d15
JJ
2285
2286 if (entry->type != LOADER_LINUX || !entry->initrd) {
2287 ret_options = NULL;
46ce6cf7
JJ
2288 ret_initrd = NULL;
2289 ret_initrd_size = 0;
acdf7d15
JJ
2290 return EFI_SUCCESS;
2291 }
2292
46ce6cf7
JJ
2293 /* Note that order of initrds matters. The kernel will only look for microcode updates in the very
2294 * first one it sees. */
2295
acdf7d15
JJ
2296 /* Add initrd= to options for older kernels that do not support LINUX_INITRD_MEDIA. Should be dropped
2297 * if linux_x86.c is dropped. */
3639d1b0 2298 _cleanup_free_ char16_t *options = NULL;
acdf7d15 2299
46ce6cf7 2300 EFI_STATUS err;
dede50a7 2301 size_t size = 0;
93521e55 2302 _cleanup_free_ uint8_t *initrd = NULL;
46ce6cf7 2303
acdf7d15 2304 STRV_FOREACH(i, entry->initrd) {
3639d1b0 2305 _cleanup_free_ char16_t *o = options;
acdf7d15 2306 if (o)
2f3c3b0b 2307 options = xasprintf("%ls initrd=%ls", o, *i);
acdf7d15 2308 else
2f3c3b0b 2309 options = xasprintf("initrd=%ls", *i);
46ce6cf7
JJ
2310
2311 _cleanup_(file_closep) EFI_FILE *handle = NULL;
2312 err = root->Open(root, &handle, *i, EFI_FILE_MODE_READ, 0);
2a5e4fe4 2313 if (err != EFI_SUCCESS)
46ce6cf7
JJ
2314 return err;
2315
93521e55 2316 _cleanup_free_ EFI_FILE_INFO *info = NULL;
5fa3e628 2317 err = get_file_info(handle, &info, NULL);
2a5e4fe4 2318 if (err != EFI_SUCCESS)
46ce6cf7
JJ
2319 return err;
2320
fd1fec53
LP
2321 if (info->FileSize == 0) /* Automatically skip over empty files */
2322 continue;
2323
dede50a7 2324 size_t new_size, read_size = info->FileSize;
46ce6cf7
JJ
2325 if (__builtin_add_overflow(size, read_size, &new_size))
2326 return EFI_OUT_OF_RESOURCES;
0af26643 2327 initrd = xrealloc(initrd, size, new_size);
46ce6cf7 2328
f70f9922 2329 err = chunked_read(handle, &read_size, initrd + size);
2a5e4fe4 2330 if (err != EFI_SUCCESS)
46ce6cf7
JJ
2331 return err;
2332
2333 /* Make sure the actual read size is what we expected. */
2334 assert(size + read_size == new_size);
2335 size = new_size;
acdf7d15
JJ
2336 }
2337
2338 if (entry->options) {
3639d1b0 2339 _cleanup_free_ char16_t *o = options;
2f3c3b0b 2340 options = xasprintf("%ls %ls", o, entry->options);
acdf7d15
JJ
2341 }
2342
2343 *ret_options = TAKE_PTR(options);
46ce6cf7
JJ
2344 *ret_initrd = TAKE_PTR(initrd);
2345 *ret_initrd_size = size;
acdf7d15
JJ
2346 return EFI_SUCCESS;
2347}
2348
580fe4df
LP
2349static EFI_STATUS image_start(
2350 EFI_HANDLE parent_image,
580fe4df
LP
2351 const ConfigEntry *entry) {
2352
6e86342b 2353 _cleanup_(devicetree_cleanup) struct devicetree_state dtstate = {};
f386daa0 2354 _cleanup_(unload_imagep) EFI_HANDLE image = NULL;
93521e55 2355 _cleanup_free_ EFI_DEVICE_PATH *path = NULL;
0fa2cac4
KS
2356 EFI_STATUS err;
2357
508df915
JJ
2358 assert(entry);
2359
661615a0
JJ
2360 /* If this loader entry has a special way to boot, try that first. */
2361 if (entry->call)
2362 (void) entry->call();
2363
f747ca3e
JJ
2364 _cleanup_(file_closep) EFI_FILE *image_root = NULL;
2365 err = open_volume(entry->device, &image_root);
2366 if (err != EFI_SUCCESS)
c2c62035 2367 return log_error_status(err, "Error opening root path: %m");
7b21973c 2368
79a2b916
JJ
2369 err = make_file_device_path(entry->device, entry->loader, &path);
2370 if (err != EFI_SUCCESS)
c2c62035 2371 return log_error_status(err, "Error making file device path: %m");
0fa2cac4 2372
dede50a7 2373 size_t initrd_size = 0;
93521e55 2374 _cleanup_free_ void *initrd = NULL;
3639d1b0 2375 _cleanup_free_ char16_t *options_initrd = NULL;
46ce6cf7 2376 err = initrd_prepare(image_root, entry, &options_initrd, &initrd, &initrd_size);
2a5e4fe4 2377 if (err != EFI_SUCCESS)
c2c62035 2378 return log_error_status(err, "Error preparing initrd: %m");
acdf7d15 2379
09da51f8 2380 err = shim_load_image(parent_image, path, &image);
2a5e4fe4 2381 if (err != EFI_SUCCESS)
c2c62035 2382 return log_error_status(err, "Error loading %ls: %m", entry->loader);
0fa2cac4 2383
6e86342b 2384 if (entry->devicetree) {
7b21973c 2385 err = devicetree_install(&dtstate, image_root, entry->devicetree);
2a5e4fe4 2386 if (err != EFI_SUCCESS)
c2c62035 2387 return log_error_status(err, "Error loading %ls: %m", entry->devicetree);
6e86342b
ERB
2388 }
2389
46ce6cf7
JJ
2390 _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
2391 err = initrd_register(initrd, initrd_size, &initrd_handle);
2a5e4fe4 2392 if (err != EFI_SUCCESS)
c2c62035 2393 return log_error_status(err, "Error registering initrd: %m");
46ce6cf7 2394
b30a43df 2395 EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
19f08504 2396 err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
2a5e4fe4 2397 if (err != EFI_SUCCESS)
c2c62035 2398 return log_error_status(err, "Error getting LoadedImageProtocol handle: %m");
c43a282c 2399
3639d1b0 2400 char16_t *options = options_initrd ?: entry->options;
acdf7d15 2401 if (options) {
acdf7d15 2402 loaded_image->LoadOptions = options;
60c2af56 2403 loaded_image->LoadOptionsSize = strsize16(options);
92ed3bb4 2404
7db5706e 2405 /* Try to log any options to the TPM, especially to catch manually edited options */
8d5e4d59 2406 (void) tpm_log_load_options(options, NULL);
0fa2cac4
KS
2407 }
2408
19f08504 2409 efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeExecUSec", 0);
12f32748 2410 err = BS->StartImage(image, NULL, NULL);
e5a1b8f9 2411 graphics_mode(false);
c43a282c
JJ
2412 if (err == EFI_SUCCESS)
2413 return EFI_SUCCESS;
f386daa0 2414
c43a282c
JJ
2415 /* Try calling the kernel compat entry point if one exists. */
2416 if (err == EFI_UNSUPPORTED && entry->type == LOADER_LINUX) {
dcde6ae1 2417 uint32_t compat_address;
c43a282c 2418
dcde6ae1 2419 err = pe_kernel_info(loaded_image->ImageBase, &compat_address);
2a5e4fe4 2420 if (err != EFI_SUCCESS) {
c43a282c 2421 if (err != EFI_UNSUPPORTED)
c2c62035 2422 return log_error_status(err, "Error finding kernel compat entry address: %m");
dcde6ae1 2423 } else if (compat_address > 0) {
c43a282c 2424 EFI_IMAGE_ENTRY_POINT kernel_entry =
dcde6ae1 2425 (EFI_IMAGE_ENTRY_POINT) ((uint8_t *) loaded_image->ImageBase + compat_address);
c43a282c
JJ
2426
2427 err = kernel_entry(image, ST);
e5a1b8f9 2428 graphics_mode(false);
c43a282c
JJ
2429 if (err == EFI_SUCCESS)
2430 return EFI_SUCCESS;
dcde6ae1
JJ
2431 } else
2432 err = EFI_UNSUPPORTED;
c43a282c
JJ
2433 }
2434
c2c62035 2435 return log_error_status(err, "Failed to execute %ls (%ls): %m", entry->title_show, entry->loader);
0fa2cac4
KS
2436}
2437
70cd15e9 2438static void config_free(Config *config) {
508df915 2439 assert(config);
3a59c55f 2440 for (size_t i = 0; i < config->n_entries; i++)
0fa2cac4 2441 config_entry_free(config->entries[i]);
0af26643
JJ
2442 free(config->entries);
2443 free(config->entry_default_config);
7da9db53 2444 free(config->entry_default_efivar);
0af26643 2445 free(config->entry_oneshot);
7da9db53 2446 free(config->entry_saved);
0fa2cac4
KS
2447}
2448
70cd15e9 2449static void config_write_entries_to_variable(Config *config) {
07d0fde4 2450 _cleanup_free_ char *buffer = NULL;
dede50a7 2451 size_t sz = 0;
07d0fde4 2452 char *p;
0e2bc732 2453
508df915
JJ
2454 assert(config);
2455
3a59c55f 2456 for (size_t i = 0; i < config->n_entries; i++)
60c2af56 2457 sz += strsize16(config->entries[i]->id);
0e2bc732 2458
0af26643 2459 p = buffer = xmalloc(sz);
0e2bc732 2460
3a59c55f 2461 for (size_t i = 0; i < config->n_entries; i++)
515581d6 2462 p = mempcpy(p, config->entries[i]->id, strsize16(config->entries[i]->id));
0e2bc732 2463
b3fc3a3c
JJ
2464 assert(p == buffer + sz);
2465
0e2bc732 2466 /* Store the full list of discovered entries. */
19f08504 2467 (void) efivar_set_raw(MAKE_GUID_PTR(LOADER), u"LoaderEntries", buffer, sz, 0);
0e2bc732
LP
2468}
2469
ee4fd9cb
JJ
2470static void save_selected_entry(const Config *config, const ConfigEntry *entry) {
2471 assert(config);
2472 assert(entry);
661615a0 2473 assert(entry->loader || !entry->call);
ee4fd9cb
JJ
2474
2475 /* Always export the selected boot entry to the system in a volatile var. */
19f08504 2476 (void) efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderEntrySelected", entry->id, 0);
ee4fd9cb
JJ
2477
2478 /* Do not save or delete if this was a oneshot boot. */
ec436bdf 2479 if (streq16(config->entry_oneshot, entry->id))
ee4fd9cb
JJ
2480 return;
2481
2482 if (config->use_saved_entry_efivar || (!config->entry_default_efivar && config->use_saved_entry)) {
2483 /* Avoid unnecessary NVRAM writes. */
ec436bdf 2484 if (streq16(config->entry_saved, entry->id))
ee4fd9cb
JJ
2485 return;
2486
19f08504 2487 (void) efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderEntryLastBooted", entry->id, EFI_VARIABLE_NON_VOLATILE);
ee4fd9cb
JJ
2488 } else
2489 /* Delete the non-volatile var if not needed. */
19f08504 2490 (void) efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderEntryLastBooted", NULL, EFI_VARIABLE_NON_VOLATILE);
ee4fd9cb
JJ
2491}
2492
e6b0cfad
VD
2493static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) {
2494 EFI_STATUS err;
2495 _cleanup_(file_closep) EFI_FILE *keys_basedir = NULL;
2496
2497 if (secure_boot_mode() != SECURE_BOOT_SETUP)
2498 return EFI_SUCCESS;
2499
2500 /* the lack of a 'keys' directory is not fatal and is silently ignored */
2501 err = open_directory(root_dir, u"\\loader\\keys", &keys_basedir);
2502 if (err == EFI_NOT_FOUND)
2503 return EFI_SUCCESS;
2504 if (err != EFI_SUCCESS)
2505 return err;
2506
2507 for (;;) {
2508 _cleanup_free_ EFI_FILE_INFO *dirent = NULL;
2509 size_t dirent_size = 0;
2510 ConfigEntry *entry = NULL;
2511
5fa3e628 2512 err = readdir(keys_basedir, &dirent, &dirent_size);
e6b0cfad
VD
2513 if (err != EFI_SUCCESS || !dirent)
2514 return err;
2515
2516 if (dirent->FileName[0] == '.')
2517 continue;
2518
2519 if (!FLAGS_SET(dirent->Attribute, EFI_FILE_DIRECTORY))
2520 continue;
2521
2522 entry = xnew(ConfigEntry, 1);
2523 *entry = (ConfigEntry) {
2f3c3b0b
JJ
2524 .id = xasprintf("secure-boot-keys-%ls", dirent->FileName),
2525 .title = xasprintf("Enroll Secure Boot keys: %ls", dirent->FileName),
2526 .path = xasprintf("\\loader\\keys\\%ls", dirent->FileName),
e6b0cfad
VD
2527 .type = LOADER_SECURE_BOOT_KEYS,
2528 .tries_done = -1,
2529 .tries_left = -1,
2530 };
2531 config_add_entry(config, entry);
2532
1e8e7f44
JJ
2533 if (IN_SET(config->secure_boot_enroll, ENROLL_IF_SAFE, ENROLL_FORCE) &&
2534 strcaseeq16(dirent->FileName, u"auto"))
5c19169f 2535 /* if we auto enroll successfully this call does not return, if it fails we still
e6b0cfad 2536 * want to add other potential entries to the menu */
1e8e7f44 2537 secure_boot_enroll_at(root_dir, entry->path, config->secure_boot_enroll == ENROLL_FORCE);
e6b0cfad
VD
2538 }
2539
2540 return EFI_SUCCESS;
2541}
2542
70cd15e9 2543static void export_variables(
b30a43df 2544 EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
3639d1b0 2545 const char16_t *loaded_image_path,
db4122d1 2546 uint64_t init_usec) {
0be2a06a 2547
db4122d1 2548 static const uint64_t loader_features =
91b08bb0
ZJS
2549 EFI_LOADER_FEATURE_CONFIG_TIMEOUT |
2550 EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT |
2551 EFI_LOADER_FEATURE_ENTRY_DEFAULT |
2552 EFI_LOADER_FEATURE_ENTRY_ONESHOT |
2553 EFI_LOADER_FEATURE_BOOT_COUNTING |
3a76fbe0 2554 EFI_LOADER_FEATURE_XBOOTLDR |
e4dcf7aa 2555 EFI_LOADER_FEATURE_RANDOM_SEED |
2553a548 2556 EFI_LOADER_FEATURE_LOAD_DRIVER |
a67f22c4
LP
2557 EFI_LOADER_FEATURE_SORT_KEY |
2558 EFI_LOADER_FEATURE_SAVED_ENTRY |
2559 EFI_LOADER_FEATURE_DEVICETREE |
92bb46c4
LP
2560 EFI_LOADER_FEATURE_SECUREBOOT_ENROLL |
2561 EFI_LOADER_FEATURE_RETAIN_SHIM |
5dd5f7cf
LP
2562 0;
2563
3639d1b0 2564 _cleanup_free_ char16_t *infostr = NULL, *typestr = NULL;
0fa2cac4 2565
0be2a06a 2566 assert(loaded_image);
0be2a06a 2567
19f08504
JJ
2568 efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeInitUSec", init_usec);
2569 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderInfo", u"systemd-boot " GIT_VERSION, 0);
a42d7cf1 2570
2f3c3b0b 2571 infostr = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
19f08504 2572 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", infostr, 0);
a42d7cf1 2573
2f3c3b0b 2574 typestr = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
19f08504 2575 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", typestr, 0);
0fa2cac4 2576
19f08504 2577 (void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", loader_features, 0);
5dd5f7cf 2578
0be2a06a 2579 /* the filesystem path to this image, to prevent adding ourselves to the menu */
19f08504 2580 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", loaded_image_path, 0);
0fa2cac4
KS
2581
2582 /* export the device path this image is started from */
8e760b3f
JJ
2583 _cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle);
2584 if (uuid)
19f08504 2585 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0);
0be2a06a
LP
2586}
2587
70cd15e9 2588static void config_load_all_entries(
d72f81be 2589 Config *config,
b30a43df 2590 EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
3639d1b0 2591 const char16_t *loaded_image_path,
d72f81be
LP
2592 EFI_FILE *root_dir) {
2593
d72f81be
LP
2594 assert(config);
2595 assert(loaded_image);
d72f81be
LP
2596 assert(root_dir);
2597
2598 config_load_defaults(config, root_dir);
2599
2600 /* scan /EFI/Linux/ directory */
9287b661 2601 config_entry_add_unified(config, loaded_image->DeviceHandle, root_dir);
d72f81be
LP
2602
2603 /* scan /loader/entries/\*.conf files */
2604 config_load_entries(config, loaded_image->DeviceHandle, root_dir, loaded_image_path);
2605
2606 /* Similar, but on any XBOOTLDR partition */
2607 config_load_xbootldr(config, loaded_image->DeviceHandle);
2608
2609 /* sort entries after version number */
3a59c55f 2610 sort_pointer_array((void **) config->entries, config->n_entries, (compare_pointer_func_t) config_entry_compare);
d72f81be
LP
2611
2612 /* if we find some well-known loaders, add them to the end of the list */
f65a3326
LP
2613 config_entry_add_osx(config);
2614 config_entry_add_windows(config, loaded_image->DeviceHandle, root_dir);
d72f81be 2615 config_entry_add_loader_auto(config, loaded_image->DeviceHandle, root_dir, NULL,
a083aed0 2616 u"auto-efi-shell", 's', u"EFI Shell", u"\\shell" EFI_MACHINE_TYPE_NAME ".efi");
d72f81be 2617 config_entry_add_loader_auto(config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
a083aed0 2618 u"auto-efi-default", '\0', u"EFI Default Loader", NULL);
d72f81be 2619
095a153d
JJ
2620 if (config->auto_firmware && FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) {
2621 ConfigEntry *entry = xnew(ConfigEntry, 1);
2622 *entry = (ConfigEntry) {
7783ab5d
JJ
2623 .id = xstrdup16(u"auto-reboot-to-firmware-setup"),
2624 .title = xstrdup16(u"Reboot Into Firmware Interface"),
095a153d 2625 .call = reboot_into_firmware,
15338195
JJ
2626 .tries_done = -1,
2627 .tries_left = -1,
095a153d
JJ
2628 };
2629 config_add_entry(config, entry);
2630 }
163d1ea5 2631
e6b0cfad
VD
2632 /* find if secure boot signing keys exist and autoload them if necessary
2633 otherwise creates menu entries so that the user can load them manually
2634 if the secure-boot-enroll variable is set to no (the default), we do not
2635 even search for keys on the ESP */
2636 if (config->secure_boot_enroll != ENROLL_OFF)
2637 secure_boot_discover_keys(config, root_dir);
2638
3a59c55f 2639 if (config->n_entries == 0)
0bf78c12 2640 return;
163d1ea5
JJ
2641
2642 config_write_entries_to_variable(config);
2643
2644 config_title_generate(config);
2645
2646 /* select entry by configured pattern or EFI LoaderDefaultEntry= variable */
2647 config_default_entry_select(config);
d72f81be
LP
2648}
2649
8fec4f95
GH
2650static EFI_STATUS discover_root_dir(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, EFI_FILE **ret_dir) {
2651 if (is_direct_boot(loaded_image->DeviceHandle))
2652 return vmm_open(&loaded_image->DeviceHandle, ret_dir);
2653 else
2654 return open_volume(loaded_image->DeviceHandle, ret_dir);
2655}
2656
31a131bb 2657static EFI_STATUS run(EFI_HANDLE image) {
b30a43df 2658 EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
85eb489e 2659 _cleanup_(file_closep) EFI_FILE *root_dir = NULL;
4ec9d873 2660 _cleanup_(config_free) Config config = {};
b7b327f8 2661 _cleanup_free_ char16_t *loaded_image_path = NULL;
0be2a06a 2662 EFI_STATUS err;
db4122d1 2663 uint64_t init_usec;
e5a1b8f9 2664 bool menu = false;
0be2a06a 2665
0be2a06a
LP
2666 init_usec = time_usec();
2667
e1f1b5fc
LB
2668 /* Ask Shim to leave its protocol around, so that the stub can use it to validate PEs.
2669 * By default, Shim uninstalls its protocol when calling StartImage(). */
2670 shim_retain_protocol();
2671
b4eb2de7 2672 err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
2a5e4fe4 2673 if (err != EFI_SUCCESS)
c2c62035 2674 return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
0be2a06a 2675
af7ef648 2676 (void) device_path_to_str(loaded_image->FilePath, &loaded_image_path);
0be2a06a
LP
2677
2678 export_variables(loaded_image, loaded_image_path, init_usec);
0fa2cac4 2679
8fec4f95 2680 err = discover_root_dir(loaded_image, &root_dir);
f747ca3e 2681 if (err != EFI_SUCCESS)
c2c62035 2682 return log_error_status(err, "Unable to open root directory: %m");
0fa2cac4 2683
70cd15e9 2684 (void) load_drivers(image, loaded_image, root_dir);
2553a548 2685
d72f81be 2686 config_load_all_entries(&config, loaded_image, loaded_image_path, root_dir);
0fa2cac4 2687
3a59c55f 2688 if (config.n_entries == 0)
b4eb2de7
JJ
2689 return log_error_status(
2690 EFI_NOT_FOUND,
2691 "No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
0fa2cac4 2692
0fa2cac4 2693 /* select entry or show menu when key is pressed or timeout is set */
fe2579dd 2694 if (config.force_menu || config.timeout_sec > 0)
e5a1b8f9 2695 menu = true;
fe2579dd 2696 else {
db4122d1 2697 uint64_t key;
0fa2cac4 2698
e98d271e
JJ
2699 /* Block up to 100ms to give firmware time to get input working. */
2700 err = console_key_read(&key, 100 * 1000);
2a5e4fe4 2701 if (err == EFI_SUCCESS) {
0fa2cac4 2702 /* find matching key in config entries */
dede50a7 2703 size_t idx = entry_lookup_key(&config, config.idx_default, KEYCHAR(key));
fb90100c 2704 if (idx != IDX_INVALID)
0fa2cac4
KS
2705 config.idx_default = idx;
2706 else
e5a1b8f9 2707 menu = true;
0fa2cac4 2708 }
fe2579dd 2709 }
0fa2cac4
KS
2710
2711 for (;;) {
2712 ConfigEntry *entry;
2713
2714 entry = config.entries[config.idx_default];
2715 if (menu) {
19f08504 2716 efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeMenuUSec", 0);
7361099e 2717 if (!menu_run(&config, &entry, loaded_image_path))
b4eb2de7 2718 return EFI_SUCCESS;
996daf2f 2719 }
0fa2cac4 2720
e6b0cfad
VD
2721 /* if auto enrollment is activated, we try to load keys for the given entry. */
2722 if (entry->type == LOADER_SECURE_BOOT_KEYS && config.secure_boot_enroll != ENROLL_OFF) {
1e8e7f44 2723 err = secure_boot_enroll_at(root_dir, entry->path, /*force=*/ true);
f234a56d
JJ
2724 if (err != EFI_SUCCESS)
2725 return err;
e6b0cfad
VD
2726 continue;
2727 }
2728
661615a0
JJ
2729 /* Run special entry like "reboot" now. Those that have a loader
2730 * will be handled by image_start() instead. */
2731 if (entry->call && !entry->loader) {
996daf2f
LP
2732 entry->call();
2733 continue;
0fa2cac4
KS
2734 }
2735
66fd078b 2736 (void) config_entry_bump_counters(entry);
ee4fd9cb 2737 save_selected_entry(&config, entry);
e4dcf7aa
LP
2738
2739 /* Optionally, read a random seed off the ESP and pass it to the OS */
47b3e966 2740 (void) process_random_seed(root_dir);
0fa2cac4 2741
5c330b36 2742 err = image_start(image, entry);
f386daa0 2743 if (err != EFI_SUCCESS)
b4eb2de7 2744 return err;
0fa2cac4 2745
e5a1b8f9 2746 menu = true;
0fa2cac4
KS
2747 config.timeout_sec = 0;
2748 }
0fa2cac4 2749}
6ac54809 2750
31a131bb 2751DEFINE_EFI_MAIN_FUNCTION(run, "systemd-boot", /*wait_for_debugger=*/false);