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