]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/bootspec.c
strv: make iterator in STRV_FOREACH() declaread in the loop
[thirdparty/systemd.git] / src / shared / bootspec.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
7e87c7d9 2
ca78ad1d 3#include <unistd.h>
7e87c7d9 4
7e87c7d9 5#include "bootspec.h"
e94830c0 6#include "bootspec-fundamental.h"
7e87c7d9 7#include "conf-files.h"
5e146a75 8#include "dirent-util.h"
0bb2f0f1 9#include "efi-loader.h"
5e146a75 10#include "env-file.h"
7e87c7d9
ZJS
11#include "fd-util.h"
12#include "fileio.h"
e94830c0 13#include "find-esp.h"
cc7a0bfa 14#include "path-util.h"
5e146a75 15#include "pe-header.h"
760877e9 16#include "sort-util.h"
af918182 17#include "stat-util.h"
7e87c7d9 18#include "strv.h"
5e146a75 19#include "unaligned.h"
7e87c7d9 20
0de2e1fd 21static void boot_entry_free(BootEntry *entry) {
4fe2ba0e 22 assert(entry);
7e87c7d9 23
12580bc3 24 free(entry->id);
15b82eec 25 free(entry->id_old);
2d3bfb69 26 free(entry->path);
43b736a8 27 free(entry->root);
7e87c7d9 28 free(entry->title);
64f05708 29 free(entry->show_title);
20ec8f53 30 free(entry->sort_key);
7e87c7d9
ZJS
31 free(entry->version);
32 free(entry->machine_id);
33 free(entry->architecture);
34 strv_free(entry->options);
35 free(entry->kernel);
36 free(entry->efi);
37 strv_free(entry->initrd);
38 free(entry->device_tree);
fdc5c042 39 strv_free(entry->device_tree_overlay);
7e87c7d9
ZJS
40}
41
43b736a8
LP
42static int boot_entry_load(
43 const char *root,
44 const char *path,
45 BootEntry *entry) {
46
93f14ce2
LP
47 _cleanup_(boot_entry_free) BootEntry tmp = {
48 .type = BOOT_ENTRY_CONF,
49 };
50
7e87c7d9
ZJS
51 _cleanup_fclose_ FILE *f = NULL;
52 unsigned line = 1;
736783d4 53 char *c;
7e87c7d9
ZJS
54 int r;
55
43b736a8 56 assert(root);
4fe2ba0e
LP
57 assert(path);
58 assert(entry);
59
736783d4
LP
60 r = path_extract_filename(path, &tmp.id);
61 if (r < 0)
62 return log_error_errno(r, "Failed to extract file name from path '%s': %m", path);
7e87c7d9 63
736783d4
LP
64 c = endswith_no_case(tmp.id, ".conf");
65 if (!c)
66 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", tmp.id);
7e87c7d9 67
eed7210a 68 if (!efi_loader_entry_name_valid(tmp.id))
dfc22cb4 69 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
eed7210a 70
736783d4
LP
71 tmp.id_old = strndup(tmp.id, c - tmp.id);
72 if (!tmp.id_old)
73 return log_oom();
74
2d3bfb69
ZJS
75 tmp.path = strdup(path);
76 if (!tmp.path)
77 return log_oom();
78
43b736a8
LP
79 tmp.root = strdup(root);
80 if (!tmp.root)
81 return log_oom();
82
263195c6
YW
83 f = fopen(path, "re");
84 if (!f)
85 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
86
7e87c7d9 87 for (;;) {
f99fdc3e
YW
88 _cleanup_free_ char *buf = NULL, *field = NULL;
89 const char *p;
7e87c7d9
ZJS
90
91 r = read_line(f, LONG_LINE_MAX, &buf);
92 if (r == 0)
93 break;
94 if (r == -ENOBUFS)
95 return log_error_errno(r, "%s:%u: Line too long", path, line);
96 if (r < 0)
97 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
98
99 line++;
100
101 if (IN_SET(*strstrip(buf), '#', '\0'))
102 continue;
103
f99fdc3e
YW
104 p = buf;
105 r = extract_first_word(&p, &field, " \t", 0);
106 if (r < 0) {
107 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
108 continue;
109 }
110 if (r == 0) {
7e87c7d9
ZJS
111 log_warning("%s:%u: Bad syntax", path, line);
112 continue;
113 }
7e87c7d9 114
f99fdc3e 115 if (streq(field, "title"))
7e87c7d9 116 r = free_and_strdup(&tmp.title, p);
20ec8f53
LP
117 else if (streq(field, "sort-key"))
118 r = free_and_strdup(&tmp.sort_key, p);
f99fdc3e 119 else if (streq(field, "version"))
7e87c7d9 120 r = free_and_strdup(&tmp.version, p);
f99fdc3e 121 else if (streq(field, "machine-id"))
7e87c7d9 122 r = free_and_strdup(&tmp.machine_id, p);
f99fdc3e 123 else if (streq(field, "architecture"))
7e87c7d9 124 r = free_and_strdup(&tmp.architecture, p);
f99fdc3e 125 else if (streq(field, "options"))
7e87c7d9 126 r = strv_extend(&tmp.options, p);
f99fdc3e 127 else if (streq(field, "linux"))
7e87c7d9 128 r = free_and_strdup(&tmp.kernel, p);
f99fdc3e 129 else if (streq(field, "efi"))
7e87c7d9 130 r = free_and_strdup(&tmp.efi, p);
f99fdc3e 131 else if (streq(field, "initrd"))
7e87c7d9 132 r = strv_extend(&tmp.initrd, p);
f99fdc3e 133 else if (streq(field, "devicetree"))
7e87c7d9 134 r = free_and_strdup(&tmp.device_tree, p);
fdc5c042
LP
135 else if (streq(field, "devicetree-overlay")) {
136 _cleanup_strv_free_ char **l = NULL;
137
138 l = strv_split(p, NULL);
139 if (!l)
140 return log_oom();
141
142 r = strv_extend_strv(&tmp.device_tree_overlay, l, false);
143 } else {
feb41f1f 144 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
7e87c7d9
ZJS
145 continue;
146 }
147 if (r < 0)
148 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
149 }
150
151 *entry = tmp;
152 tmp = (BootEntry) {};
153 return 0;
154}
155
156void boot_config_free(BootConfig *config) {
da6053d0 157 size_t i;
7e87c7d9 158
4fe2ba0e
LP
159 assert(config);
160
7e87c7d9
ZJS
161 free(config->default_pattern);
162 free(config->timeout);
163 free(config->editor);
c1d4e298
JJ
164 free(config->auto_entries);
165 free(config->auto_firmware);
72263375 166 free(config->console_mode);
fe5a698f 167 free(config->random_seed_mode);
d403d8f0 168 free(config->beep);
7e87c7d9
ZJS
169
170 free(config->entry_oneshot);
171 free(config->entry_default);
a78e472d 172 free(config->entry_selected);
7e87c7d9
ZJS
173
174 for (i = 0; i < config->n_entries; i++)
175 boot_entry_free(config->entries + i);
176 free(config->entries);
177}
178
0de2e1fd 179static int boot_loader_read_conf(const char *path, BootConfig *config) {
7e87c7d9
ZJS
180 _cleanup_fclose_ FILE *f = NULL;
181 unsigned line = 1;
182 int r;
183
4fe2ba0e
LP
184 assert(path);
185 assert(config);
186
7e87c7d9 187 f = fopen(path, "re");
f91ed3dc
LP
188 if (!f) {
189 if (errno == ENOENT)
190 return 0;
191
7e87c7d9 192 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
f91ed3dc 193 }
7e87c7d9
ZJS
194
195 for (;;) {
f99fdc3e
YW
196 _cleanup_free_ char *buf = NULL, *field = NULL;
197 const char *p;
7e87c7d9
ZJS
198
199 r = read_line(f, LONG_LINE_MAX, &buf);
200 if (r == 0)
201 break;
202 if (r == -ENOBUFS)
203 return log_error_errno(r, "%s:%u: Line too long", path, line);
204 if (r < 0)
205 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
206
207 line++;
208
209 if (IN_SET(*strstrip(buf), '#', '\0'))
210 continue;
211
f99fdc3e
YW
212 p = buf;
213 r = extract_first_word(&p, &field, " \t", 0);
214 if (r < 0) {
215 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
216 continue;
217 }
218 if (r == 0) {
7e87c7d9
ZJS
219 log_warning("%s:%u: Bad syntax", path, line);
220 continue;
221 }
7e87c7d9 222
f99fdc3e 223 if (streq(field, "default"))
7e87c7d9 224 r = free_and_strdup(&config->default_pattern, p);
f99fdc3e 225 else if (streq(field, "timeout"))
7e87c7d9 226 r = free_and_strdup(&config->timeout, p);
f99fdc3e 227 else if (streq(field, "editor"))
7e87c7d9 228 r = free_and_strdup(&config->editor, p);
790f84eb 229 else if (streq(field, "auto-entries"))
c1d4e298 230 r = free_and_strdup(&config->auto_entries, p);
790f84eb 231 else if (streq(field, "auto-firmware"))
c1d4e298 232 r = free_and_strdup(&config->auto_firmware, p);
790f84eb 233 else if (streq(field, "console-mode"))
d37b0737 234 r = free_and_strdup(&config->console_mode, p);
fe5a698f
YW
235 else if (streq(field, "random-seed-mode"))
236 r = free_and_strdup(&config->random_seed_mode, p);
d403d8f0
LP
237 else if (streq(field, "beep"))
238 r = free_and_strdup(&config->beep, p);
7e87c7d9 239 else {
feb41f1f 240 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
7e87c7d9
ZJS
241 continue;
242 }
243 if (r < 0)
244 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
245 }
246
f91ed3dc 247 return 1;
7e87c7d9
ZJS
248}
249
93bab288 250static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
20ec8f53
LP
251 int r;
252
253 assert(a);
254 assert(b);
255
256 r = CMP(!a->sort_key, !b->sort_key);
257 if (r != 0)
258 return r;
259 if (a->sort_key && b->sort_key) {
260 r = strcmp(a->sort_key, b->sort_key);
261 if (r != 0)
262 return r;
263
264 r = strcmp_ptr(a->machine_id, b->machine_id);
265 if (r != 0)
266 return r;
267
268 r = -strverscmp_improved(a->version, b->version);
269 if (r != 0)
270 return r;
271 }
272
8087644a 273 return strverscmp_improved(a->id, b->id);
7e87c7d9
ZJS
274}
275
43b736a8
LP
276static int boot_entries_find(
277 const char *root,
278 const char *dir,
a2f8664e
LP
279 BootEntry **entries,
280 size_t *n_entries) {
43b736a8 281
7e87c7d9 282 _cleanup_strv_free_ char **files = NULL;
7e87c7d9 283 int r;
7e87c7d9 284
43b736a8 285 assert(root);
4fe2ba0e 286 assert(dir);
a2f8664e
LP
287 assert(entries);
288 assert(n_entries);
4fe2ba0e 289
36b12282 290 r = conf_files_list(&files, ".conf", NULL, 0, dir);
7e87c7d9
ZJS
291 if (r < 0)
292 return log_error_errno(r, "Failed to list files in \"%s\": %m", dir);
293
294 STRV_FOREACH(f, files) {
319a4f4b 295 if (!GREEDY_REALLOC0(*entries, *n_entries + 1))
7e87c7d9
ZJS
296 return log_oom();
297
a2f8664e 298 r = boot_entry_load(root, *f, *entries + *n_entries);
7e87c7d9
ZJS
299 if (r < 0)
300 continue;
301
a2f8664e 302 (*n_entries) ++;
7e87c7d9
ZJS
303 }
304
7e87c7d9
ZJS
305 return 0;
306}
307
5e146a75
LP
308static int boot_entry_load_unified(
309 const char *root,
310 const char *path,
311 const char *osrelease,
312 const char *cmdline,
313 BootEntry *ret) {
314
c2caeb5d
LP
315 _cleanup_free_ char *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
316 *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
93f14ce2
LP
317 _cleanup_(boot_entry_free) BootEntry tmp = {
318 .type = BOOT_ENTRY_UNIFIED,
319 };
20ec8f53 320 const char *k, *good_name, *good_version, *good_sort_key;
5e146a75 321 _cleanup_fclose_ FILE *f = NULL;
5e146a75
LP
322 int r;
323
324 assert(root);
325 assert(path);
326 assert(osrelease);
327
328 k = path_startswith(path, root);
329 if (!k)
330 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
331
673a1e6f 332 f = fmemopen_unlocked((void*) osrelease, strlen(osrelease), "r");
5e146a75
LP
333 if (!f)
334 return log_error_errno(errno, "Failed to open os-release buffer: %m");
335
15b82eec
SM
336 r = parse_env_file(f, "os-release",
337 "PRETTY_NAME", &os_pretty_name,
c2caeb5d
LP
338 "IMAGE_ID", &os_image_id,
339 "NAME", &os_name,
15b82eec 340 "ID", &os_id,
c2caeb5d
LP
341 "IMAGE_VERSION", &os_image_version,
342 "VERSION", &os_version,
343 "VERSION_ID", &os_version_id,
344 "BUILD_ID", &os_build_id);
5e146a75
LP
345 if (r < 0)
346 return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
347
20ec8f53 348 if (!bootspec_pick_name_version_sort_key(
c2caeb5d
LP
349 os_pretty_name,
350 os_image_id,
351 os_name,
352 os_id,
353 os_image_version,
354 os_version,
355 os_version_id,
356 os_build_id,
357 &good_name,
20ec8f53
LP
358 &good_version,
359 &good_sort_key))
5e146a75
LP
360 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
361
c2caeb5d
LP
362 r = path_extract_filename(path, &tmp.id);
363 if (r < 0)
364 return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
5e146a75 365
eed7210a 366 if (!efi_loader_entry_name_valid(tmp.id))
dfc22cb4 367 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
eed7210a 368
c2caeb5d
LP
369 if (os_id && os_version_id) {
370 tmp.id_old = strjoin(os_id, "-", os_version_id);
371 if (!tmp.id_old)
372 return log_oom();
373 }
374
5e146a75
LP
375 tmp.path = strdup(path);
376 if (!tmp.path)
377 return log_oom();
378
379 tmp.root = strdup(root);
380 if (!tmp.root)
381 return log_oom();
382
383 tmp.kernel = strdup(skip_leading_chars(k, "/"));
384 if (!tmp.kernel)
385 return log_oom();
386
387 tmp.options = strv_new(skip_leading_chars(cmdline, WHITESPACE));
388 if (!tmp.options)
389 return log_oom();
390
391 delete_trailing_chars(tmp.options[0], WHITESPACE);
392
c2caeb5d
LP
393 tmp.title = strdup(good_name);
394 if (!tmp.title)
395 return log_oom();
396
20ec8f53
LP
397 tmp.sort_key = strdup(good_sort_key);
398 if (!tmp.sort_key)
399 return log_oom();
400
c2caeb5d
LP
401 tmp.version = strdup(good_version);
402 if (!tmp.version)
403 return log_oom();
5e146a75
LP
404
405 *ret = tmp;
406 tmp = (BootEntry) {};
407 return 0;
408}
409
410/* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
411 * the ones we do care about and we are willing to load into memory have this size limit.) */
412#define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
413
414static int find_sections(
415 int fd,
416 char **ret_osrelease,
417 char **ret_cmdline) {
418
419 _cleanup_free_ struct PeSectionHeader *sections = NULL;
420 _cleanup_free_ char *osrelease = NULL, *cmdline = NULL;
421 size_t i, n_sections;
422 struct DosFileHeader dos;
423 struct PeHeader pe;
424 uint64_t start;
425 ssize_t n;
426
427 n = pread(fd, &dos, sizeof(dos), 0);
428 if (n < 0)
429 return log_error_errno(errno, "Failed read DOS header: %m");
430 if (n != sizeof(dos))
431 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading DOS header, refusing.");
432
433 if (dos.Magic[0] != 'M' || dos.Magic[1] != 'Z')
434 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "DOS executable magic missing, refusing.");
435
436 start = unaligned_read_le32(&dos.ExeHeader);
437 n = pread(fd, &pe, sizeof(pe), start);
438 if (n < 0)
439 return log_error_errno(errno, "Failed to read PE header: %m");
440 if (n != sizeof(pe))
441 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading PE header, refusing.");
442
443 if (pe.Magic[0] != 'P' || pe.Magic[1] != 'E' || pe.Magic[2] != 0 || pe.Magic[3] != 0)
444 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PE executable magic missing, refusing.");
445
446 n_sections = unaligned_read_le16(&pe.FileHeader.NumberOfSections);
447 if (n_sections > 96)
448 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PE header has too many sections, refusing.");
449
450 sections = new(struct PeSectionHeader, n_sections);
451 if (!sections)
452 return log_oom();
453
454 n = pread(fd, sections,
455 n_sections * sizeof(struct PeSectionHeader),
456 start + sizeof(pe) + unaligned_read_le16(&pe.FileHeader.SizeOfOptionalHeader));
457 if (n < 0)
458 return log_error_errno(errno, "Failed to read section data: %m");
459 if ((size_t) n != n_sections * sizeof(struct PeSectionHeader))
460 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading sections, refusing.");
461
462 for (i = 0; i < n_sections; i++) {
463 _cleanup_free_ char *k = NULL;
464 uint32_t offset, size;
465 char **b;
466
467 if (strneq((char*) sections[i].Name, ".osrel", sizeof(sections[i].Name)))
468 b = &osrelease;
469 else if (strneq((char*) sections[i].Name, ".cmdline", sizeof(sections[i].Name)))
470 b = &cmdline;
471 else
472 continue;
473
474 if (*b)
475 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section %s, refusing.", sections[i].Name);
476
477 offset = unaligned_read_le32(&sections[i].PointerToRawData);
478 size = unaligned_read_le32(&sections[i].VirtualSize);
479
480 if (size > PE_SECTION_SIZE_MAX)
481 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Section %s too large, refusing.", sections[i].Name);
482
483 k = new(char, size+1);
484 if (!k)
485 return log_oom();
486
487 n = pread(fd, k, size, offset);
488 if (n < 0)
489 return log_error_errno(errno, "Failed to read section payload: %m");
a75fcef8 490 if ((size_t) n != size)
5e146a75
LP
491 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading section payload, refusing:");
492
493 /* Allow one trailing NUL byte, but nothing more. */
494 if (size > 0 && memchr(k, 0, size - 1))
495 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Section contains embedded NUL byte: %m");
496
497 k[size] = 0;
498 *b = TAKE_PTR(k);
499 }
500
501 if (!osrelease)
502 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Image lacks .osrel section, refusing.");
503
504 if (ret_osrelease)
505 *ret_osrelease = TAKE_PTR(osrelease);
506 if (ret_cmdline)
507 *ret_cmdline = TAKE_PTR(cmdline);
508
509 return 0;
510}
511
512static int boot_entries_find_unified(
513 const char *root,
514 const char *dir,
515 BootEntry **entries,
516 size_t *n_entries) {
517
518 _cleanup_(closedirp) DIR *d = NULL;
5e146a75
LP
519 int r;
520
521 assert(root);
522 assert(dir);
523 assert(entries);
524 assert(n_entries);
525
526 d = opendir(dir);
527 if (!d) {
528 if (errno == ENOENT)
529 return 0;
530
531 return log_error_errno(errno, "Failed to open %s: %m", dir);
532 }
533
534 FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", dir)) {
535 _cleanup_free_ char *j = NULL, *osrelease = NULL, *cmdline = NULL;
536 _cleanup_close_ int fd = -1;
537
538 if (!dirent_is_file(de))
539 continue;
540
541 if (!endswith_no_case(de->d_name, ".efi"))
542 continue;
543
319a4f4b 544 if (!GREEDY_REALLOC0(*entries, *n_entries + 1))
5e146a75
LP
545 return log_oom();
546
547 fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
548 if (fd < 0) {
549 log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", dir, de->d_name);
550 continue;
551 }
552
553 r = fd_verify_regular(fd);
554 if (r < 0) {
dba33c4a 555 log_warning_errno(r, "File %s/%s is not regular, ignoring: %m", dir, de->d_name);
5e146a75
LP
556 continue;
557 }
558
559 r = find_sections(fd, &osrelease, &cmdline);
560 if (r < 0)
561 continue;
562
563 j = path_join(dir, de->d_name);
564 if (!j)
565 return log_oom();
566
567 r = boot_entry_load_unified(root, j, osrelease, cmdline, *entries + *n_entries);
568 if (r < 0)
569 continue;
570
571 (*n_entries) ++;
5e146a75
LP
572 }
573
5e146a75
LP
574 return 0;
575}
576
bb682057 577static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
64f05708
ZJS
578 bool non_unique = false;
579
4fe2ba0e
LP
580 assert(entries || n_entries == 0);
581 assert(arr || n_entries == 0);
582
d5ac1d4e 583 for (size_t i = 0; i < n_entries; i++)
64f05708
ZJS
584 arr[i] = false;
585
d5ac1d4e
LP
586 for (size_t i = 0; i < n_entries; i++)
587 for (size_t j = 0; j < n_entries; j++)
64f05708
ZJS
588 if (i != j && streq(boot_entry_title(entries + i),
589 boot_entry_title(entries + j)))
590 non_unique = arr[i] = arr[j] = true;
591
592 return non_unique;
593}
594
595static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
d5ac1d4e 596 _cleanup_free_ bool *arr = NULL;
64f05708 597 char *s;
64f05708 598
4fe2ba0e
LP
599 assert(entries || n_entries == 0);
600
d5ac1d4e
LP
601 if (n_entries == 0)
602 return 0;
603
604 arr = new(bool, n_entries);
605 if (!arr)
606 return -ENOMEM;
607
64f05708
ZJS
608 /* Find _all_ non-unique titles */
609 if (!find_nonunique(entries, n_entries, arr))
610 return 0;
611
612 /* Add version to non-unique titles */
d5ac1d4e 613 for (size_t i = 0; i < n_entries; i++)
64f05708 614 if (arr[i] && entries[i].version) {
d5ac1d4e 615 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version) < 0)
64f05708
ZJS
616 return -ENOMEM;
617
618 free_and_replace(entries[i].show_title, s);
619 }
620
621 if (!find_nonunique(entries, n_entries, arr))
622 return 0;
623
624 /* Add machine-id to non-unique titles */
d5ac1d4e 625 for (size_t i = 0; i < n_entries; i++)
64f05708 626 if (arr[i] && entries[i].machine_id) {
d5ac1d4e 627 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id) < 0)
64f05708
ZJS
628 return -ENOMEM;
629
630 free_and_replace(entries[i].show_title, s);
631 }
632
633 if (!find_nonunique(entries, n_entries, arr))
634 return 0;
635
636 /* Add file name to non-unique titles */
d5ac1d4e 637 for (size_t i = 0; i < n_entries; i++)
64f05708 638 if (arr[i]) {
d5ac1d4e 639 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id) < 0)
64f05708
ZJS
640 return -ENOMEM;
641
642 free_and_replace(entries[i].show_title, s);
643 }
644
645 return 0;
646}
647
20ec8f53
LP
648static int boot_config_find(const BootConfig *config, const char *id) {
649 assert(config);
650
651 if (!id)
652 return -1;
653
654 for (size_t i = 0; i < config->n_entries; i++)
655 if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
656 return i;
657
658 return -1;
659}
660
ad1afd60 661static int boot_entries_select_default(const BootConfig *config) {
7e87c7d9
ZJS
662 int i;
663
4fe2ba0e 664 assert(config);
388d2993
ZJS
665 assert(config->entries || config->n_entries == 0);
666
667 if (config->n_entries == 0) {
668 log_debug("Found no default boot entry :(");
669 return -1; /* -1 means "no default" */
670 }
4fe2ba0e 671
20ec8f53
LP
672 if (config->entry_oneshot) {
673 i = boot_config_find(config, config->entry_oneshot);
674 if (i >= 0) {
675 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
676 config->entries[i].id);
677 return i;
678 }
679 }
680
681 if (config->entry_default) {
682 i = boot_config_find(config, config->entry_default);
683 if (i >= 0) {
684 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
685 config->entries[i].id);
686 return i;
687 }
688 }
689
690 if (config->default_pattern) {
691 i = boot_config_find(config, config->default_pattern);
692 if (i >= 0) {
693 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
694 config->entries[i].id, config->default_pattern);
695 return i;
696 }
697 }
698
699 log_debug("Found default: first entry \"%s\"", config->entries[0].id);
700 return 0;
7e87c7d9
ZJS
701}
702
a78e472d 703static int boot_entries_select_selected(const BootConfig *config) {
a78e472d
LP
704 assert(config);
705 assert(config->entries || config->n_entries == 0);
706
707 if (!config->entry_selected || config->n_entries == 0)
708 return -1;
709
20ec8f53 710 return boot_config_find(config, config->entry_selected);
a78e472d
LP
711}
712
713static int boot_load_efi_entry_pointers(BootConfig *config) {
714 int r;
715
716 assert(config);
717
718 if (!is_efi_boot())
719 return 0;
720
721 /* Loads the three "pointers" to boot loader entries from their EFI variables */
722
723 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot), &config->entry_oneshot);
724 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) {
725 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\": %m");
726 if (r == -ENOMEM)
727 return r;
728 }
729
730 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryDefault), &config->entry_default);
731 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) {
732 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\": %m");
733 if (r == -ENOMEM)
734 return r;
735 }
736
737 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntrySelected), &config->entry_selected);
738 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) {
739 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\": %m");
740 if (r == -ENOMEM)
741 return r;
742 }
743
744 return 1;
745}
746
a2f8664e
LP
747int boot_entries_load_config(
748 const char *esp_path,
749 const char *xbootldr_path,
750 BootConfig *config) {
751
7e87c7d9
ZJS
752 const char *p;
753 int r;
754
4fe2ba0e
LP
755 assert(config);
756
a2f8664e
LP
757 if (esp_path) {
758 p = strjoina(esp_path, "/loader/loader.conf");
759 r = boot_loader_read_conf(p, config);
760 if (r < 0)
761 return r;
7e87c7d9 762
a2f8664e
LP
763 p = strjoina(esp_path, "/loader/entries");
764 r = boot_entries_find(esp_path, p, &config->entries, &config->n_entries);
765 if (r < 0)
766 return r;
5e146a75
LP
767
768 p = strjoina(esp_path, "/EFI/Linux/");
769 r = boot_entries_find_unified(esp_path, p, &config->entries, &config->n_entries);
770 if (r < 0)
771 return r;
a2f8664e
LP
772 }
773
774 if (xbootldr_path) {
775 p = strjoina(xbootldr_path, "/loader/entries");
776 r = boot_entries_find(xbootldr_path, p, &config->entries, &config->n_entries);
777 if (r < 0)
778 return r;
5e146a75
LP
779
780 p = strjoina(xbootldr_path, "/EFI/Linux/");
781 r = boot_entries_find_unified(xbootldr_path, p, &config->entries, &config->n_entries);
782 if (r < 0)
783 return r;
a2f8664e 784 }
7e87c7d9 785
dd2bf34c
LP
786 typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
787
64f05708
ZJS
788 r = boot_entries_uniquify(config->entries, config->n_entries);
789 if (r < 0)
790 return log_error_errno(r, "Failed to uniquify boot entries: %m");
791
a78e472d
LP
792 r = boot_load_efi_entry_pointers(config);
793 if (r < 0)
794 return r;
7e87c7d9
ZJS
795
796 config->default_entry = boot_entries_select_default(config);
a78e472d
LP
797 config->selected_entry = boot_entries_select_selected(config);
798
7e87c7d9
ZJS
799 return 0;
800}
af918182 801
eea4ce1e
LP
802int boot_entries_load_config_auto(
803 const char *override_esp_path,
804 const char *override_xbootldr_path,
805 BootConfig *config) {
806
807 _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
f63b5ad9 808 dev_t esp_devid = 0, xbootldr_devid = 0;
eea4ce1e
LP
809 int r;
810
811 assert(config);
812
813 /* This function is similar to boot_entries_load_config(), however we automatically search for the
814 * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
815 * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
816 * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
817 * it). This allows other boot loaders to pass boot loader entry information to our tools if they
818 * want to. */
819
820 if (!override_esp_path && !override_xbootldr_path) {
f40999f8
ZJS
821 if (access("/run/boot-loader-entries/", F_OK) >= 0)
822 return boot_entries_load_config("/run/boot-loader-entries/", NULL, config);
eea4ce1e 823
f40999f8
ZJS
824 if (errno != ENOENT)
825 return log_error_errno(errno,
826 "Failed to determine whether /run/boot-loader-entries/ exists: %m");
eea4ce1e
LP
827 }
828
f63b5ad9 829 r = find_esp_and_warn(override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid);
cc5957dc 830 if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
eea4ce1e
LP
831 return r;
832
f63b5ad9 833 r = find_xbootldr_and_warn(override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid);
eea4ce1e
LP
834 if (r < 0 && r != -ENOKEY)
835 return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
836
f63b5ad9
LP
837 /* If both paths actually refer to the same inode, suppress the xbootldr path */
838 if (esp_where && xbootldr_where && devid_set_and_equal(esp_devid, xbootldr_devid))
839 xbootldr_where = mfree(xbootldr_where);
840
f40999f8 841 return boot_entries_load_config(esp_where, xbootldr_where, config);
eea4ce1e
LP
842}
843
d4bd786d
LP
844int boot_entries_augment_from_loader(
845 BootConfig *config,
9951736b
LP
846 char **found_by_loader,
847 bool only_auto) {
d4bd786d
LP
848
849 static const char *const title_table[] = {
93f14ce2
LP
850 /* Pretty names for a few well-known automatically discovered entries. */
851 "auto-osx", "macOS",
852 "auto-windows", "Windows Boot Manager",
853 "auto-efi-shell", "EFI Shell",
854 "auto-efi-default", "EFI Default Loader",
855 "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
856 };
857
93f14ce2
LP
858 assert(config);
859
860 /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
861 * already included there. */
862
93f14ce2 863 STRV_FOREACH(i, found_by_loader) {
bb682057 864 BootEntry *existing;
ce4c4f81 865 _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
93f14ce2 866
bb682057
LP
867 existing = boot_config_find_entry(config, *i);
868 if (existing) {
869 existing->reported_by_loader = true;
93f14ce2 870 continue;
bb682057 871 }
93f14ce2 872
9951736b 873 if (only_auto && !startswith(*i, "auto-"))
93f14ce2
LP
874 continue;
875
876 c = strdup(*i);
877 if (!c)
878 return log_oom();
879
880 STRV_FOREACH_PAIR(a, b, (char**) title_table)
881 if (streq(*a, *i)) {
882 t = strdup(*b);
883 if (!t)
884 return log_oom();
885 break;
886 }
887
e6f055cb 888 p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderEntries)));
ce4c4f81
ZJS
889 if (!p)
890 return log_oom();
891
319a4f4b 892 if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
93f14ce2
LP
893 return log_oom();
894
895 config->entries[config->n_entries++] = (BootEntry) {
bb682057 896 .type = startswith(*i, "auto-") ? BOOT_ENTRY_LOADER_AUTO : BOOT_ENTRY_LOADER,
93f14ce2
LP
897 .id = TAKE_PTR(c),
898 .title = TAKE_PTR(t),
ce4c4f81 899 .path = TAKE_PTR(p),
bb682057 900 .reported_by_loader = true,
93f14ce2
LP
901 };
902 }
903
904 return 0;
905}