]>
Commit | Line | Data |
---|---|---|
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 | 37 | typedef 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 | |
47 | typedef 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 | ||
69 | typedef 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 ↔ … */ | |
97 | enum { | |
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 |
106 | enum { |
107 | IDX_MAX = INT16_MAX, | |
108 | IDX_INVALID, | |
109 | }; | |
110 | ||
dede50a7 | 111 | static 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 | 121 | static 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 | 131 | static 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 | 358 | static 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 | 384 | static 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 | 416 | static 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 | 427 | static 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 | 436 | static 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 | 598 | static 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 | 616 | static 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 | 1078 | static 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 | 1094 | static 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 | 1114 | static void config_entry_freep(ConfigEntry **entry) { |
4ec9d873 JJ |
1115 | config_entry_free(*entry); |
1116 | } | |
1117 | ||
07d0fde4 JJ |
1118 | static 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 |
1192 | static 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 | 1297 | static 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 | 1361 | static 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 | 1417 | static 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 |
1550 | static 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 | 1578 | static 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 | 1639 | static 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 |
1683 | static 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 | 1737 | static 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 | 1752 | static 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 | 1794 | static 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 | 1812 | static 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 | 1871 | static 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 | 1894 | static 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 | 1949 | static 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 | 1984 | static 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 | 2066 | static 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 | 2094 | static 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 | 2254 | static 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 |
2273 | static 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 |
2349 | static 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 | 2438 | static 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 | 2449 | static 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 |
2470 | static 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 |
2493 | static 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 | 2543 | static 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 | 2588 | static 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 |
2650 | static 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 | 2657 | static 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 | 2751 | DEFINE_EFI_MAIN_FUNCTION(run, "systemd-boot", /*wait_for_debugger=*/false); |