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