]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/bootspec.c
bootspec: let's actually use the result of strstrip() for further parsing
[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"
7e87c7d9 7#include "conf-files.h"
7176f06c 8#include "devnum-util.h"
5e146a75 9#include "dirent-util.h"
0bb2f0f1 10#include "efi-loader.h"
5e146a75 11#include "env-file.h"
432ce537 12#include "errno-util.h"
7e87c7d9
ZJS
13#include "fd-util.h"
14#include "fileio.h"
e94830c0 15#include "find-esp.h"
cc7a0bfa 16#include "path-util.h"
5e146a75 17#include "pe-header.h"
432ce537 18#include "pretty-print.h"
d04f0331 19#include "recurse-dir.h"
760877e9 20#include "sort-util.h"
432ce537 21#include "string-table.h"
7e87c7d9 22#include "strv.h"
432ce537 23#include "terminal-util.h"
5e146a75 24#include "unaligned.h"
7e87c7d9 25
432ce537
ZJS
26static const char* const boot_entry_type_table[_BOOT_ENTRY_TYPE_MAX] = {
27 [BOOT_ENTRY_CONF] = "Boot Loader Specification Type #1 (.conf)",
28 [BOOT_ENTRY_UNIFIED] = "Boot Loader Specification Type #2 (.efi)",
29 [BOOT_ENTRY_LOADER] = "Reported by Boot Loader",
30 [BOOT_ENTRY_LOADER_AUTO] = "Automatic",
31};
32
33DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type, BootEntryType);
34
0de2e1fd 35static void boot_entry_free(BootEntry *entry) {
4fe2ba0e 36 assert(entry);
7e87c7d9 37
12580bc3 38 free(entry->id);
15b82eec 39 free(entry->id_old);
2d3bfb69 40 free(entry->path);
43b736a8 41 free(entry->root);
7e87c7d9 42 free(entry->title);
64f05708 43 free(entry->show_title);
20ec8f53 44 free(entry->sort_key);
7e87c7d9
ZJS
45 free(entry->version);
46 free(entry->machine_id);
47 free(entry->architecture);
48 strv_free(entry->options);
49 free(entry->kernel);
50 free(entry->efi);
51 strv_free(entry->initrd);
52 free(entry->device_tree);
fdc5c042 53 strv_free(entry->device_tree_overlay);
7e87c7d9
ZJS
54}
55
bb9133bb
LP
56static int mangle_path(const char *field, const char *p, char **ret) {
57 _cleanup_free_ char *c = NULL;
58
59 assert(field);
60 assert(p);
61 assert(ret);
62
63 /* Spec leaves open if prefixed with "/" or not, let's normalize that */
64 if (path_is_absolute(p))
65 c = strdup(p);
66 else
67 c = strjoin("/", p);
68 if (!c)
69 return -ENOMEM;
70
71 /* We only reference files, never directories */
72 if (endswith(c, "/")) {
73 log_warning("Path in field '%s' has trailing slash, ignoring: %s", field, c);
74 *ret = NULL;
75 return 0;
76 }
77
78 /* Remove duplicate "/" */
79 path_simplify(c);
80
81 /* No ".." or "." or so */
82 if (!path_is_normalized(c)) {
83 log_warning("Path in field '%s' is not normalized, ignoring: %s", field, c);
84 *ret = NULL;
85 return 0;
86 }
87
88 *ret = TAKE_PTR(c);
89 return 1;
90}
91
92static int parse_path_one(const char *field, char **s, const char *p) {
93 _cleanup_free_ char *c = NULL;
94 int r;
95
96 assert(field);
97 assert(s);
98 assert(p);
99
100 r = mangle_path(field, p, &c);
101 if (r <= 0)
102 return r;
103
104 free_and_replace(*s, c);
105 return 0;
106}
107
108static int parse_path_strv(const char *field, char ***s, const char *p) {
109 char *c;
110 int r;
111
112 assert(field);
113 assert(s);
114 assert(p);
115
116 r = mangle_path(field, p, &c);
117 if (r <= 0)
118 return r;
119
120 return strv_consume(s, c);
121}
122
123static int parse_path_many(const char *field, char ***s, const char *p) {
124 _cleanup_strv_free_ char **l = NULL, **f = NULL;
125 int r;
126
127 l = strv_split(p, NULL);
128 if (!l)
129 return -ENOMEM;
130
131 STRV_FOREACH(i, l) {
132 char *c;
133
134 r = mangle_path(field, *i, &c);
135 if (r < 0)
136 return r;
137 if (r == 0)
138 continue;
139
140 r = strv_consume(&f, c);
141 if (r < 0)
142 return r;
143 }
144
145 return strv_extend_strv(s, f, /* filter_duplicates= */ false);
146}
147
85e17916 148static int boot_entry_load_type1(
d04f0331 149 FILE *f,
43b736a8 150 const char *root,
d04f0331
LP
151 const char *dir,
152 const char *id,
43b736a8
LP
153 BootEntry *entry) {
154
93f14ce2
LP
155 _cleanup_(boot_entry_free) BootEntry tmp = {
156 .type = BOOT_ENTRY_CONF,
157 };
158
7e87c7d9 159 unsigned line = 1;
736783d4 160 char *c;
7e87c7d9
ZJS
161 int r;
162
d04f0331 163 assert(f);
43b736a8 164 assert(root);
d04f0331
LP
165 assert(dir);
166 assert(id);
4fe2ba0e
LP
167 assert(entry);
168
d04f0331 169 /* Loads a Type #1 boot menu entry from the specified FILE* object */
7e87c7d9 170
d04f0331
LP
171 if (!efi_loader_entry_name_valid(id))
172 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", id);
173
174 c = endswith_no_case(id, ".conf");
736783d4 175 if (!c)
d04f0331 176 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", id);
7e87c7d9 177
d04f0331
LP
178 tmp.id = strdup(id);
179 if (!tmp.id)
180 return log_oom();
eed7210a 181
d04f0331 182 tmp.id_old = strndup(id, c - id); /* Without .conf suffix */
736783d4
LP
183 if (!tmp.id_old)
184 return log_oom();
185
d04f0331 186 tmp.path = path_join(dir, id);
2d3bfb69
ZJS
187 if (!tmp.path)
188 return log_oom();
189
43b736a8
LP
190 tmp.root = strdup(root);
191 if (!tmp.root)
192 return log_oom();
193
7e87c7d9 194 for (;;) {
f99fdc3e
YW
195 _cleanup_free_ char *buf = NULL, *field = NULL;
196 const char *p;
7e87c7d9
ZJS
197
198 r = read_line(f, LONG_LINE_MAX, &buf);
199 if (r == 0)
200 break;
201 if (r == -ENOBUFS)
d04f0331 202 return log_error_errno(r, "%s:%u: Line too long", tmp.path, line);
7e87c7d9 203 if (r < 0)
d04f0331 204 return log_error_errno(r, "%s:%u: Error while reading: %m", tmp.path, line);
7e87c7d9
ZJS
205
206 line++;
207
da8f277c
LP
208 p = strstrip(buf);
209 if (IN_SET(p[0], '#', '\0'))
7e87c7d9
ZJS
210 continue;
211
da8f277c 212 r = extract_first_word(&p, &field, NULL, 0);
f99fdc3e 213 if (r < 0) {
d04f0331 214 log_error_errno(r, "Failed to parse config file %s line %u: %m", tmp.path, line);
f99fdc3e
YW
215 continue;
216 }
217 if (r == 0) {
d04f0331 218 log_warning("%s:%u: Bad syntax", tmp.path, line);
7e87c7d9
ZJS
219 continue;
220 }
7e87c7d9 221
b6bd2562
ZJS
222 if (isempty(p)) {
223 /* Some fields can reasonably have an empty value. In other cases warn. */
224 if (!STR_IN_SET(field, "options", "devicetree-overlay"))
225 log_warning("%s:%u: Field %s without value", tmp.path, line, field);
226 continue;
227 }
228
f99fdc3e 229 if (streq(field, "title"))
7e87c7d9 230 r = free_and_strdup(&tmp.title, p);
20ec8f53
LP
231 else if (streq(field, "sort-key"))
232 r = free_and_strdup(&tmp.sort_key, p);
f99fdc3e 233 else if (streq(field, "version"))
7e87c7d9 234 r = free_and_strdup(&tmp.version, p);
f99fdc3e 235 else if (streq(field, "machine-id"))
7e87c7d9 236 r = free_and_strdup(&tmp.machine_id, p);
f99fdc3e 237 else if (streq(field, "architecture"))
7e87c7d9 238 r = free_and_strdup(&tmp.architecture, p);
f99fdc3e 239 else if (streq(field, "options"))
7e87c7d9 240 r = strv_extend(&tmp.options, p);
f99fdc3e 241 else if (streq(field, "linux"))
bb9133bb 242 r = parse_path_one(field, &tmp.kernel, p);
f99fdc3e 243 else if (streq(field, "efi"))
bb9133bb 244 r = parse_path_one(field, &tmp.efi, p);
f99fdc3e 245 else if (streq(field, "initrd"))
bb9133bb 246 r = parse_path_strv(field, &tmp.initrd, p);
f99fdc3e 247 else if (streq(field, "devicetree"))
bb9133bb
LP
248 r = parse_path_one(field, &tmp.device_tree, p);
249 else if (streq(field, "devicetree-overlay"))
250 r = parse_path_many(field, &tmp.device_tree_overlay, p);
251 else {
d04f0331 252 log_notice("%s:%u: Unknown line \"%s\", ignoring.", tmp.path, line, field);
7e87c7d9
ZJS
253 continue;
254 }
255 if (r < 0)
d04f0331 256 return log_error_errno(r, "%s:%u: Error while reading: %m", tmp.path, line);
7e87c7d9
ZJS
257 }
258
259 *entry = tmp;
260 tmp = (BootEntry) {};
261 return 0;
262}
263
a847b539
ZJS
264int boot_config_load_type1(
265 BootConfig *config,
266 FILE *f,
267 const char *root,
268 const char *dir,
269 const char *id) {
270 int r;
271
272 assert(config);
273 assert(f);
274 assert(root);
275 assert(dir);
276 assert(id);
277
278 if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
279 return log_oom();
280
281 r = boot_entry_load_type1(f, root, dir, id, config->entries + config->n_entries);
282 if (r < 0)
283 return r;
284
285 config->n_entries++;
286 return 0;
287}
288
7e87c7d9 289void boot_config_free(BootConfig *config) {
4fe2ba0e
LP
290 assert(config);
291
7e87c7d9
ZJS
292 free(config->default_pattern);
293 free(config->timeout);
294 free(config->editor);
c1d4e298
JJ
295 free(config->auto_entries);
296 free(config->auto_firmware);
72263375 297 free(config->console_mode);
fe5a698f 298 free(config->random_seed_mode);
d403d8f0 299 free(config->beep);
7e87c7d9
ZJS
300
301 free(config->entry_oneshot);
302 free(config->entry_default);
a78e472d 303 free(config->entry_selected);
7e87c7d9 304
9817b7db 305 for (size_t i = 0; i < config->n_entries; i++)
7e87c7d9
ZJS
306 boot_entry_free(config->entries + i);
307 free(config->entries);
d486a0ea
LP
308
309 set_free(config->inodes_seen);
7e87c7d9
ZJS
310}
311
5ba1550f 312int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
7e87c7d9
ZJS
313 unsigned line = 1;
314 int r;
315
4fe2ba0e 316 assert(config);
5ba1550f
ZJS
317 assert(file);
318 assert(path);
7e87c7d9
ZJS
319
320 for (;;) {
f99fdc3e
YW
321 _cleanup_free_ char *buf = NULL, *field = NULL;
322 const char *p;
7e87c7d9 323
5ba1550f 324 r = read_line(file, LONG_LINE_MAX, &buf);
7e87c7d9
ZJS
325 if (r == 0)
326 break;
327 if (r == -ENOBUFS)
328 return log_error_errno(r, "%s:%u: Line too long", path, line);
329 if (r < 0)
330 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
331
332 line++;
333
da8f277c
LP
334 p = strstrip(buf);
335 if (IN_SET(p[0], '#', '\0'))
7e87c7d9
ZJS
336 continue;
337
da8f277c 338 r = extract_first_word(&p, &field, NULL, 0);
f99fdc3e
YW
339 if (r < 0) {
340 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
341 continue;
342 }
343 if (r == 0) {
7e87c7d9
ZJS
344 log_warning("%s:%u: Bad syntax", path, line);
345 continue;
346 }
7e87c7d9 347
f99fdc3e 348 if (streq(field, "default"))
7e87c7d9 349 r = free_and_strdup(&config->default_pattern, p);
f99fdc3e 350 else if (streq(field, "timeout"))
7e87c7d9 351 r = free_and_strdup(&config->timeout, p);
f99fdc3e 352 else if (streq(field, "editor"))
7e87c7d9 353 r = free_and_strdup(&config->editor, p);
790f84eb 354 else if (streq(field, "auto-entries"))
c1d4e298 355 r = free_and_strdup(&config->auto_entries, p);
790f84eb 356 else if (streq(field, "auto-firmware"))
c1d4e298 357 r = free_and_strdup(&config->auto_firmware, p);
790f84eb 358 else if (streq(field, "console-mode"))
d37b0737 359 r = free_and_strdup(&config->console_mode, p);
fe5a698f
YW
360 else if (streq(field, "random-seed-mode"))
361 r = free_and_strdup(&config->random_seed_mode, p);
d403d8f0
LP
362 else if (streq(field, "beep"))
363 r = free_and_strdup(&config->beep, p);
7e87c7d9 364 else {
feb41f1f 365 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
7e87c7d9
ZJS
366 continue;
367 }
368 if (r < 0)
369 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
370 }
371
f91ed3dc 372 return 1;
7e87c7d9
ZJS
373}
374
5ba1550f
ZJS
375static int boot_loader_read_conf_path(BootConfig *config, const char *path) {
376 _cleanup_fclose_ FILE *f = NULL;
377
378 assert(config);
379 assert(path);
380
381 f = fopen(path, "re");
382 if (!f) {
383 if (errno == ENOENT)
384 return 0;
385
386 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
387 }
388
389 return boot_loader_read_conf(config, f, path);
390}
391
93bab288 392static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
20ec8f53
LP
393 int r;
394
395 assert(a);
396 assert(b);
397
398 r = CMP(!a->sort_key, !b->sort_key);
399 if (r != 0)
400 return r;
62a4b584 401
20ec8f53
LP
402 if (a->sort_key && b->sort_key) {
403 r = strcmp(a->sort_key, b->sort_key);
404 if (r != 0)
405 return r;
406
407 r = strcmp_ptr(a->machine_id, b->machine_id);
408 if (r != 0)
409 return r;
410
411 r = -strverscmp_improved(a->version, b->version);
412 if (r != 0)
413 return r;
414 }
415
62a4b584 416 return -strverscmp_improved(a->id, b->id);
7e87c7d9
ZJS
417}
418
d486a0ea
LP
419static void inode_hash_func(const struct stat *q, struct siphash *state) {
420 siphash24_compress(&q->st_dev, sizeof(q->st_dev), state);
421 siphash24_compress(&q->st_ino, sizeof(q->st_ino), state);
422}
423
424static int inode_compare_func(const struct stat *a, const struct stat *b) {
425 int r;
426
427 r = CMP(a->st_dev, b->st_dev);
428 if (r != 0)
429 return r;
430
431 return CMP(a->st_ino, b->st_ino);
432}
433
434DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
435
436static int config_check_inode_relevant_and_unseen(BootConfig *config, int fd, const char *fname) {
437 _cleanup_free_ char *d = NULL;
438 struct stat st;
439
440 assert(config);
441 assert(fd >= 0);
442 assert(fname);
443
444 /* So, here's the thing: because of the mess around /efi/ vs. /boot/ vs. /boot/efi/ it might be that
445 * people have these dirs, or subdirs of them symlinked or bind mounted, and we might end up
446 * iterating though some dirs multiple times. Let's thus rather be safe than sorry, and track the
447 * inodes we already processed: let's ignore inodes we have seen already. This should be robust
448 * against any form of symlinking or bind mounting, and effectively suppress any such duplicates. */
449
450 if (fstat(fd, &st) < 0)
451 return log_error_errno(errno, "Failed to stat('%s'): %m", fname);
452 if (!S_ISREG(st.st_mode)) {
453 log_debug("File '%s' is not a reguar file, ignoring.", fname);
454 return false;
455 }
456
457 if (set_contains(config->inodes_seen, &st)) {
458 log_debug("Inode '%s' already seen before, ignoring.", fname);
459 return false;
460 }
461 d = memdup(&st, sizeof(st));
462 if (!d)
463 return log_oom();
464 if (set_ensure_put(&config->inodes_seen, &inode_hash_ops, d) < 0)
465 return log_oom();
466
467 TAKE_PTR(d);
468 return true;
469}
470
85e17916 471static int boot_entries_find_type1(
85f4ae2f 472 BootConfig *config,
43b736a8 473 const char *root,
85f4ae2f 474 const char *dir) {
43b736a8 475
d04f0331
LP
476 _cleanup_free_ DirectoryEntries *dentries = NULL;
477 _cleanup_close_ int dir_fd = -1;
7e87c7d9 478 int r;
7e87c7d9 479
85f4ae2f 480 assert(config);
43b736a8 481 assert(root);
4fe2ba0e 482 assert(dir);
4fe2ba0e 483
d04f0331
LP
484 dir_fd = open(dir, O_DIRECTORY|O_CLOEXEC);
485 if (dir_fd < 0) {
486 if (errno == ENOENT)
487 return 0;
488
489 return log_error_errno(errno, "Failed to open '%s': %m", dir);
490 }
491
492 r = readdir_all(dir_fd, RECURSE_DIR_IGNORE_DOT, &dentries);
7e87c7d9 493 if (r < 0)
d04f0331
LP
494 return log_error_errno(r, "Failed to read directory '%s': %m", dir);
495
496 for (size_t i = 0; i < dentries->n_entries; i++) {
497 const struct dirent *de = dentries->entries[i];
498 _cleanup_fclose_ FILE *f = NULL;
499
500 if (!dirent_is_file(de))
501 continue;
502
503 if (!endswith_no_case(de->d_name, ".conf"))
504 continue;
7e87c7d9 505
d04f0331
LP
506 r = xfopenat(dir_fd, de->d_name, "re", 0, &f);
507 if (r < 0) {
508 log_warning_errno(r, "Failed to open %s/%s, ignoring: %m", dir, de->d_name);
509 continue;
510 }
511
d486a0ea
LP
512 r = config_check_inode_relevant_and_unseen(config, fileno(f), de->d_name);
513 if (r < 0)
514 return r;
515 if (r == 0) /* inode already seen or otherwise not relevant */
d04f0331 516 continue;
d486a0ea 517
a847b539
ZJS
518 r = boot_config_load_type1(config, f, root, dir, de->d_name);
519 if (r == -ENOMEM)
520 return r;
7e87c7d9
ZJS
521 }
522
7e87c7d9
ZJS
523 return 0;
524}
525
5e146a75
LP
526static int boot_entry_load_unified(
527 const char *root,
528 const char *path,
529 const char *osrelease,
530 const char *cmdline,
531 BootEntry *ret) {
532
c2caeb5d
LP
533 _cleanup_free_ char *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
534 *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
93f14ce2
LP
535 _cleanup_(boot_entry_free) BootEntry tmp = {
536 .type = BOOT_ENTRY_UNIFIED,
537 };
20ec8f53 538 const char *k, *good_name, *good_version, *good_sort_key;
5e146a75 539 _cleanup_fclose_ FILE *f = NULL;
5e146a75
LP
540 int r;
541
542 assert(root);
543 assert(path);
544 assert(osrelease);
545
546 k = path_startswith(path, root);
547 if (!k)
548 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
549
673a1e6f 550 f = fmemopen_unlocked((void*) osrelease, strlen(osrelease), "r");
5e146a75
LP
551 if (!f)
552 return log_error_errno(errno, "Failed to open os-release buffer: %m");
553
15b82eec
SM
554 r = parse_env_file(f, "os-release",
555 "PRETTY_NAME", &os_pretty_name,
c2caeb5d
LP
556 "IMAGE_ID", &os_image_id,
557 "NAME", &os_name,
15b82eec 558 "ID", &os_id,
c2caeb5d
LP
559 "IMAGE_VERSION", &os_image_version,
560 "VERSION", &os_version,
561 "VERSION_ID", &os_version_id,
562 "BUILD_ID", &os_build_id);
5e146a75
LP
563 if (r < 0)
564 return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
565
20ec8f53 566 if (!bootspec_pick_name_version_sort_key(
c2caeb5d
LP
567 os_pretty_name,
568 os_image_id,
569 os_name,
570 os_id,
571 os_image_version,
572 os_version,
573 os_version_id,
574 os_build_id,
575 &good_name,
20ec8f53
LP
576 &good_version,
577 &good_sort_key))
5e146a75
LP
578 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
579
c2caeb5d
LP
580 r = path_extract_filename(path, &tmp.id);
581 if (r < 0)
582 return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
5e146a75 583
eed7210a 584 if (!efi_loader_entry_name_valid(tmp.id))
dfc22cb4 585 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
eed7210a 586
c2caeb5d
LP
587 if (os_id && os_version_id) {
588 tmp.id_old = strjoin(os_id, "-", os_version_id);
589 if (!tmp.id_old)
590 return log_oom();
591 }
592
5e146a75
LP
593 tmp.path = strdup(path);
594 if (!tmp.path)
595 return log_oom();
596
597 tmp.root = strdup(root);
598 if (!tmp.root)
599 return log_oom();
600
601 tmp.kernel = strdup(skip_leading_chars(k, "/"));
602 if (!tmp.kernel)
603 return log_oom();
604
605 tmp.options = strv_new(skip_leading_chars(cmdline, WHITESPACE));
606 if (!tmp.options)
607 return log_oom();
608
609 delete_trailing_chars(tmp.options[0], WHITESPACE);
610
c2caeb5d
LP
611 tmp.title = strdup(good_name);
612 if (!tmp.title)
613 return log_oom();
614
20ec8f53
LP
615 tmp.sort_key = strdup(good_sort_key);
616 if (!tmp.sort_key)
617 return log_oom();
618
87c77795
VW
619 if (good_version) {
620 tmp.version = strdup(good_version);
621 if (!tmp.version)
622 return log_oom();
623 }
5e146a75
LP
624
625 *ret = tmp;
626 tmp = (BootEntry) {};
627 return 0;
628}
629
630/* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
631 * the ones we do care about and we are willing to load into memory have this size limit.) */
632#define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
633
634static int find_sections(
635 int fd,
35148e8f 636 const char *path,
5e146a75
LP
637 char **ret_osrelease,
638 char **ret_cmdline) {
639
640 _cleanup_free_ struct PeSectionHeader *sections = NULL;
641 _cleanup_free_ char *osrelease = NULL, *cmdline = NULL;
5e146a75
LP
642 ssize_t n;
643
9817b7db 644 struct DosFileHeader dos;
5e146a75
LP
645 n = pread(fd, &dos, sizeof(dos), 0);
646 if (n < 0)
35148e8f 647 return log_warning_errno(errno, "%s: Failed to read DOS header, ignoring: %m", path);
5e146a75 648 if (n != sizeof(dos))
35148e8f 649 return log_warning_errno(SYNTHETIC_ERRNO(EIO), "%s: Short read while reading DOS header, ignoring.", path);
5e146a75
LP
650
651 if (dos.Magic[0] != 'M' || dos.Magic[1] != 'Z')
35148e8f 652 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: DOS executable magic missing, ignoring.", path);
5e146a75 653
9817b7db
ZJS
654 uint64_t start = unaligned_read_le32(&dos.ExeHeader);
655
656 struct PeHeader pe;
5e146a75
LP
657 n = pread(fd, &pe, sizeof(pe), start);
658 if (n < 0)
35148e8f 659 return log_warning_errno(errno, "%s: Failed to read PE header, ignoring: %m", path);
5e146a75 660 if (n != sizeof(pe))
35148e8f 661 return log_warning_errno(SYNTHETIC_ERRNO(EIO), "%s: Short read while reading PE header, ignoring.", path);
5e146a75
LP
662
663 if (pe.Magic[0] != 'P' || pe.Magic[1] != 'E' || pe.Magic[2] != 0 || pe.Magic[3] != 0)
35148e8f 664 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: PE executable magic missing, ignoring.", path);
5e146a75 665
9817b7db 666 size_t n_sections = unaligned_read_le16(&pe.FileHeader.NumberOfSections);
5e146a75 667 if (n_sections > 96)
35148e8f 668 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: PE header has too many sections, ignoring.", path);
5e146a75
LP
669
670 sections = new(struct PeSectionHeader, n_sections);
671 if (!sections)
672 return log_oom();
673
674 n = pread(fd, sections,
675 n_sections * sizeof(struct PeSectionHeader),
676 start + sizeof(pe) + unaligned_read_le16(&pe.FileHeader.SizeOfOptionalHeader));
677 if (n < 0)
35148e8f 678 return log_warning_errno(errno, "%s: Failed to read section data, ignoring: %m", path);
5e146a75 679 if ((size_t) n != n_sections * sizeof(struct PeSectionHeader))
35148e8f 680 return log_warning_errno(SYNTHETIC_ERRNO(EIO), "%s: Short read while reading sections, ignoring.", path);
5e146a75 681
9817b7db 682 for (size_t i = 0; i < n_sections; i++) {
5e146a75
LP
683 _cleanup_free_ char *k = NULL;
684 uint32_t offset, size;
685 char **b;
686
687 if (strneq((char*) sections[i].Name, ".osrel", sizeof(sections[i].Name)))
688 b = &osrelease;
689 else if (strneq((char*) sections[i].Name, ".cmdline", sizeof(sections[i].Name)))
690 b = &cmdline;
691 else
692 continue;
693
694 if (*b)
35148e8f 695 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: Duplicate section %s, ignoring.", path, sections[i].Name);
5e146a75
LP
696
697 offset = unaligned_read_le32(&sections[i].PointerToRawData);
698 size = unaligned_read_le32(&sections[i].VirtualSize);
699
700 if (size > PE_SECTION_SIZE_MAX)
35148e8f 701 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: Section %s too large, ignoring.", path, sections[i].Name);
5e146a75
LP
702
703 k = new(char, size+1);
704 if (!k)
705 return log_oom();
706
707 n = pread(fd, k, size, offset);
708 if (n < 0)
35148e8f 709 return log_warning_errno(errno, "%s: Failed to read section payload, ignoring: %m", path);
a75fcef8 710 if ((size_t) n != size)
35148e8f 711 return log_warning_errno(SYNTHETIC_ERRNO(EIO), "%s: Short read while reading section payload, ignoring:", path);
5e146a75
LP
712
713 /* Allow one trailing NUL byte, but nothing more. */
714 if (size > 0 && memchr(k, 0, size - 1))
35148e8f 715 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: Section contains embedded NUL byte, ignoring.", path);
5e146a75
LP
716
717 k[size] = 0;
718 *b = TAKE_PTR(k);
719 }
720
721 if (!osrelease)
35148e8f 722 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: Image lacks .osrel section, ignoring.", path);
5e146a75
LP
723
724 if (ret_osrelease)
725 *ret_osrelease = TAKE_PTR(osrelease);
726 if (ret_cmdline)
727 *ret_cmdline = TAKE_PTR(cmdline);
728
729 return 0;
730}
731
732static int boot_entries_find_unified(
85f4ae2f 733 BootConfig *config,
5e146a75 734 const char *root,
85f4ae2f 735 const char *dir) {
5e146a75
LP
736
737 _cleanup_(closedirp) DIR *d = NULL;
5e146a75
LP
738 int r;
739
85f4ae2f 740 assert(config);
5e146a75 741 assert(dir);
5e146a75
LP
742
743 d = opendir(dir);
744 if (!d) {
745 if (errno == ENOENT)
746 return 0;
747
748 return log_error_errno(errno, "Failed to open %s: %m", dir);
749 }
750
751 FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", dir)) {
752 _cleanup_free_ char *j = NULL, *osrelease = NULL, *cmdline = NULL;
753 _cleanup_close_ int fd = -1;
754
755 if (!dirent_is_file(de))
756 continue;
757
758 if (!endswith_no_case(de->d_name, ".efi"))
759 continue;
760
85f4ae2f 761 if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
5e146a75
LP
762 return log_oom();
763
764 fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
765 if (fd < 0) {
766 log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", dir, de->d_name);
767 continue;
768 }
769
d486a0ea
LP
770 r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
771 if (r < 0)
772 return r;
773 if (r == 0) /* inode already seen or otherwise not relevant */
5e146a75 774 continue;
5e146a75 775
5e146a75
LP
776 j = path_join(dir, de->d_name);
777 if (!j)
778 return log_oom();
779
35148e8f
JJ
780 if (find_sections(fd, j, &osrelease, &cmdline) < 0)
781 continue;
782
85f4ae2f 783 r = boot_entry_load_unified(root, j, osrelease, cmdline, config->entries + config->n_entries);
5e146a75
LP
784 if (r < 0)
785 continue;
786
85f4ae2f 787 config->n_entries++;
5e146a75
LP
788 }
789
5e146a75
LP
790 return 0;
791}
792
bb682057 793static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
64f05708
ZJS
794 bool non_unique = false;
795
4fe2ba0e
LP
796 assert(entries || n_entries == 0);
797 assert(arr || n_entries == 0);
798
d5ac1d4e 799 for (size_t i = 0; i < n_entries; i++)
64f05708
ZJS
800 arr[i] = false;
801
d5ac1d4e
LP
802 for (size_t i = 0; i < n_entries; i++)
803 for (size_t j = 0; j < n_entries; j++)
64f05708
ZJS
804 if (i != j && streq(boot_entry_title(entries + i),
805 boot_entry_title(entries + j)))
806 non_unique = arr[i] = arr[j] = true;
807
808 return non_unique;
809}
810
811static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
d5ac1d4e 812 _cleanup_free_ bool *arr = NULL;
64f05708 813 char *s;
64f05708 814
4fe2ba0e
LP
815 assert(entries || n_entries == 0);
816
d5ac1d4e
LP
817 if (n_entries == 0)
818 return 0;
819
820 arr = new(bool, n_entries);
821 if (!arr)
822 return -ENOMEM;
823
64f05708
ZJS
824 /* Find _all_ non-unique titles */
825 if (!find_nonunique(entries, n_entries, arr))
826 return 0;
827
828 /* Add version to non-unique titles */
d5ac1d4e 829 for (size_t i = 0; i < n_entries; i++)
64f05708 830 if (arr[i] && entries[i].version) {
d5ac1d4e 831 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version) < 0)
64f05708
ZJS
832 return -ENOMEM;
833
834 free_and_replace(entries[i].show_title, s);
835 }
836
837 if (!find_nonunique(entries, n_entries, arr))
838 return 0;
839
840 /* Add machine-id to non-unique titles */
d5ac1d4e 841 for (size_t i = 0; i < n_entries; i++)
64f05708 842 if (arr[i] && entries[i].machine_id) {
d5ac1d4e 843 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id) < 0)
64f05708
ZJS
844 return -ENOMEM;
845
846 free_and_replace(entries[i].show_title, s);
847 }
848
849 if (!find_nonunique(entries, n_entries, arr))
850 return 0;
851
852 /* Add file name to non-unique titles */
d5ac1d4e 853 for (size_t i = 0; i < n_entries; i++)
64f05708 854 if (arr[i]) {
d5ac1d4e 855 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id) < 0)
64f05708
ZJS
856 return -ENOMEM;
857
858 free_and_replace(entries[i].show_title, s);
859 }
860
861 return 0;
862}
863
20ec8f53
LP
864static int boot_config_find(const BootConfig *config, const char *id) {
865 assert(config);
866
867 if (!id)
868 return -1;
869
870 for (size_t i = 0; i < config->n_entries; i++)
871 if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
872 return i;
873
874 return -1;
875}
876
ad1afd60 877static int boot_entries_select_default(const BootConfig *config) {
7e87c7d9
ZJS
878 int i;
879
4fe2ba0e 880 assert(config);
388d2993
ZJS
881 assert(config->entries || config->n_entries == 0);
882
883 if (config->n_entries == 0) {
884 log_debug("Found no default boot entry :(");
885 return -1; /* -1 means "no default" */
886 }
4fe2ba0e 887
20ec8f53
LP
888 if (config->entry_oneshot) {
889 i = boot_config_find(config, config->entry_oneshot);
890 if (i >= 0) {
891 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
892 config->entries[i].id);
893 return i;
894 }
895 }
896
897 if (config->entry_default) {
898 i = boot_config_find(config, config->entry_default);
899 if (i >= 0) {
900 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
901 config->entries[i].id);
902 return i;
903 }
904 }
905
906 if (config->default_pattern) {
907 i = boot_config_find(config, config->default_pattern);
908 if (i >= 0) {
909 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
910 config->entries[i].id, config->default_pattern);
911 return i;
912 }
913 }
914
915 log_debug("Found default: first entry \"%s\"", config->entries[0].id);
916 return 0;
7e87c7d9
ZJS
917}
918
a78e472d 919static int boot_entries_select_selected(const BootConfig *config) {
a78e472d
LP
920 assert(config);
921 assert(config->entries || config->n_entries == 0);
922
923 if (!config->entry_selected || config->n_entries == 0)
924 return -1;
925
20ec8f53 926 return boot_config_find(config, config->entry_selected);
a78e472d
LP
927}
928
80a2381d 929static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) {
a78e472d
LP
930 int r;
931
932 assert(config);
933
80a2381d 934 if (skip_efivars || !is_efi_boot())
a78e472d
LP
935 return 0;
936
937 /* Loads the three "pointers" to boot loader entries from their EFI variables */
938
939 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot), &config->entry_oneshot);
92067ab6
LP
940 if (r == -ENOMEM)
941 return log_oom();
942 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
943 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m");
a78e472d
LP
944
945 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryDefault), &config->entry_default);
92067ab6
LP
946 if (r == -ENOMEM)
947 return log_oom();
948 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
949 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m");
a78e472d
LP
950
951 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntrySelected), &config->entry_selected);
92067ab6
LP
952 if (r == -ENOMEM)
953 return log_oom();
954 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
955 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m");
a78e472d
LP
956
957 return 1;
958}
959
80a2381d 960int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) {
f7a7a5e2
LP
961 int r;
962
963 assert(config);
964
80a2381d 965 r = boot_load_efi_entry_pointers(config, skip_efivars);
f7a7a5e2
LP
966 if (r < 0)
967 return r;
968
969 config->default_entry = boot_entries_select_default(config);
970 config->selected_entry = boot_entries_select_selected(config);
971
972 return 0;
973}
974
5ba1550f
ZJS
975int boot_config_finalize(BootConfig *config) {
976 int r;
977
978 typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
979
980 r = boot_entries_uniquify(config->entries, config->n_entries);
981 if (r < 0)
982 return log_error_errno(r, "Failed to uniquify boot entries: %m");
983
984 return 0;
985}
986
af9ae750
LP
987int boot_config_load(
988 BootConfig *config,
a2f8664e 989 const char *esp_path,
af9ae750 990 const char *xbootldr_path) {
a2f8664e 991
7e87c7d9
ZJS
992 const char *p;
993 int r;
994
4fe2ba0e
LP
995 assert(config);
996
a2f8664e
LP
997 if (esp_path) {
998 p = strjoina(esp_path, "/loader/loader.conf");
5ba1550f 999 r = boot_loader_read_conf_path(config, p);
a2f8664e
LP
1000 if (r < 0)
1001 return r;
7e87c7d9 1002
a2f8664e 1003 p = strjoina(esp_path, "/loader/entries");
85e17916 1004 r = boot_entries_find_type1(config, esp_path, p);
a2f8664e
LP
1005 if (r < 0)
1006 return r;
5e146a75
LP
1007
1008 p = strjoina(esp_path, "/EFI/Linux/");
85f4ae2f 1009 r = boot_entries_find_unified(config, esp_path, p);
5e146a75
LP
1010 if (r < 0)
1011 return r;
a2f8664e
LP
1012 }
1013
1014 if (xbootldr_path) {
1015 p = strjoina(xbootldr_path, "/loader/entries");
85e17916 1016 r = boot_entries_find_type1(config, xbootldr_path, p);
a2f8664e
LP
1017 if (r < 0)
1018 return r;
5e146a75
LP
1019
1020 p = strjoina(xbootldr_path, "/EFI/Linux/");
85f4ae2f 1021 r = boot_entries_find_unified(config, xbootldr_path, p);
5e146a75
LP
1022 if (r < 0)
1023 return r;
a2f8664e 1024 }
7e87c7d9 1025
5ba1550f 1026 return boot_config_finalize(config);
7e87c7d9 1027}
af918182 1028
af9ae750
LP
1029int boot_config_load_auto(
1030 BootConfig *config,
eea4ce1e 1031 const char *override_esp_path,
af9ae750 1032 const char *override_xbootldr_path) {
eea4ce1e
LP
1033
1034 _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
f63b5ad9 1035 dev_t esp_devid = 0, xbootldr_devid = 0;
eea4ce1e
LP
1036 int r;
1037
1038 assert(config);
1039
1040 /* This function is similar to boot_entries_load_config(), however we automatically search for the
1041 * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
1042 * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
1043 * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
1044 * it). This allows other boot loaders to pass boot loader entry information to our tools if they
1045 * want to. */
1046
1047 if (!override_esp_path && !override_xbootldr_path) {
f40999f8 1048 if (access("/run/boot-loader-entries/", F_OK) >= 0)
af9ae750 1049 return boot_config_load(config, "/run/boot-loader-entries/", NULL);
eea4ce1e 1050
f40999f8
ZJS
1051 if (errno != ENOENT)
1052 return log_error_errno(errno,
1053 "Failed to determine whether /run/boot-loader-entries/ exists: %m");
eea4ce1e
LP
1054 }
1055
80a2381d 1056 r = find_esp_and_warn(NULL, override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid);
cc5957dc 1057 if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
eea4ce1e
LP
1058 return r;
1059
80a2381d 1060 r = find_xbootldr_and_warn(NULL, override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid);
eea4ce1e
LP
1061 if (r < 0 && r != -ENOKEY)
1062 return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
1063
f63b5ad9 1064 /* If both paths actually refer to the same inode, suppress the xbootldr path */
7176f06c 1065 if (esp_where && xbootldr_where && devnum_set_and_equal(esp_devid, xbootldr_devid))
f63b5ad9
LP
1066 xbootldr_where = mfree(xbootldr_where);
1067
af9ae750 1068 return boot_config_load(config, esp_where, xbootldr_where);
eea4ce1e
LP
1069}
1070
af9ae750 1071int boot_config_augment_from_loader(
d4bd786d 1072 BootConfig *config,
9951736b
LP
1073 char **found_by_loader,
1074 bool only_auto) {
d4bd786d
LP
1075
1076 static const char *const title_table[] = {
93f14ce2
LP
1077 /* Pretty names for a few well-known automatically discovered entries. */
1078 "auto-osx", "macOS",
1079 "auto-windows", "Windows Boot Manager",
1080 "auto-efi-shell", "EFI Shell",
1081 "auto-efi-default", "EFI Default Loader",
1082 "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
d4f72d10 1083 NULL,
93f14ce2
LP
1084 };
1085
93f14ce2
LP
1086 assert(config);
1087
1088 /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
1089 * already included there. */
1090
93f14ce2 1091 STRV_FOREACH(i, found_by_loader) {
bb682057 1092 BootEntry *existing;
ce4c4f81 1093 _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
93f14ce2 1094
bb682057
LP
1095 existing = boot_config_find_entry(config, *i);
1096 if (existing) {
1097 existing->reported_by_loader = true;
93f14ce2 1098 continue;
bb682057 1099 }
93f14ce2 1100
9951736b 1101 if (only_auto && !startswith(*i, "auto-"))
93f14ce2
LP
1102 continue;
1103
1104 c = strdup(*i);
1105 if (!c)
1106 return log_oom();
1107
2034c8b8 1108 STRV_FOREACH_PAIR(a, b, title_table)
93f14ce2
LP
1109 if (streq(*a, *i)) {
1110 t = strdup(*b);
1111 if (!t)
1112 return log_oom();
1113 break;
1114 }
1115
e6f055cb 1116 p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderEntries)));
ce4c4f81
ZJS
1117 if (!p)
1118 return log_oom();
1119
319a4f4b 1120 if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
93f14ce2
LP
1121 return log_oom();
1122
1123 config->entries[config->n_entries++] = (BootEntry) {
bb682057 1124 .type = startswith(*i, "auto-") ? BOOT_ENTRY_LOADER_AUTO : BOOT_ENTRY_LOADER,
93f14ce2
LP
1125 .id = TAKE_PTR(c),
1126 .title = TAKE_PTR(t),
ce4c4f81 1127 .path = TAKE_PTR(p),
bb682057 1128 .reported_by_loader = true,
93f14ce2
LP
1129 };
1130 }
1131
1132 return 0;
1133}
3f8e42c0 1134
432ce537
ZJS
1135static int boot_entry_file_check(const char *root, const char *p) {
1136 _cleanup_free_ char *path = NULL;
1137
1138 path = path_join(root, p);
1139 if (!path)
1140 return log_oom();
1141
1142 return RET_NERRNO(access(path, F_OK));
1143}
1144
3f8e42c0
LP
1145BootEntry* boot_config_find_entry(BootConfig *config, const char *id) {
1146 assert(config);
1147 assert(id);
1148
1149 for (size_t j = 0; j < config->n_entries; j++)
1150 if (streq_ptr(config->entries[j].id, id) ||
1151 streq_ptr(config->entries[j].id_old, id))
1152 return config->entries + j;
1153
1154 return NULL;
1155}
432ce537
ZJS
1156
1157static void boot_entry_file_list(const char *field, const char *root, const char *p, int *ret_status) {
1158 int status = boot_entry_file_check(root, p);
1159
1160 printf("%13s%s ", strempty(field), field ? ":" : " ");
1161 if (status < 0) {
1162 errno = -status;
1163 printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal());
1164 } else
1165 printf("%s\n", p);
1166
1167 if (*ret_status == 0 && status < 0)
1168 *ret_status = status;
1169}
1170
1171int show_boot_entry(
1172 const BootEntry *e,
1173 bool show_as_default,
1174 bool show_as_selected,
1175 bool show_reported) {
1176
1177 int status = 0;
1178
1179 /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
1180 boot entry itself. */
1181
1182 assert(e);
1183
1184 printf(" type: %s\n",
1185 boot_entry_type_to_string(e->type));
1186
1187 printf(" title: %s%s%s",
1188 ansi_highlight(), boot_entry_title(e), ansi_normal());
1189
1190 if (show_as_default)
1191 printf(" %s(default)%s",
1192 ansi_highlight_green(), ansi_normal());
1193
1194 if (show_as_selected)
1195 printf(" %s(selected)%s",
1196 ansi_highlight_magenta(), ansi_normal());
1197
1198 if (show_reported) {
1199 if (e->type == BOOT_ENTRY_LOADER)
1200 printf(" %s(reported/absent)%s",
1201 ansi_highlight_red(), ansi_normal());
1202 else if (!e->reported_by_loader && e->type != BOOT_ENTRY_LOADER_AUTO)
1203 printf(" %s(not reported/new)%s",
1204 ansi_highlight_green(), ansi_normal());
1205 }
1206
1207 putchar('\n');
1208
1209 if (e->id)
1210 printf(" id: %s\n", e->id);
1211 if (e->path) {
1212 _cleanup_free_ char *link = NULL;
1213
1214 /* Let's urlify the link to make it easy to view in an editor, but only if it is a text
1215 * file. Unified images are binary ELFs, and EFI variables are not pure text either. */
1216 if (e->type == BOOT_ENTRY_CONF)
1217 (void) terminal_urlify_path(e->path, NULL, &link);
1218
1219 printf(" source: %s\n", link ?: e->path);
1220 }
1221 if (e->sort_key)
1222 printf(" sort-key: %s\n", e->sort_key);
1223 if (e->version)
1224 printf(" version: %s\n", e->version);
1225 if (e->machine_id)
1226 printf(" machine-id: %s\n", e->machine_id);
1227 if (e->architecture)
1228 printf(" architecture: %s\n", e->architecture);
1229 if (e->kernel)
1230 boot_entry_file_list("linux", e->root, e->kernel, &status);
1231
1232 STRV_FOREACH(s, e->initrd)
1233 boot_entry_file_list(s == e->initrd ? "initrd" : NULL,
1234 e->root,
1235 *s,
1236 &status);
1237
1238 if (!strv_isempty(e->options)) {
1239 _cleanup_free_ char *t = NULL, *t2 = NULL;
1240 _cleanup_strv_free_ char **ts = NULL;
1241
1242 t = strv_join(e->options, " ");
1243 if (!t)
1244 return log_oom();
1245
1246 ts = strv_split_newlines(t);
1247 if (!ts)
1248 return log_oom();
1249
1250 t2 = strv_join(ts, "\n ");
1251 if (!t2)
1252 return log_oom();
1253
1254 printf(" options: %s\n", t2);
1255 }
1256
1257 if (e->device_tree)
1258 boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
1259
1260 STRV_FOREACH(s, e->device_tree_overlay)
1261 boot_entry_file_list(s == e->device_tree_overlay ? "devicetree-overlay" : NULL,
1262 e->root,
1263 *s,
1264 &status);
1265
1266 return -status;
1267}
1268
1269int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
1270 int r;
1271
1272 if (!FLAGS_SET(json_format, JSON_FORMAT_OFF)) {
1273 for (size_t i = 0; i < config->n_entries; i++) {
1274 _cleanup_free_ char *opts = NULL;
1275 const BootEntry *e = config->entries + i;
1276 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
1277
1278 if (!strv_isempty(e->options)) {
1279 opts = strv_join(e->options, " ");
1280 if (!opts)
1281 return log_oom();
1282 }
1283
1284 r = json_build(&v, JSON_BUILD_OBJECT(
1285 JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)),
1286 JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)),
1287 JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)),
1288 JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)),
1289 JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))),
1290 JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)),
1291 JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)),
1292 JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)),
1293 JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)),
1294 JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)),
1295 JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)),
1296 JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)),
1297 JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)),
1298 JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)),
1299 JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay))));
1300 if (r < 0)
1301 return log_oom();
1302
1303 json_variant_dump(v, json_format, stdout, NULL);
1304 }
1305
1306 } else {
1307 printf("Boot Loader Entries:\n");
1308
1309 for (size_t n = 0; n < config->n_entries; n++) {
1310 r = show_boot_entry(
1311 config->entries + n,
1312 /* show_as_default= */ n == (size_t) config->default_entry,
1313 /* show_as_selected= */ n == (size_t) config->selected_entry,
1314 /* show_discovered= */ true);
1315 if (r < 0)
1316 return r;
1317
1318 if (n+1 < config->n_entries)
1319 putchar('\n');
1320 }
1321 }
1322
1323 return 0;
1324}