]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bootspec.c
bac00c5b3caed2c9eb9c3f277e621d6fe1893c1b
[thirdparty/systemd.git] / src / shared / bootspec.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fnmatch.h>
4 #include <unistd.h>
5
6 #include "sd-json.h"
7
8 #include "alloc-util.h"
9 #include "bootspec.h"
10 #include "bootspec-fundamental.h"
11 #include "chase.h"
12 #include "devnum-util.h"
13 #include "dirent-util.h"
14 #include "efi-loader.h"
15 #include "efivars.h"
16 #include "env-file.h"
17 #include "extract-word.h"
18 #include "fd-util.h"
19 #include "fileio.h"
20 #include "find-esp.h"
21 #include "log.h"
22 #include "parse-util.h"
23 #include "path-util.h"
24 #include "pe-binary.h"
25 #include "pretty-print.h"
26 #include "recurse-dir.h"
27 #include "set.h"
28 #include "sort-util.h"
29 #include "stat-util.h"
30 #include "string-table.h"
31 #include "string-util.h"
32 #include "strv.h"
33 #include "uki.h"
34
35 static const char* const boot_entry_type_table[_BOOT_ENTRY_TYPE_MAX] = {
36 [BOOT_ENTRY_CONF] = "Boot Loader Specification Type #1 (.conf)",
37 [BOOT_ENTRY_UNIFIED] = "Boot Loader Specification Type #2 (.efi)",
38 [BOOT_ENTRY_LOADER] = "Reported by Boot Loader",
39 [BOOT_ENTRY_LOADER_AUTO] = "Automatic",
40 };
41
42 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type, BootEntryType);
43
44 static const char* const boot_entry_type_json_table[_BOOT_ENTRY_TYPE_MAX] = {
45 [BOOT_ENTRY_CONF] = "type1",
46 [BOOT_ENTRY_UNIFIED] = "type2",
47 [BOOT_ENTRY_LOADER] = "loader",
48 [BOOT_ENTRY_LOADER_AUTO] = "auto",
49 };
50
51 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_json, BootEntryType);
52
53 static const char* const boot_entry_source_table[_BOOT_ENTRY_SOURCE_MAX] = {
54 [BOOT_ENTRY_ESP] = "EFI System Partition",
55 [BOOT_ENTRY_XBOOTLDR] = "Extended Boot Loader Partition",
56 };
57
58 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source, BootEntrySource);
59
60 static const char* const boot_entry_source_json_table[_BOOT_ENTRY_SOURCE_MAX] = {
61 [BOOT_ENTRY_ESP] = "esp",
62 [BOOT_ENTRY_XBOOTLDR] = "xbootldr",
63 };
64
65 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source_json, BootEntrySource);
66
67 static void boot_entry_addons_done(BootEntryAddons *addons) {
68 assert(addons);
69
70 FOREACH_ARRAY(addon, addons->items, addons->n_items) {
71 free(addon->cmdline);
72 free(addon->location);
73 }
74 addons->items = mfree(addons->items);
75 addons->n_items = 0;
76 }
77
78 static void boot_entry_free(BootEntry *entry) {
79 assert(entry);
80
81 free(entry->id);
82 free(entry->id_old);
83 free(entry->id_without_profile);
84 free(entry->path);
85 free(entry->root);
86 free(entry->title);
87 free(entry->show_title);
88 free(entry->sort_key);
89 free(entry->version);
90 free(entry->machine_id);
91 free(entry->architecture);
92 strv_free(entry->options);
93 boot_entry_addons_done(&entry->local_addons);
94 free(entry->kernel);
95 free(entry->efi);
96 strv_free(entry->initrd);
97 free(entry->device_tree);
98 strv_free(entry->device_tree_overlay);
99 }
100
101 static int mangle_path(
102 const char *fname,
103 unsigned line,
104 const char *field,
105 const char *p,
106 char **ret) {
107
108 _cleanup_free_ char *c = NULL;
109
110 assert(field);
111 assert(p);
112 assert(ret);
113
114 /* Spec leaves open if prefixed with "/" or not, let's normalize that */
115 c = path_make_absolute(p, "/");
116 if (!c)
117 return -ENOMEM;
118
119 /* We only reference files, never directories */
120 if (endswith(c, "/")) {
121 log_syntax(NULL, LOG_WARNING, fname, line, 0, "Path in field '%s' has trailing slash, ignoring: %s", field, c);
122 *ret = NULL;
123 return 0;
124 }
125
126 /* Remove duplicate "/" */
127 path_simplify(c);
128
129 /* No ".." or "." or so */
130 if (!path_is_normalized(c)) {
131 log_syntax(NULL, LOG_WARNING, fname, line, 0, "Path in field '%s' is not normalized, ignoring: %s", field, c);
132 *ret = NULL;
133 return 0;
134 }
135
136 *ret = TAKE_PTR(c);
137 return 1;
138 }
139
140 static int parse_path_one(
141 const char *fname,
142 unsigned line,
143 const char *field,
144 char **s,
145 const char *p) {
146
147 _cleanup_free_ char *c = NULL;
148 int r;
149
150 assert(field);
151 assert(s);
152 assert(p);
153
154 r = mangle_path(fname, line, field, p, &c);
155 if (r <= 0)
156 return r;
157
158 return free_and_replace(*s, c);
159 }
160
161 static int parse_path_strv(
162 const char *fname,
163 unsigned line,
164 const char *field,
165 char ***s,
166 const char *p) {
167
168 char *c;
169 int r;
170
171 assert(field);
172 assert(s);
173 assert(p);
174
175 r = mangle_path(fname, line, field, p, &c);
176 if (r <= 0)
177 return r;
178
179 return strv_consume(s, c);
180 }
181
182 static int parse_path_many(
183 const char *fname,
184 unsigned line,
185 const char *field,
186 char ***s,
187 const char *p) {
188
189 _cleanup_strv_free_ char **l = NULL, **f = NULL;
190 int r;
191
192 l = strv_split(p, NULL);
193 if (!l)
194 return -ENOMEM;
195
196 STRV_FOREACH(i, l) {
197 char *c;
198
199 r = mangle_path(fname, line, field, *i, &c);
200 if (r < 0)
201 return r;
202 if (r == 0)
203 continue;
204
205 r = strv_consume(&f, c);
206 if (r < 0)
207 return r;
208 }
209
210 return strv_extend_strv_consume(s, TAKE_PTR(f), /* filter_duplicates= */ false);
211 }
212
213 static int parse_tries(const char *fname, const char **p, unsigned *ret) {
214 _cleanup_free_ char *d = NULL;
215 unsigned tries;
216 size_t n;
217 int r;
218
219 assert(fname);
220 assert(p);
221 assert(*p);
222 assert(ret);
223
224 n = strspn(*p, DIGITS);
225 if (n == 0) {
226 *ret = UINT_MAX;
227 return 0;
228 }
229
230 d = strndup(*p, n);
231 if (!d)
232 return log_oom();
233
234 r = safe_atou_full(d, 10, &tries);
235 if (r >= 0 && tries > INT_MAX) /* sd-boot allows INT_MAX, let's use the same limit */
236 r = -ERANGE;
237 if (r < 0)
238 return log_error_errno(r, "Failed to parse tries counter of filename '%s': %m", fname);
239
240 *p = *p + n;
241 *ret = tries;
242 return 1;
243 }
244
245 int boot_filename_extract_tries(
246 const char *fname,
247 char **ret_stripped,
248 unsigned *ret_tries_left,
249 unsigned *ret_tries_done) {
250
251 unsigned tries_left = UINT_MAX, tries_done = UINT_MAX;
252 _cleanup_free_ char *stripped = NULL;
253 const char *p, *suffix, *m;
254 int r;
255
256 assert(fname);
257 assert(ret_stripped);
258 assert(ret_tries_left);
259 assert(ret_tries_done);
260
261 /* Be liberal with suffix, only insist on a dot. After all we want to cover any capitalization here
262 * (vfat is case insensitive after all), and at least .efi and .conf as suffix. */
263 suffix = strrchr(fname, '.');
264 if (!suffix)
265 goto nothing;
266
267 p = m = memrchr(fname, '+', suffix - fname);
268 if (!p)
269 goto nothing;
270 p++;
271
272 r = parse_tries(fname, &p, &tries_left);
273 if (r < 0)
274 return r;
275 if (r == 0)
276 goto nothing;
277
278 if (*p == '-') {
279 p++;
280
281 r = parse_tries(fname, &p, &tries_done);
282 if (r < 0)
283 return r;
284 if (r == 0)
285 goto nothing;
286 }
287
288 if (p != suffix)
289 goto nothing;
290
291 stripped = strndup(fname, m - fname);
292 if (!stripped)
293 return log_oom();
294
295 if (!strextend(&stripped, suffix))
296 return log_oom();
297
298 *ret_stripped = TAKE_PTR(stripped);
299 *ret_tries_left = tries_left;
300 *ret_tries_done = tries_done;
301
302 return 0;
303
304 nothing:
305 stripped = strdup(fname);
306 if (!stripped)
307 return log_oom();
308
309 *ret_stripped = TAKE_PTR(stripped);
310 *ret_tries_left = *ret_tries_done = UINT_MAX;
311 return 0;
312 }
313
314 static int boot_entry_load_type1(
315 FILE *f,
316 const char *root,
317 const BootEntrySource source,
318 const char *dir,
319 const char *fname,
320 BootEntry *ret) {
321
322 _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_CONF, source);
323 char *c;
324 int r;
325
326 assert(f);
327 assert(root);
328 assert(dir);
329 assert(fname);
330 assert(ret);
331
332 /* Loads a Type #1 boot menu entry from the specified FILE* object */
333
334 r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
335 if (r < 0)
336 return r;
337
338 if (!efi_loader_entry_name_valid(tmp.id))
339 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", fname);
340
341 c = endswith_no_case(tmp.id, ".conf");
342 if (!c)
343 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", fname);
344
345 tmp.id_old = strndup(tmp.id, c - tmp.id); /* Without .conf suffix */
346 if (!tmp.id_old)
347 return log_oom();
348
349 tmp.path = path_join(dir, fname);
350 if (!tmp.path)
351 return log_oom();
352
353 tmp.root = strdup(root);
354 if (!tmp.root)
355 return log_oom();
356
357 for (unsigned line = 1;; line++) {
358 _cleanup_free_ char *buf = NULL, *field = NULL;
359
360 r = read_stripped_line(f, LONG_LINE_MAX, &buf);
361 if (r == -ENOBUFS)
362 return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Line too long.");
363 if (r < 0)
364 return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while reading: %m");
365 if (r == 0)
366 break;
367
368 if (IN_SET(buf[0], '#', '\0'))
369 continue;
370
371 const char *p = buf;
372 r = extract_first_word(&p, &field, NULL, 0);
373 if (r < 0) {
374 log_syntax(NULL, LOG_WARNING, tmp.path, line, r, "Failed to parse, ignoring line: %m");
375 continue;
376 }
377 if (r == 0) {
378 log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Bad syntax, ignoring line.");
379 continue;
380 }
381
382 if (isempty(p)) {
383 /* Some fields can reasonably have an empty value. In other cases warn. */
384 if (!STR_IN_SET(field, "options", "devicetree-overlay"))
385 log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Field '%s' without value, ignoring line.", field);
386
387 continue;
388 }
389
390 if (streq(field, "title"))
391 r = free_and_strdup(&tmp.title, p);
392 else if (streq(field, "sort-key"))
393 r = free_and_strdup(&tmp.sort_key, p);
394 else if (streq(field, "version"))
395 r = free_and_strdup(&tmp.version, p);
396 else if (streq(field, "machine-id"))
397 r = free_and_strdup(&tmp.machine_id, p);
398 else if (streq(field, "architecture"))
399 r = free_and_strdup(&tmp.architecture, p);
400 else if (streq(field, "options"))
401 r = strv_extend(&tmp.options, p);
402 else if (streq(field, "linux"))
403 r = parse_path_one(tmp.path, line, field, &tmp.kernel, p);
404 else if (streq(field, "efi"))
405 r = parse_path_one(tmp.path, line, field, &tmp.efi, p);
406 else if (streq(field, "initrd"))
407 r = parse_path_strv(tmp.path, line, field, &tmp.initrd, p);
408 else if (streq(field, "devicetree"))
409 r = parse_path_one(tmp.path, line, field, &tmp.device_tree, p);
410 else if (streq(field, "devicetree-overlay"))
411 r = parse_path_many(tmp.path, line, field, &tmp.device_tree_overlay, p);
412 else {
413 log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Unknown line '%s', ignoring.", field);
414 continue;
415 }
416 if (r < 0)
417 return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while parsing: %m");
418 }
419
420 *ret = TAKE_STRUCT(tmp);
421 return 0;
422 }
423
424 int boot_config_load_type1(
425 BootConfig *config,
426 FILE *f,
427 const char *root,
428 const BootEntrySource source,
429 const char *dir,
430 const char *fname) {
431 int r;
432
433 assert(config);
434 assert(f);
435 assert(root);
436 assert(dir);
437 assert(fname);
438
439 if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
440 return log_oom();
441
442 BootEntry *entry = config->entries + config->n_entries;
443
444 r = boot_entry_load_type1(f, root, source, dir, fname, entry);
445 if (r < 0)
446 return r;
447 config->n_entries++;
448
449 entry->global_addons = &config->global_addons[source];
450
451 return 0;
452 }
453
454 void boot_config_free(BootConfig *config) {
455 assert(config);
456
457 free(config->default_pattern);
458
459 free(config->entry_oneshot);
460 free(config->entry_default);
461 free(config->entry_selected);
462 free(config->entry_sysfail);
463
464 FOREACH_ARRAY(i, config->entries, config->n_entries)
465 boot_entry_free(i);
466 free(config->entries);
467
468 FOREACH_ELEMENT(i, config->global_addons)
469 boot_entry_addons_done(i);
470
471 set_free(config->inodes_seen);
472 }
473
474 int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
475 int r;
476
477 assert(config);
478 assert(file);
479 assert(path);
480
481 for (unsigned line = 1;; line++) {
482 _cleanup_free_ char *buf = NULL, *field = NULL;
483
484 r = read_stripped_line(file, LONG_LINE_MAX, &buf);
485 if (r == -ENOBUFS)
486 return log_syntax(NULL, LOG_ERR, path, line, r, "Line too long.");
487 if (r < 0)
488 return log_syntax(NULL, LOG_ERR, path, line, r, "Error while reading: %m");
489 if (r == 0)
490 break;
491
492 if (IN_SET(buf[0], '#', '\0'))
493 continue;
494
495 const char *p = buf;
496 r = extract_first_word(&p, &field, NULL, 0);
497 if (r < 0) {
498 log_syntax(NULL, LOG_WARNING, path, line, r, "Failed to parse, ignoring line: %m");
499 continue;
500 }
501 if (r == 0) {
502 log_syntax(NULL, LOG_WARNING, path, line, 0, "Bad syntax, ignoring line.");
503 continue;
504 }
505 if (isempty(p)) {
506 log_syntax(NULL, LOG_WARNING, path, line, 0, "Field '%s' without value, ignoring line.", field);
507 continue;
508 }
509
510 if (streq(field, "default"))
511 r = free_and_strdup(&config->default_pattern, p);
512 else if (STR_IN_SET(field, "timeout", "editor", "auto-entries", "auto-firmware",
513 "auto-poweroff", "auto-reboot", "beep", "reboot-for-bitlocker",
514 "secure-boot-enroll", "console-mode"))
515 r = 0; /* we don't parse these in userspace, but they are OK */
516 else {
517 log_syntax(NULL, LOG_WARNING, path, line, 0, "Unknown line '%s', ignoring.", field);
518 continue;
519 }
520 if (r < 0)
521 return log_syntax(NULL, LOG_ERR, path, line, r, "Error while parsing: %m");
522 }
523
524 return 1;
525 }
526
527 static int boot_loader_read_conf_path(BootConfig *config, const char *root, const char *path) {
528 _cleanup_free_ char *full = NULL;
529 _cleanup_fclose_ FILE *f = NULL;
530 int r;
531
532 assert(config);
533 assert(path);
534
535 r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, "re", &full, &f);
536 if (r == -ENOENT)
537 return 0;
538 if (r < 0)
539 return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(path));
540
541 return boot_loader_read_conf(config, f, full);
542 }
543
544 static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
545 int r;
546
547 assert(a);
548 assert(b);
549
550 /* This mimics a function of the same name in src/boot/efi/sd-boot.c */
551
552 r = CMP(a->tries_left == 0, b->tries_left == 0);
553 if (r != 0)
554 return r;
555
556 r = CMP(!a->sort_key, !b->sort_key);
557 if (r != 0)
558 return r;
559
560 if (a->sort_key && b->sort_key) {
561 r = strcmp(a->sort_key, b->sort_key);
562 if (r != 0)
563 return r;
564
565 r = strcmp_ptr(a->machine_id, b->machine_id);
566 if (r != 0)
567 return r;
568
569 r = -strverscmp_improved(a->version, b->version);
570 if (r != 0)
571 return r;
572 }
573
574 r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id);
575 if (r != 0)
576 return r;
577
578 if (a->id_without_profile && b->id_without_profile) {
579 /* The strverscmp_improved() call above already established that we are talking about the
580 * same image here, hence order by profile, if there is one */
581 r = CMP(a->profile, b->profile);
582 if (r != 0)
583 return r;
584 }
585
586 if (a->tries_left != UINT_MAX || b->tries_left != UINT_MAX)
587 return 0;
588
589 r = -CMP(a->tries_left, b->tries_left);
590 if (r != 0)
591 return r;
592
593 return CMP(a->tries_done, b->tries_done);
594 }
595
596 static int config_check_inode_relevant_and_unseen(BootConfig *config, int fd, const char *fname) {
597 _cleanup_free_ char *d = NULL;
598 struct stat st;
599
600 assert(config);
601 assert(fd >= 0);
602 assert(fname);
603
604 /* So, here's the thing: because of the mess around /efi/ vs. /boot/ vs. /boot/efi/ it might be that
605 * people have these dirs, or subdirs of them symlinked or bind mounted, and we might end up
606 * iterating though some dirs multiple times. Let's thus rather be safe than sorry, and track the
607 * inodes we already processed: let's ignore inodes we have seen already. This should be robust
608 * against any form of symlinking or bind mounting, and effectively suppress any such duplicates. */
609
610 if (fstat(fd, &st) < 0)
611 return log_error_errno(errno, "Failed to stat('%s'): %m", fname);
612 if (!S_ISREG(st.st_mode)) {
613 log_debug("File '%s' is not a regular file, ignoring.", fname);
614 return false;
615 }
616
617 if (set_contains(config->inodes_seen, &st)) {
618 log_debug("Inode '%s' already seen before, ignoring.", fname);
619 return false;
620 }
621
622 d = memdup(&st, sizeof(st));
623 if (!d)
624 return log_oom();
625
626 if (set_ensure_consume(&config->inodes_seen, &inode_hash_ops, TAKE_PTR(d)) < 0)
627 return log_oom();
628
629 return true;
630 }
631
632 static int boot_entries_find_type1(
633 BootConfig *config,
634 const char *root,
635 const BootEntrySource source,
636 const char *dir) {
637
638 _cleanup_free_ DirectoryEntries *dentries = NULL;
639 _cleanup_free_ char *full = NULL;
640 _cleanup_close_ int dir_fd = -EBADF;
641 int r;
642
643 assert(config);
644 assert(root);
645 assert(dir);
646
647 dir_fd = chase_and_open(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, &full);
648 if (dir_fd == -ENOENT)
649 return 0;
650 if (dir_fd < 0)
651 return log_error_errno(dir_fd, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
652
653 r = readdir_all(dir_fd, RECURSE_DIR_IGNORE_DOT, &dentries);
654 if (r < 0)
655 return log_error_errno(r, "Failed to read directory '%s': %m", full);
656
657 FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) {
658 const struct dirent *de = *i;
659 _cleanup_fclose_ FILE *f = NULL;
660
661 if (!dirent_is_file(de))
662 continue;
663
664 if (!endswith_no_case(de->d_name, ".conf"))
665 continue;
666
667 r = xfopenat(dir_fd, de->d_name, "re", O_NOFOLLOW|O_NOCTTY, &f);
668 if (r < 0) {
669 log_warning_errno(r, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
670 continue;
671 }
672
673 r = config_check_inode_relevant_and_unseen(config, fileno(f), de->d_name);
674 if (r < 0)
675 return r;
676 if (r == 0) /* inode already seen or otherwise not relevant */
677 continue;
678
679 r = boot_config_load_type1(config, f, root, source, full, de->d_name);
680 if (r == -ENOMEM) /* ignore all other errors */
681 return log_oom();
682 }
683
684 return 0;
685 }
686
687 static int boot_entry_load_unified(
688 const char *root,
689 const BootEntrySource source,
690 const char *path,
691 unsigned profile,
692 const char *osrelease_text,
693 const char *profile_text,
694 const char *cmdline_text,
695 BootEntry *ret) {
696
697 _cleanup_free_ char *fname = NULL, *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
698 *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
699 const char *k, *good_name, *good_version, *good_sort_key;
700 _cleanup_fclose_ FILE *f = NULL;
701 int r;
702
703 assert(root);
704 assert(path);
705 assert(osrelease_text);
706 assert(ret);
707
708 k = path_startswith(path, root);
709 if (!k)
710 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
711
712 f = fmemopen_unlocked((void*) osrelease_text, strlen(osrelease_text), "r");
713 if (!f)
714 return log_oom();
715
716 r = parse_env_file(f, "os-release",
717 "PRETTY_NAME", &os_pretty_name,
718 "IMAGE_ID", &os_image_id,
719 "NAME", &os_name,
720 "ID", &os_id,
721 "IMAGE_VERSION", &os_image_version,
722 "VERSION", &os_version,
723 "VERSION_ID", &os_version_id,
724 "BUILD_ID", &os_build_id);
725 if (r < 0)
726 return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
727
728 if (!bootspec_pick_name_version_sort_key(
729 os_pretty_name,
730 os_image_id,
731 os_name,
732 os_id,
733 os_image_version,
734 os_version,
735 os_version_id,
736 os_build_id,
737 &good_name,
738 &good_version,
739 &good_sort_key))
740 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
741
742 _cleanup_free_ char *profile_id = NULL, *profile_title = NULL;
743 if (profile_text) {
744 fclose(f);
745
746 f = fmemopen_unlocked((void*) profile_text, strlen(profile_text), "r");
747 if (!f)
748 return log_oom();
749
750 r = parse_env_file(
751 f, "profile",
752 "ID", &profile_id,
753 "TITLE", &profile_title);
754 if (r < 0)
755 return log_error_errno(r, "Failed to parse profile data from unified kernel image '%s': %m", path);
756 }
757
758 r = path_extract_filename(path, &fname);
759 if (r < 0)
760 return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
761
762 _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_UNIFIED, source);
763
764 r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
765 if (r < 0)
766 return r;
767
768 if (!efi_loader_entry_name_valid(tmp.id))
769 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
770
771 tmp.profile = profile;
772
773 if (profile_id || profile > 0) {
774 tmp.id_without_profile = TAKE_PTR(tmp.id);
775
776 if (profile_id)
777 tmp.id = strjoin(tmp.id_without_profile, "@", profile_id);
778 else
779 (void) asprintf(&tmp.id, "%s@%u", tmp.id_without_profile, profile);
780 if (!tmp.id)
781 return log_oom();
782 }
783
784 if (os_id && os_version_id) {
785 tmp.id_old = strjoin(os_id, "-", os_version_id);
786 if (!tmp.id_old)
787 return log_oom();
788 }
789
790 tmp.path = strdup(path);
791 if (!tmp.path)
792 return log_oom();
793
794 tmp.root = strdup(root);
795 if (!tmp.root)
796 return log_oom();
797
798 tmp.kernel = path_make_absolute(k, "/");
799 if (!tmp.kernel)
800 return log_oom();
801
802 tmp.options = strv_new(cmdline_text);
803 if (!tmp.options)
804 return log_oom();
805
806 if (profile_title)
807 tmp.title = strjoin(good_name, " (", profile_title, ")");
808 else if (profile_id)
809 tmp.title = strjoin(good_name, " (", profile_id, ")");
810 else if (profile > 0)
811 (void) asprintf(&tmp.title, "%s (@%u)", good_name, profile);
812 else
813 tmp.title = strdup(good_name);
814 if (!tmp.title)
815 return log_oom();
816
817 if (good_sort_key) {
818 tmp.sort_key = strdup(good_sort_key);
819 if (!tmp.sort_key)
820 return log_oom();
821 }
822
823 if (good_version) {
824 tmp.version = strdup(good_version);
825 if (!tmp.version)
826 return log_oom();
827 }
828
829 *ret = TAKE_STRUCT(tmp);
830 return 0;
831 }
832
833 static int pe_load_headers_and_sections(
834 int fd,
835 const char *path,
836 IMAGE_SECTION_HEADER **ret_sections,
837 PeHeader **ret_pe_header) {
838
839 _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
840 _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
841 _cleanup_free_ PeHeader *pe_header = NULL;
842 int r;
843
844 assert(fd >= 0);
845 assert(path);
846
847 r = pe_load_headers(fd, &dos_header, &pe_header);
848 if (r < 0)
849 return log_error_errno(r, "Failed to parse PE file '%s': %m", path);
850
851 r = pe_load_sections(fd, dos_header, pe_header, &sections);
852 if (r < 0)
853 return log_error_errno(r, "Failed to parse PE sections of '%s': %m", path);
854
855 if (ret_pe_header)
856 *ret_pe_header = TAKE_PTR(pe_header);
857 if (ret_sections)
858 *ret_sections = TAKE_PTR(sections);
859
860 return 0;
861 }
862
863 static const IMAGE_SECTION_HEADER* pe_find_profile_section_table(
864 const PeHeader *pe_header,
865 const IMAGE_SECTION_HEADER *sections,
866 unsigned profile,
867 size_t *ret_n_sections) {
868
869 assert(pe_header);
870
871 /* Looks for the part of the section table that defines the specified profile. If 'profile' is
872 * specified as UINT_MAX this will look for the base profile. */
873
874 if (le16toh(pe_header->pe.NumberOfSections) == 0)
875 return NULL;
876
877 assert(sections);
878
879 const IMAGE_SECTION_HEADER
880 *p = sections,
881 *e = sections + le16toh(pe_header->pe.NumberOfSections),
882 *start = profile == UINT_MAX ? sections : NULL,
883 *end;
884 unsigned current_profile = UINT_MAX;
885
886 for (;;) {
887 p = pe_section_table_find(p, e - p, ".profile");
888 if (!p) {
889 end = e;
890 break;
891 }
892 if (current_profile == profile) {
893 end = p;
894 break;
895 }
896
897 if (current_profile == UINT_MAX)
898 current_profile = 0;
899 else
900 current_profile++;
901
902 if (current_profile == profile)
903 start = p;
904
905 p++; /* Continue scanning after the .profile entry we just found */
906 }
907
908 if (!start)
909 return NULL;
910
911 if (ret_n_sections)
912 *ret_n_sections = end - start;
913
914 return start;
915 }
916
917 static int trim_cmdline(char **cmdline) {
918 assert(cmdline);
919
920 /* Strips leading and trailing whitespace from command line */
921
922 if (!*cmdline)
923 return 0;
924
925 const char *skipped = skip_leading_chars(*cmdline, WHITESPACE);
926
927 if (isempty(skipped)) {
928 *cmdline = mfree(*cmdline);
929 return 0;
930 }
931
932 if (skipped != *cmdline) {
933 _cleanup_free_ char *c = strdup(skipped);
934 if (!c)
935 return -ENOMEM;
936
937 free_and_replace(*cmdline, c);
938 }
939
940 delete_trailing_chars(*cmdline, WHITESPACE);
941 return 1;
942 }
943
944 /* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
945 * the ones we do care about and we are willing to load into memory have this size limit.) */
946 #define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
947
948 static int pe_find_uki_sections(
949 int fd,
950 const char *path,
951 unsigned profile,
952 char **ret_osrelease,
953 char **ret_profile,
954 char **ret_cmdline) {
955
956 _cleanup_free_ char *osrelease_text = NULL, *profile_text = NULL, *cmdline_text = NULL;
957 _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
958 _cleanup_free_ PeHeader *pe_header = NULL;
959 int r;
960
961 assert(fd >= 0);
962 assert(path);
963 assert(profile != UINT_MAX);
964 assert(ret_osrelease);
965 assert(ret_profile);
966 assert(ret_cmdline);
967
968 r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
969 if (r < 0)
970 return r;
971
972 if (!pe_is_uki(pe_header, sections))
973 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path);
974
975 if (!pe_is_native(pe_header)) /* Don't process non-native UKIs */
976 goto nothing;
977
978 /* Find part of the section table for this profile */
979 size_t n_psections = 0;
980 const IMAGE_SECTION_HEADER *psections = pe_find_profile_section_table(pe_header, sections, profile, &n_psections);
981 if (!psections && profile != 0) /* Profile not found? (Profile @0 needs no explicit .profile!) */
982 goto nothing;
983
984 /* Find base profile part of section table */
985 size_t n_bsections;
986 const IMAGE_SECTION_HEADER *bsections = ASSERT_PTR(pe_find_profile_section_table(pe_header, sections, UINT_MAX, &n_bsections));
987
988 struct {
989 const char *name;
990 char **data;
991 } table[] = {
992 { ".osrel", &osrelease_text },
993 { ".profile", &profile_text },
994 { ".cmdline", &cmdline_text },
995 };
996
997 FOREACH_ELEMENT(t, table) {
998 const IMAGE_SECTION_HEADER *found;
999
1000 /* First look in the profile part of the section table, and if we don't find anything there, look into the base part */
1001 found = pe_section_table_find(psections, n_psections, t->name);
1002 if (!found) {
1003 found = pe_section_table_find(bsections, n_bsections, t->name);
1004 if (!found)
1005 continue;
1006 }
1007
1008 /* Permit "masking" of sections in the base profile */
1009 if (found->VirtualSize == 0)
1010 continue;
1011
1012 r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) t->data, /* ret_size= */ NULL);
1013 if (r < 0)
1014 return log_error_errno(r, "Failed to load contents of section '%s': %m", t->name);
1015 }
1016
1017 if (!osrelease_text)
1018 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unified kernel image lacks .osrel data for profile @%u, refusing.", profile);
1019
1020 if (trim_cmdline(&cmdline_text) < 0)
1021 return log_oom();
1022
1023 *ret_osrelease = TAKE_PTR(osrelease_text);
1024 *ret_profile = TAKE_PTR(profile_text);
1025 *ret_cmdline = TAKE_PTR(cmdline_text);
1026 return 1;
1027
1028 nothing:
1029 *ret_osrelease = *ret_profile = *ret_cmdline = NULL;
1030 return 0;
1031 }
1032
1033 static int pe_find_addon_sections(
1034 int fd,
1035 const char *path,
1036 char **ret_cmdline) {
1037
1038 _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
1039 _cleanup_free_ PeHeader *pe_header = NULL;
1040 int r;
1041
1042 assert(fd >= 0);
1043 assert(path);
1044
1045 r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
1046 if (r < 0)
1047 return r;
1048
1049 if (!pe_is_addon(pe_header, sections))
1050 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parse PE file '%s' is not an add-on.", path);
1051
1052 /* Define early, before the gotos below */
1053 _cleanup_free_ char *cmdline_text = NULL;
1054
1055 if (!pe_is_native(pe_header))
1056 goto nothing;
1057
1058 const IMAGE_SECTION_HEADER *found = pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), ".cmdline");
1059 if (!found)
1060 goto nothing;
1061
1062 r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) &cmdline_text, /* ret_size= */ NULL);
1063 if (r < 0)
1064 return log_error_errno(r, "Failed to load contents of section '.cmdline': %m");
1065
1066 if (trim_cmdline(&cmdline_text) < 0)
1067 return log_oom();
1068
1069 *ret_cmdline = TAKE_PTR(cmdline_text);
1070 return 1;
1071
1072 nothing:
1073 *ret_cmdline = NULL;
1074 return 0;
1075 }
1076
1077 static int insert_boot_entry_addon(
1078 BootEntryAddons *addons,
1079 char *location,
1080 char *cmdline) {
1081
1082 assert(addons);
1083
1084 if (!GREEDY_REALLOC(addons->items, addons->n_items + 1))
1085 return log_oom();
1086
1087 addons->items[addons->n_items++] = (BootEntryAddon) {
1088 .location = location,
1089 .cmdline = cmdline,
1090 };
1091
1092 return 0;
1093 }
1094
1095 static int boot_entries_find_unified_addons(
1096 BootConfig *config,
1097 int d_fd,
1098 const char *addon_dir,
1099 const char *root,
1100 BootEntryAddons *ret_addons) {
1101
1102 _cleanup_closedir_ DIR *d = NULL;
1103 _cleanup_free_ char *full = NULL;
1104 _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {};
1105 int r;
1106
1107 assert(ret_addons);
1108 assert(config);
1109
1110 r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d);
1111 if (r == -ENOENT)
1112 return 0;
1113 if (r < 0)
1114 return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(addon_dir));
1115
1116 FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
1117 _cleanup_free_ char *j = NULL, *cmdline = NULL, *location = NULL;
1118 _cleanup_close_ int fd = -EBADF;
1119
1120 if (!dirent_is_file(de))
1121 continue;
1122
1123 if (!endswith_no_case(de->d_name, ".addon.efi"))
1124 continue;
1125
1126 fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
1127 if (fd < 0) {
1128 log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
1129 continue;
1130 }
1131
1132 r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
1133 if (r < 0)
1134 return r;
1135 if (r == 0) /* inode already seen or otherwise not relevant */
1136 continue;
1137
1138 j = path_join(full, de->d_name);
1139 if (!j)
1140 return log_oom();
1141
1142 if (pe_find_addon_sections(fd, j, &cmdline) <= 0)
1143 continue;
1144
1145 location = strdup(j);
1146 if (!location)
1147 return log_oom();
1148
1149 r = insert_boot_entry_addon(&addons, location, cmdline);
1150 if (r < 0)
1151 return r;
1152
1153 TAKE_PTR(location);
1154 TAKE_PTR(cmdline);
1155 }
1156
1157 *ret_addons = TAKE_STRUCT(addons);
1158 return 0;
1159 }
1160
1161 static int boot_entries_find_unified_global_addons(
1162 BootConfig *config,
1163 const char *root,
1164 const char *d_name,
1165 BootEntryAddons *ret_addons) {
1166
1167 int r;
1168 _cleanup_closedir_ DIR *d = NULL;
1169
1170 assert(ret_addons);
1171
1172 r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d);
1173 if (r == -ENOENT)
1174 return 0;
1175 if (r < 0)
1176 return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(d_name));
1177
1178 return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, ret_addons);
1179 }
1180
1181 static int boot_entries_find_unified_local_addons(
1182 BootConfig *config,
1183 int d_fd,
1184 const char *d_name,
1185 const char *root,
1186 BootEntry *ret) {
1187
1188 _cleanup_free_ char *addon_dir = NULL;
1189
1190 assert(ret);
1191
1192 addon_dir = strjoin(d_name, ".extra.d");
1193 if (!addon_dir)
1194 return log_oom();
1195
1196 return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
1197 }
1198
1199 static int boot_entries_find_unified(
1200 BootConfig *config,
1201 const char *root,
1202 BootEntrySource source,
1203 const char *dir) {
1204
1205 _cleanup_closedir_ DIR *d = NULL;
1206 _cleanup_free_ char *full = NULL;
1207 int r;
1208
1209 assert(config);
1210 assert(dir);
1211
1212 r = chase_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
1213 if (r == -ENOENT)
1214 return 0;
1215 if (r < 0)
1216 return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
1217
1218 FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
1219 if (!dirent_is_file(de))
1220 continue;
1221
1222 if (!endswith_no_case(de->d_name, ".efi"))
1223 continue;
1224
1225 _cleanup_close_ int fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
1226 if (fd < 0) {
1227 log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
1228 continue;
1229 }
1230
1231 r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
1232 if (r < 0)
1233 return r;
1234 if (r == 0) /* inode already seen or otherwise not relevant */
1235 continue;
1236
1237 _cleanup_free_ char *j = path_join(full, de->d_name);
1238 if (!j)
1239 return log_oom();
1240
1241 for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
1242 _cleanup_free_ char *osrelease = NULL, *profile = NULL, *cmdline = NULL;
1243
1244 r = pe_find_uki_sections(fd, j, p, &osrelease, &profile, &cmdline);
1245 if (r == 0) /* this profile does not exist, we are done */
1246 break;
1247 if (r < 0)
1248 continue;
1249
1250 if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
1251 return log_oom();
1252
1253 BootEntry *entry = config->entries + config->n_entries;
1254
1255 if (boot_entry_load_unified(root, source, j, p, osrelease, profile, cmdline, entry) < 0)
1256 continue;
1257
1258 /* look for .efi.extra.d */
1259 (void) boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, entry);
1260
1261 /* Set up the backpointer, so that we can find the global addons */
1262 entry->global_addons = &config->global_addons[source];
1263
1264 config->n_entries++;
1265 }
1266 }
1267
1268 return 0;
1269 }
1270
1271 static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
1272 bool non_unique = false;
1273
1274 assert(entries || n_entries == 0);
1275 assert(arr || n_entries == 0);
1276
1277 for (size_t i = 0; i < n_entries; i++)
1278 arr[i] = false;
1279
1280 for (size_t i = 0; i < n_entries; i++)
1281 for (size_t j = 0; j < n_entries; j++)
1282 if (i != j && streq(boot_entry_title(entries + i),
1283 boot_entry_title(entries + j)))
1284 non_unique = arr[i] = arr[j] = true;
1285
1286 return non_unique;
1287 }
1288
1289 static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
1290 _cleanup_free_ bool *arr = NULL;
1291 char *s;
1292
1293 assert(entries || n_entries == 0);
1294
1295 if (n_entries == 0)
1296 return 0;
1297
1298 arr = new(bool, n_entries);
1299 if (!arr)
1300 return -ENOMEM;
1301
1302 /* Find _all_ non-unique titles */
1303 if (!find_nonunique(entries, n_entries, arr))
1304 return 0;
1305
1306 /* Add version to non-unique titles */
1307 for (size_t i = 0; i < n_entries; i++)
1308 if (arr[i] && entries[i].version) {
1309 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version) < 0)
1310 return -ENOMEM;
1311
1312 free_and_replace(entries[i].show_title, s);
1313 }
1314
1315 if (!find_nonunique(entries, n_entries, arr))
1316 return 0;
1317
1318 /* Add machine-id to non-unique titles */
1319 for (size_t i = 0; i < n_entries; i++)
1320 if (arr[i] && entries[i].machine_id) {
1321 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id) < 0)
1322 return -ENOMEM;
1323
1324 free_and_replace(entries[i].show_title, s);
1325 }
1326
1327 if (!find_nonunique(entries, n_entries, arr))
1328 return 0;
1329
1330 /* Add file name to non-unique titles */
1331 for (size_t i = 0; i < n_entries; i++)
1332 if (arr[i]) {
1333 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id) < 0)
1334 return -ENOMEM;
1335
1336 free_and_replace(entries[i].show_title, s);
1337 }
1338
1339 return 0;
1340 }
1341
1342 static int boot_config_find(const BootConfig *config, const char *id) {
1343 assert(config);
1344
1345 if (!id)
1346 return -1;
1347
1348 if (id[0] == '@') {
1349 if (!strcaseeq(id, "@saved"))
1350 return -1;
1351 if (!config->entry_selected)
1352 return -1;
1353 id = config->entry_selected;
1354 }
1355
1356 for (size_t i = 0; i < config->n_entries; i++)
1357 if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
1358 return i;
1359
1360 return -1;
1361 }
1362
1363 static int boot_entries_select_default(const BootConfig *config) {
1364 int i;
1365
1366 assert(config);
1367 assert(config->entries || config->n_entries == 0);
1368
1369 if (config->n_entries == 0) {
1370 log_debug("Found no default boot entry :(");
1371 return -1; /* -1 means "no default" */
1372 }
1373
1374 if (config->entry_oneshot) {
1375 i = boot_config_find(config, config->entry_oneshot);
1376 if (i >= 0) {
1377 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
1378 config->entries[i].id);
1379 return i;
1380 }
1381 }
1382
1383 if (config->entry_default) {
1384 i = boot_config_find(config, config->entry_default);
1385 if (i >= 0) {
1386 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
1387 config->entries[i].id);
1388 return i;
1389 }
1390 }
1391
1392 if (config->default_pattern) {
1393 i = boot_config_find(config, config->default_pattern);
1394 if (i >= 0) {
1395 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
1396 config->entries[i].id, config->default_pattern);
1397 return i;
1398 }
1399 }
1400
1401 log_debug("Found default: first entry \"%s\"", config->entries[0].id);
1402 return 0;
1403 }
1404
1405 static int boot_entries_select_selected(const BootConfig *config) {
1406 assert(config);
1407 assert(config->entries || config->n_entries == 0);
1408
1409 if (!config->entry_selected || config->n_entries == 0)
1410 return -1;
1411
1412 return boot_config_find(config, config->entry_selected);
1413 }
1414
1415 static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) {
1416 int r;
1417
1418 assert(config);
1419
1420 if (skip_efivars || !is_efi_boot())
1421 return 0;
1422
1423 /* Loads the three "pointers" to boot loader entries from their EFI variables */
1424
1425 r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &config->entry_oneshot);
1426 if (r == -ENOMEM)
1427 return log_oom();
1428 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
1429 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m");
1430
1431 r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &config->entry_default);
1432 if (r == -ENOMEM)
1433 return log_oom();
1434 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
1435 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m");
1436
1437 r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), &config->entry_selected);
1438 if (r == -ENOMEM)
1439 return log_oom();
1440 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
1441 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m");
1442
1443 r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &config->entry_sysfail);
1444 if (r == -ENOMEM)
1445 return log_oom();
1446 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
1447 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySysFail\", ignoring: %m");
1448
1449 return 1;
1450 }
1451
1452 int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) {
1453 int r;
1454
1455 assert(config);
1456
1457 r = boot_load_efi_entry_pointers(config, skip_efivars);
1458 if (r < 0)
1459 return r;
1460
1461 config->default_entry = boot_entries_select_default(config);
1462 config->selected_entry = boot_entries_select_selected(config);
1463
1464 return 0;
1465 }
1466
1467 int boot_config_finalize(BootConfig *config) {
1468 int r;
1469
1470 typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
1471
1472 r = boot_entries_uniquify(config->entries, config->n_entries);
1473 if (r < 0)
1474 return log_error_errno(r, "Failed to uniquify boot entries: %m");
1475
1476 return 0;
1477 }
1478
1479 int boot_config_load(
1480 BootConfig *config,
1481 const char *esp_path,
1482 const char *xbootldr_path) {
1483
1484 int r;
1485
1486 assert(config);
1487
1488 if (esp_path) {
1489 r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf");
1490 if (r < 0)
1491 return r;
1492
1493 r = boot_entries_find_type1(config, esp_path, BOOT_ENTRY_ESP, "/loader/entries");
1494 if (r < 0)
1495 return r;
1496
1497 r = boot_entries_find_unified(config, esp_path, BOOT_ENTRY_ESP, "/EFI/Linux/");
1498 if (r < 0)
1499 return r;
1500
1501 r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/",
1502 &config->global_addons[BOOT_ENTRY_ESP]);
1503 if (r < 0)
1504 return r;
1505 }
1506
1507 if (xbootldr_path) {
1508 r = boot_entries_find_type1(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/loader/entries");
1509 if (r < 0)
1510 return r;
1511
1512 r = boot_entries_find_unified(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/EFI/Linux/");
1513 if (r < 0)
1514 return r;
1515
1516 r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader/addons/",
1517 &config->global_addons[BOOT_ENTRY_XBOOTLDR]);
1518 if (r < 0)
1519 return r;
1520 }
1521
1522 return boot_config_finalize(config);
1523 }
1524
1525 int boot_config_load_auto(
1526 BootConfig *config,
1527 const char *override_esp_path,
1528 const char *override_xbootldr_path) {
1529
1530 _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
1531 dev_t esp_devid = 0, xbootldr_devid = 0;
1532 int r;
1533
1534 assert(config);
1535
1536 /* This function is similar to boot_entries_load_config(), however we automatically search for the
1537 * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
1538 * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
1539 * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
1540 * it). This allows other boot loaders to pass boot loader entry information to our tools if they
1541 * want to. */
1542
1543 if (!override_esp_path && !override_xbootldr_path) {
1544 if (access("/run/boot-loader-entries/", F_OK) >= 0)
1545 return boot_config_load(config, "/run/boot-loader-entries/", NULL);
1546
1547 if (errno != ENOENT)
1548 return log_error_errno(errno,
1549 "Failed to determine whether /run/boot-loader-entries/ exists: %m");
1550 }
1551
1552 r = find_esp_and_warn(NULL, override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid);
1553 if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
1554 return r;
1555
1556 r = find_xbootldr_and_warn(NULL, override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid);
1557 if (r < 0 && r != -ENOKEY)
1558 return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
1559
1560 /* If both paths actually refer to the same inode, suppress the xbootldr path */
1561 if (esp_where && xbootldr_where && devnum_set_and_equal(esp_devid, xbootldr_devid))
1562 xbootldr_where = mfree(xbootldr_where);
1563
1564 return boot_config_load(config, esp_where, xbootldr_where);
1565 }
1566
1567 int boot_config_augment_from_loader(
1568 BootConfig *config,
1569 char **found_by_loader,
1570 bool auto_only) {
1571
1572 static const BootEntryAddons no_addons = (BootEntryAddons) {};
1573 static const char *const title_table[] = {
1574 /* Pretty names for a few well-known automatically discovered entries. */
1575 "auto-osx", "macOS",
1576 "auto-windows", "Windows Boot Manager",
1577 "auto-efi-shell", "EFI Shell",
1578 "auto-efi-default", "EFI Default Loader",
1579 "auto-poweroff", "Power Off The System",
1580 "auto-reboot", "Reboot The System",
1581 "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
1582 NULL,
1583 };
1584
1585 assert(config);
1586
1587 /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
1588 * already included there. */
1589
1590 STRV_FOREACH(i, found_by_loader) {
1591 BootEntry *existing;
1592 _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
1593
1594 existing = boot_config_find_entry(config, *i);
1595 if (existing) {
1596 existing->reported_by_loader = true;
1597 continue;
1598 }
1599
1600 if (auto_only && !startswith(*i, "auto-"))
1601 continue;
1602
1603 c = strdup(*i);
1604 if (!c)
1605 return log_oom();
1606
1607 STRV_FOREACH_PAIR(a, b, title_table)
1608 if (streq(*a, *i)) {
1609 t = strdup(*b);
1610 if (!t)
1611 return log_oom();
1612 break;
1613 }
1614
1615 p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntries")));
1616 if (!p)
1617 return log_oom();
1618
1619 if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
1620 return log_oom();
1621
1622 config->entries[config->n_entries++] = (BootEntry) {
1623 .type = startswith(*i, "auto-") ? BOOT_ENTRY_LOADER_AUTO : BOOT_ENTRY_LOADER,
1624 .id = TAKE_PTR(c),
1625 .title = TAKE_PTR(t),
1626 .path = TAKE_PTR(p),
1627 .reported_by_loader = true,
1628 .tries_left = UINT_MAX,
1629 .tries_done = UINT_MAX,
1630 .global_addons = &no_addons,
1631 };
1632 }
1633
1634 return 0;
1635 }
1636
1637 BootEntry* boot_config_find_entry(BootConfig *config, const char *id) {
1638 assert(config);
1639 assert(id);
1640
1641 for (size_t j = 0; j < config->n_entries; j++)
1642 if (strcaseeq_ptr(config->entries[j].id, id) ||
1643 strcaseeq_ptr(config->entries[j].id_old, id))
1644 return config->entries + j;
1645
1646 return NULL;
1647 }
1648
1649 static void boot_entry_file_list(
1650 const char *field,
1651 const char *root,
1652 const char *p,
1653 int *ret_status) {
1654
1655 assert(p);
1656 assert(ret_status);
1657
1658 int status = chase_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
1659
1660 /* Note that this shows two '/' between the root and the file. This is intentional to highlight (in
1661 * the absence of color support) to the user that the boot loader is only interested in the second
1662 * part of the file. */
1663 printf("%13s%s %s%s/%s", strempty(field), field ? ":" : " ", ansi_grey(), root, ansi_normal());
1664
1665 if (status < 0) {
1666 errno = -status;
1667 printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal());
1668 } else
1669 printf("%s\n", p);
1670
1671 if (*ret_status == 0 && status < 0)
1672 *ret_status = status;
1673 }
1674
1675 static void print_addon(
1676 BootEntryAddon *addon,
1677 const char *addon_str) {
1678
1679 printf(" %s: %s\n", addon_str, addon->location);
1680 printf(" options: %s%s\n", glyph(GLYPH_TREE_RIGHT), addon->cmdline);
1681 }
1682
1683 static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) {
1684 _cleanup_free_ char *t = NULL;
1685 _cleanup_strv_free_ char **ts = NULL;
1686
1687 assert(ret_cmdline);
1688
1689 ts = strv_split_newlines(cmdline);
1690 if (!ts)
1691 return -ENOMEM;
1692
1693 t = strv_join(ts, "\n ");
1694 if (!t)
1695 return -ENOMEM;
1696
1697 *ret_cmdline = TAKE_PTR(t);
1698
1699 return 0;
1700 }
1701
1702 static int print_cmdline(const BootEntry *e) {
1703
1704 _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL;
1705
1706 assert(e);
1707
1708 if (!strv_isempty(e->options)) {
1709 _cleanup_free_ char *t = NULL;
1710
1711 options = strv_join(e->options, " ");
1712 if (!options)
1713 return log_oom();
1714
1715 if (indent_embedded_newlines(options, &t) < 0)
1716 return log_oom();
1717
1718 printf(" options: %s\n", t);
1719 t2 = strdup(options);
1720 if (!t2)
1721 return log_oom();
1722 }
1723
1724 FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) {
1725 print_addon(addon, "global-addon");
1726 if (!strextend(&t2, " ", addon->cmdline))
1727 return log_oom();
1728 }
1729
1730 FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
1731 /* Add space at the beginning of addon_str to align it correctly */
1732 print_addon(addon, " local-addon");
1733 if (!strextend(&t2, " ", addon->cmdline))
1734 return log_oom();
1735 }
1736
1737 /* Don't print the combined cmdline if it's same as options. */
1738 if (streq_ptr(t2, options))
1739 return 0;
1740
1741 if (indent_embedded_newlines(t2, &combined_cmdline) < 0)
1742 return log_oom();
1743
1744 if (combined_cmdline)
1745 printf(" cmdline: %s\n", combined_cmdline);
1746
1747 return 0;
1748 }
1749
1750 static int json_addon(
1751 BootEntryAddon *addon,
1752 const char *addon_str,
1753 sd_json_variant **array) {
1754
1755 int r;
1756
1757 assert(addon);
1758 assert(addon_str);
1759
1760 r = sd_json_variant_append_arraybo(
1761 array,
1762 SD_JSON_BUILD_PAIR(addon_str, SD_JSON_BUILD_STRING(addon->location)),
1763 SD_JSON_BUILD_PAIR("options", SD_JSON_BUILD_STRING(addon->cmdline)));
1764 if (r < 0)
1765 return log_oom();
1766
1767 return 0;
1768 }
1769
1770 static int json_cmdline(
1771 const BootEntry *e,
1772 const char *def_cmdline,
1773 sd_json_variant **v) {
1774
1775 _cleanup_free_ char *combined_cmdline = NULL;
1776 _cleanup_(sd_json_variant_unrefp) sd_json_variant *addons_array = NULL;
1777 int r;
1778
1779 assert(e);
1780
1781 if (def_cmdline) {
1782 combined_cmdline = strdup(def_cmdline);
1783 if (!combined_cmdline)
1784 return log_oom();
1785 }
1786
1787 FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) {
1788 r = json_addon(addon, "globalAddon", &addons_array);
1789 if (r < 0)
1790 return r;
1791 if (!strextend(&combined_cmdline, " ", addon->cmdline))
1792 return log_oom();
1793 }
1794
1795 FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
1796 r = json_addon(addon, "localAddon", &addons_array);
1797 if (r < 0)
1798 return r;
1799 if (!strextend(&combined_cmdline, " ", addon->cmdline))
1800 return log_oom();
1801 }
1802
1803 r = sd_json_variant_merge_objectbo(
1804 v,
1805 SD_JSON_BUILD_PAIR("addons", SD_JSON_BUILD_VARIANT(addons_array)),
1806 SD_JSON_BUILD_PAIR_CONDITION(!!combined_cmdline, "cmdline", SD_JSON_BUILD_STRING(combined_cmdline)));
1807 if (r < 0)
1808 return log_oom();
1809 return 0;
1810 }
1811
1812 int show_boot_entry(
1813 const BootEntry *e,
1814 bool show_as_default,
1815 bool show_as_selected,
1816 bool show_reported) {
1817
1818 int status = 0, r = 0;
1819
1820 /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
1821 boot entry itself. */
1822
1823 assert(e);
1824
1825 printf(" type: %s\n",
1826 boot_entry_type_to_string(e->type));
1827
1828 printf(" title: %s%s%s",
1829 ansi_highlight(), boot_entry_title(e), ansi_normal());
1830
1831 if (show_as_default)
1832 printf(" %s(default)%s",
1833 ansi_highlight_green(), ansi_normal());
1834
1835 if (show_as_selected)
1836 printf(" %s(selected)%s",
1837 ansi_highlight_magenta(), ansi_normal());
1838
1839 if (show_reported) {
1840 if (e->type == BOOT_ENTRY_LOADER)
1841 printf(" %s(reported/absent)%s",
1842 ansi_highlight_red(), ansi_normal());
1843 else if (!e->reported_by_loader && e->type != BOOT_ENTRY_LOADER_AUTO)
1844 printf(" %s(not reported/new)%s",
1845 ansi_highlight_green(), ansi_normal());
1846 }
1847
1848 putchar('\n');
1849
1850 if (e->id) {
1851 printf(" id: %s", e->id);
1852
1853 if (e->id_without_profile && !streq_ptr(e->id, e->id_without_profile))
1854 printf(" (without profile: %s)\n", e->id_without_profile);
1855 else
1856 putchar('\n');
1857 }
1858 if (e->path) {
1859 _cleanup_free_ char *text = NULL, *link = NULL;
1860
1861 const char *p = e->root ? path_startswith(e->path, e->root) : NULL;
1862 if (p) {
1863 text = strjoin(ansi_grey(), e->root, "/", ansi_normal(), "/", p);
1864 if (!text)
1865 return log_oom();
1866 }
1867
1868 /* Let's urlify the link to make it easy to view in an editor, but only if it is a text
1869 * file. Unified images are binary ELFs, and EFI variables are not pure text either. */
1870 if (e->type == BOOT_ENTRY_CONF)
1871 (void) terminal_urlify_path(e->path, text, &link);
1872
1873 printf(" source: %s (on the %s)\n",
1874 link ?: text ?: e->path,
1875 boot_entry_source_to_string(e->source));
1876 }
1877 if (e->tries_left != UINT_MAX) {
1878 printf(" tries: %u left", e->tries_left);
1879
1880 if (e->tries_done != UINT_MAX)
1881 printf("; %u done\n", e->tries_done);
1882 else
1883 putchar('\n');
1884 }
1885
1886 if (e->sort_key)
1887 printf(" sort-key: %s\n", e->sort_key);
1888 if (e->version)
1889 printf(" version: %s\n", e->version);
1890 if (e->machine_id)
1891 printf(" machine-id: %s\n", e->machine_id);
1892 if (e->architecture)
1893 printf(" architecture: %s\n", e->architecture);
1894 if (e->kernel)
1895 boot_entry_file_list("linux", e->root, e->kernel, &status);
1896 if (e->efi)
1897 boot_entry_file_list("efi", e->root, e->efi, &status);
1898
1899 STRV_FOREACH(s, e->initrd)
1900 boot_entry_file_list(s == e->initrd ? "initrd" : NULL,
1901 e->root,
1902 *s,
1903 &status);
1904
1905 r = print_cmdline(e);
1906 if (r < 0)
1907 return r;
1908
1909 if (e->device_tree)
1910 boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
1911
1912 STRV_FOREACH(s, e->device_tree_overlay)
1913 boot_entry_file_list(s == e->device_tree_overlay ? "devicetree-overlay" : NULL,
1914 e->root,
1915 *s,
1916 &status);
1917
1918 return -status;
1919 }
1920
1921 int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret) {
1922 _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
1923 _cleanup_free_ char *opts = NULL;
1924 const BootEntry *e;
1925 int r;
1926
1927 assert(c);
1928 assert(ret);
1929
1930 if (i >= c->n_entries) {
1931 *ret = NULL;
1932 return 0;
1933 }
1934
1935 e = c->entries + i;
1936
1937 if (!strv_isempty(e->options)) {
1938 opts = strv_join(e->options, " ");
1939 if (!opts)
1940 return log_oom();
1941 }
1942
1943 r = sd_json_variant_merge_objectbo(
1944 &v,
1945 SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))),
1946 SD_JSON_BUILD_PAIR("source", SD_JSON_BUILD_STRING(boot_entry_source_json_to_string(e->source))),
1947 SD_JSON_BUILD_PAIR_CONDITION(!!e->id, "id", SD_JSON_BUILD_STRING(e->id)),
1948 SD_JSON_BUILD_PAIR_CONDITION(!!e->path, "path", SD_JSON_BUILD_STRING(e->path)),
1949 SD_JSON_BUILD_PAIR_CONDITION(!!e->root, "root", SD_JSON_BUILD_STRING(e->root)),
1950 SD_JSON_BUILD_PAIR_CONDITION(!!e->title, "title", SD_JSON_BUILD_STRING(e->title)),
1951 SD_JSON_BUILD_PAIR_CONDITION(!!boot_entry_title(e), "showTitle", SD_JSON_BUILD_STRING(boot_entry_title(e))),
1952 SD_JSON_BUILD_PAIR_CONDITION(!!e->sort_key, "sortKey", SD_JSON_BUILD_STRING(e->sort_key)),
1953 SD_JSON_BUILD_PAIR_CONDITION(!!e->version, "version", SD_JSON_BUILD_STRING(e->version)),
1954 SD_JSON_BUILD_PAIR_CONDITION(!!e->machine_id, "machineId", SD_JSON_BUILD_STRING(e->machine_id)),
1955 SD_JSON_BUILD_PAIR_CONDITION(!!e->architecture, "architecture", SD_JSON_BUILD_STRING(e->architecture)),
1956 SD_JSON_BUILD_PAIR_CONDITION(!!opts, "options", SD_JSON_BUILD_STRING(opts)),
1957 SD_JSON_BUILD_PAIR_CONDITION(!!e->kernel, "linux", SD_JSON_BUILD_STRING(e->kernel)),
1958 SD_JSON_BUILD_PAIR_CONDITION(!!e->efi, "efi", SD_JSON_BUILD_STRING(e->efi)),
1959 SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", SD_JSON_BUILD_STRV(e->initrd)),
1960 SD_JSON_BUILD_PAIR_CONDITION(!!e->device_tree, "devicetree", SD_JSON_BUILD_STRING(e->device_tree)),
1961 SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", SD_JSON_BUILD_STRV(e->device_tree_overlay)));
1962 if (r < 0)
1963 return log_oom();
1964
1965 /* Sanitizers (only memory sanitizer?) do not like function call with too many
1966 * arguments and trigger false positive warnings. Let's not add too many json objects
1967 * at once. */
1968 r = sd_json_variant_merge_objectbo(
1969 &v,
1970 SD_JSON_BUILD_PAIR("isReported", SD_JSON_BUILD_BOOLEAN(e->reported_by_loader)),
1971 SD_JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", SD_JSON_BUILD_UNSIGNED(e->tries_left)),
1972 SD_JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", SD_JSON_BUILD_UNSIGNED(e->tries_done)),
1973 SD_JSON_BUILD_PAIR_CONDITION(c->default_entry >= 0, "isDefault", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->default_entry)),
1974 SD_JSON_BUILD_PAIR_CONDITION(c->selected_entry >= 0, "isSelected", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->selected_entry)));
1975 if (r < 0)
1976 return log_oom();
1977
1978 r = json_cmdline(e, opts, &v);
1979 if (r < 0)
1980 return log_oom();
1981
1982 *ret = TAKE_PTR(v);
1983 return 1;
1984 }
1985
1986 int show_boot_entries(const BootConfig *config, sd_json_format_flags_t json_format) {
1987 int r;
1988
1989 assert(config);
1990
1991 if (sd_json_format_enabled(json_format)) {
1992 _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
1993
1994 for (size_t i = 0; i < config->n_entries; i++) {
1995 _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
1996
1997 r = boot_entry_to_json(config, i, &v);
1998 if (r < 0)
1999 return log_oom();
2000
2001 r = sd_json_variant_append_array(&array, v);
2002 if (r < 0)
2003 return log_oom();
2004 }
2005
2006 return sd_json_variant_dump(array, json_format | SD_JSON_FORMAT_EMPTY_ARRAY, NULL, NULL);
2007 } else
2008 for (size_t n = 0; n < config->n_entries; n++) {
2009 r = show_boot_entry(
2010 config->entries + n,
2011 /* show_as_default= */ n == (size_t) config->default_entry,
2012 /* show_as_selected= */ n == (size_t) config->selected_entry,
2013 /* show_reported= */ true);
2014 if (r < 0)
2015 return r;
2016
2017 if (n+1 < config->n_entries)
2018 putchar('\n');
2019 }
2020
2021 return 0;
2022 }