1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "alloc-util.h"
10 #include "bootspec-fundamental.h"
12 #include "devnum-util.h"
13 #include "dirent-util.h"
14 #include "efi-loader.h"
17 #include "extract-word.h"
22 #include "parse-util.h"
23 #include "path-util.h"
24 #include "pe-binary.h"
25 #include "pretty-print.h"
26 #include "recurse-dir.h"
28 #include "sort-util.h"
29 #include "stat-util.h"
30 #include "string-table.h"
31 #include "string-util.h"
35 static const char* const boot_entry_type_table
[_BOOT_ENTRY_TYPE_MAX
] = {
36 [BOOT_ENTRY_CONF
] = "Boot Loader Specification Type #1 (.conf)",
37 [BOOT_ENTRY_UNIFIED
] = "Boot Loader Specification Type #2 (.efi)",
38 [BOOT_ENTRY_LOADER
] = "Reported by Boot Loader",
39 [BOOT_ENTRY_LOADER_AUTO
] = "Automatic",
42 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type
, BootEntryType
);
44 static const char* const boot_entry_type_json_table
[_BOOT_ENTRY_TYPE_MAX
] = {
45 [BOOT_ENTRY_CONF
] = "type1",
46 [BOOT_ENTRY_UNIFIED
] = "type2",
47 [BOOT_ENTRY_LOADER
] = "loader",
48 [BOOT_ENTRY_LOADER_AUTO
] = "auto",
51 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_json
, BootEntryType
);
53 static const char* const boot_entry_source_table
[_BOOT_ENTRY_SOURCE_MAX
] = {
54 [BOOT_ENTRY_ESP
] = "EFI System Partition",
55 [BOOT_ENTRY_XBOOTLDR
] = "Extended Boot Loader Partition",
58 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source
, BootEntrySource
);
60 static const char* const boot_entry_source_json_table
[_BOOT_ENTRY_SOURCE_MAX
] = {
61 [BOOT_ENTRY_ESP
] = "esp",
62 [BOOT_ENTRY_XBOOTLDR
] = "xbootldr",
65 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source_json
, BootEntrySource
);
67 static void boot_entry_addons_done(BootEntryAddons
*addons
) {
70 FOREACH_ARRAY(addon
, addons
->items
, addons
->n_items
) {
72 free(addon
->location
);
74 addons
->items
= mfree(addons
->items
);
78 static void boot_entry_free(BootEntry
*entry
) {
83 free(entry
->id_without_profile
);
87 free(entry
->show_title
);
88 free(entry
->sort_key
);
90 free(entry
->machine_id
);
91 free(entry
->architecture
);
92 strv_free(entry
->options
);
93 boot_entry_addons_done(&entry
->local_addons
);
96 strv_free(entry
->initrd
);
97 free(entry
->device_tree
);
98 strv_free(entry
->device_tree_overlay
);
101 static int mangle_path(
108 _cleanup_free_
char *c
= NULL
;
114 /* Spec leaves open if prefixed with "/" or not, let's normalize that */
115 c
= path_make_absolute(p
, "/");
119 /* We only reference files, never directories */
120 if (endswith(c
, "/")) {
121 log_syntax(NULL
, LOG_WARNING
, fname
, line
, 0, "Path in field '%s' has trailing slash, ignoring: %s", field
, c
);
126 /* Remove duplicate "/" */
129 /* No ".." or "." or so */
130 if (!path_is_normalized(c
)) {
131 log_syntax(NULL
, LOG_WARNING
, fname
, line
, 0, "Path in field '%s' is not normalized, ignoring: %s", field
, c
);
140 static int parse_path_one(
147 _cleanup_free_
char *c
= NULL
;
154 r
= mangle_path(fname
, line
, field
, p
, &c
);
158 return free_and_replace(*s
, c
);
161 static int parse_path_strv(
175 r
= mangle_path(fname
, line
, field
, p
, &c
);
179 return strv_consume(s
, c
);
182 static int parse_path_many(
189 _cleanup_strv_free_
char **l
= NULL
, **f
= NULL
;
192 l
= strv_split(p
, NULL
);
199 r
= mangle_path(fname
, line
, field
, *i
, &c
);
205 r
= strv_consume(&f
, c
);
210 return strv_extend_strv_consume(s
, TAKE_PTR(f
), /* filter_duplicates= */ false);
213 static int parse_tries(const char *fname
, const char **p
, unsigned *ret
) {
214 _cleanup_free_
char *d
= NULL
;
224 n
= strspn(*p
, DIGITS
);
234 r
= safe_atou_full(d
, 10, &tries
);
235 if (r
>= 0 && tries
> INT_MAX
) /* sd-boot allows INT_MAX, let's use the same limit */
238 return log_error_errno(r
, "Failed to parse tries counter of filename '%s': %m", fname
);
245 int boot_filename_extract_tries(
248 unsigned *ret_tries_left
,
249 unsigned *ret_tries_done
) {
251 unsigned tries_left
= UINT_MAX
, tries_done
= UINT_MAX
;
252 _cleanup_free_
char *stripped
= NULL
;
253 const char *p
, *suffix
, *m
;
257 assert(ret_stripped
);
258 assert(ret_tries_left
);
259 assert(ret_tries_done
);
261 /* Be liberal with suffix, only insist on a dot. After all we want to cover any capitalization here
262 * (vfat is case insensitive after all), and at least .efi and .conf as suffix. */
263 suffix
= strrchr(fname
, '.');
267 p
= m
= memrchr(fname
, '+', suffix
- fname
);
272 r
= parse_tries(fname
, &p
, &tries_left
);
281 r
= parse_tries(fname
, &p
, &tries_done
);
291 stripped
= strndup(fname
, m
- fname
);
295 if (!strextend(&stripped
, suffix
))
298 *ret_stripped
= TAKE_PTR(stripped
);
299 *ret_tries_left
= tries_left
;
300 *ret_tries_done
= tries_done
;
305 stripped
= strdup(fname
);
309 *ret_stripped
= TAKE_PTR(stripped
);
310 *ret_tries_left
= *ret_tries_done
= UINT_MAX
;
314 static int boot_entry_load_type1(
317 const BootEntrySource source
,
322 _cleanup_(boot_entry_free
) BootEntry tmp
= BOOT_ENTRY_INIT(BOOT_ENTRY_CONF
, source
);
332 /* Loads a Type #1 boot menu entry from the specified FILE* object */
334 r
= boot_filename_extract_tries(fname
, &tmp
.id
, &tmp
.tries_left
, &tmp
.tries_done
);
338 if (!efi_loader_entry_name_valid(tmp
.id
))
339 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry name: %s", fname
);
341 c
= endswith_no_case(tmp
.id
, ".conf");
343 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry file suffix: %s", fname
);
345 tmp
.id_old
= strndup(tmp
.id
, c
- tmp
.id
); /* Without .conf suffix */
349 tmp
.path
= path_join(dir
, fname
);
353 tmp
.root
= strdup(root
);
357 for (unsigned line
= 1;; line
++) {
358 _cleanup_free_
char *buf
= NULL
, *field
= NULL
;
360 r
= read_stripped_line(f
, LONG_LINE_MAX
, &buf
);
362 return log_syntax(NULL
, LOG_ERR
, tmp
.path
, line
, r
, "Line too long.");
364 return log_syntax(NULL
, LOG_ERR
, tmp
.path
, line
, r
, "Error while reading: %m");
368 if (IN_SET(buf
[0], '#', '\0'))
372 r
= extract_first_word(&p
, &field
, NULL
, 0);
374 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, r
, "Failed to parse, ignoring line: %m");
378 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, 0, "Bad syntax, ignoring line.");
383 /* Some fields can reasonably have an empty value. In other cases warn. */
384 if (!STR_IN_SET(field
, "options", "devicetree-overlay"))
385 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, 0, "Field '%s' without value, ignoring line.", field
);
390 if (streq(field
, "title"))
391 r
= free_and_strdup(&tmp
.title
, p
);
392 else if (streq(field
, "sort-key"))
393 r
= free_and_strdup(&tmp
.sort_key
, p
);
394 else if (streq(field
, "version"))
395 r
= free_and_strdup(&tmp
.version
, p
);
396 else if (streq(field
, "machine-id"))
397 r
= free_and_strdup(&tmp
.machine_id
, p
);
398 else if (streq(field
, "architecture"))
399 r
= free_and_strdup(&tmp
.architecture
, p
);
400 else if (streq(field
, "options"))
401 r
= strv_extend(&tmp
.options
, p
);
402 else if (streq(field
, "linux"))
403 r
= parse_path_one(tmp
.path
, line
, field
, &tmp
.kernel
, p
);
404 else if (streq(field
, "efi"))
405 r
= parse_path_one(tmp
.path
, line
, field
, &tmp
.efi
, p
);
406 else if (streq(field
, "initrd"))
407 r
= parse_path_strv(tmp
.path
, line
, field
, &tmp
.initrd
, p
);
408 else if (streq(field
, "devicetree"))
409 r
= parse_path_one(tmp
.path
, line
, field
, &tmp
.device_tree
, p
);
410 else if (streq(field
, "devicetree-overlay"))
411 r
= parse_path_many(tmp
.path
, line
, field
, &tmp
.device_tree_overlay
, p
);
413 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, 0, "Unknown line '%s', ignoring.", field
);
417 return log_syntax(NULL
, LOG_ERR
, tmp
.path
, line
, r
, "Error while parsing: %m");
420 *ret
= TAKE_STRUCT(tmp
);
424 int boot_config_load_type1(
428 const BootEntrySource source
,
439 if (!GREEDY_REALLOC(config
->entries
, config
->n_entries
+ 1))
442 BootEntry
*entry
= config
->entries
+ config
->n_entries
;
444 r
= boot_entry_load_type1(f
, root
, source
, dir
, fname
, entry
);
449 entry
->global_addons
= &config
->global_addons
[source
];
454 void boot_config_free(BootConfig
*config
) {
457 free(config
->default_pattern
);
459 free(config
->entry_oneshot
);
460 free(config
->entry_default
);
461 free(config
->entry_selected
);
462 free(config
->entry_sysfail
);
464 FOREACH_ARRAY(i
, config
->entries
, config
->n_entries
)
466 free(config
->entries
);
468 FOREACH_ELEMENT(i
, config
->global_addons
)
469 boot_entry_addons_done(i
);
471 set_free(config
->inodes_seen
);
474 int boot_loader_read_conf(BootConfig
*config
, FILE *file
, const char *path
) {
481 for (unsigned line
= 1;; line
++) {
482 _cleanup_free_
char *buf
= NULL
, *field
= NULL
;
484 r
= read_stripped_line(file
, LONG_LINE_MAX
, &buf
);
486 return log_syntax(NULL
, LOG_ERR
, path
, line
, r
, "Line too long.");
488 return log_syntax(NULL
, LOG_ERR
, path
, line
, r
, "Error while reading: %m");
492 if (IN_SET(buf
[0], '#', '\0'))
496 r
= extract_first_word(&p
, &field
, NULL
, 0);
498 log_syntax(NULL
, LOG_WARNING
, path
, line
, r
, "Failed to parse, ignoring line: %m");
502 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "Bad syntax, ignoring line.");
506 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "Field '%s' without value, ignoring line.", field
);
510 if (streq(field
, "default"))
511 r
= free_and_strdup(&config
->default_pattern
, p
);
512 else if (STR_IN_SET(field
, "timeout", "editor", "auto-entries", "auto-firmware",
513 "auto-poweroff", "auto-reboot", "beep", "reboot-for-bitlocker",
514 "secure-boot-enroll", "console-mode"))
515 r
= 0; /* we don't parse these in userspace, but they are OK */
517 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "Unknown line '%s', ignoring.", field
);
521 return log_syntax(NULL
, LOG_ERR
, path
, line
, r
, "Error while parsing: %m");
527 static int boot_loader_read_conf_path(BootConfig
*config
, const char *root
, const char *path
) {
528 _cleanup_free_
char *full
= NULL
;
529 _cleanup_fclose_
FILE *f
= NULL
;
535 r
= chase_and_fopen_unlocked(path
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, "re", &full
, &f
);
539 return log_error_errno(r
, "Failed to open '%s/%s': %m", root
, skip_leading_slash(path
));
541 return boot_loader_read_conf(config
, f
, full
);
544 static int boot_entry_compare(const BootEntry
*a
, const BootEntry
*b
) {
550 /* This mimics a function of the same name in src/boot/efi/sd-boot.c */
552 r
= CMP(a
->tries_left
== 0, b
->tries_left
== 0);
556 r
= CMP(!a
->sort_key
, !b
->sort_key
);
560 if (a
->sort_key
&& b
->sort_key
) {
561 r
= strcmp(a
->sort_key
, b
->sort_key
);
565 r
= strcmp_ptr(a
->machine_id
, b
->machine_id
);
569 r
= -strverscmp_improved(a
->version
, b
->version
);
574 r
= -strverscmp_improved(a
->id_without_profile
?: a
->id
, b
->id_without_profile
?: b
->id
);
578 if (a
->id_without_profile
&& b
->id_without_profile
) {
579 /* The strverscmp_improved() call above already established that we are talking about the
580 * same image here, hence order by profile, if there is one */
581 r
= CMP(a
->profile
, b
->profile
);
586 if (a
->tries_left
!= UINT_MAX
|| b
->tries_left
!= UINT_MAX
)
589 r
= -CMP(a
->tries_left
, b
->tries_left
);
593 return CMP(a
->tries_done
, b
->tries_done
);
596 static int config_check_inode_relevant_and_unseen(BootConfig
*config
, int fd
, const char *fname
) {
597 _cleanup_free_
char *d
= NULL
;
604 /* So, here's the thing: because of the mess around /efi/ vs. /boot/ vs. /boot/efi/ it might be that
605 * people have these dirs, or subdirs of them symlinked or bind mounted, and we might end up
606 * iterating though some dirs multiple times. Let's thus rather be safe than sorry, and track the
607 * inodes we already processed: let's ignore inodes we have seen already. This should be robust
608 * against any form of symlinking or bind mounting, and effectively suppress any such duplicates. */
610 if (fstat(fd
, &st
) < 0)
611 return log_error_errno(errno
, "Failed to stat('%s'): %m", fname
);
612 if (!S_ISREG(st
.st_mode
)) {
613 log_debug("File '%s' is not a regular file, ignoring.", fname
);
617 if (set_contains(config
->inodes_seen
, &st
)) {
618 log_debug("Inode '%s' already seen before, ignoring.", fname
);
622 d
= memdup(&st
, sizeof(st
));
626 if (set_ensure_consume(&config
->inodes_seen
, &inode_hash_ops
, TAKE_PTR(d
)) < 0)
632 static int boot_entries_find_type1(
635 const BootEntrySource source
,
638 _cleanup_free_ DirectoryEntries
*dentries
= NULL
;
639 _cleanup_free_
char *full
= NULL
;
640 _cleanup_close_
int dir_fd
= -EBADF
;
647 dir_fd
= chase_and_open(dir
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, O_DIRECTORY
|O_CLOEXEC
, &full
);
648 if (dir_fd
== -ENOENT
)
651 return log_error_errno(dir_fd
, "Failed to open '%s/%s': %m", root
, skip_leading_slash(dir
));
653 r
= readdir_all(dir_fd
, RECURSE_DIR_IGNORE_DOT
, &dentries
);
655 return log_error_errno(r
, "Failed to read directory '%s': %m", full
);
657 FOREACH_ARRAY(i
, dentries
->entries
, dentries
->n_entries
) {
658 const struct dirent
*de
= *i
;
659 _cleanup_fclose_
FILE *f
= NULL
;
661 if (!dirent_is_file(de
))
664 if (!endswith_no_case(de
->d_name
, ".conf"))
667 r
= xfopenat(dir_fd
, de
->d_name
, "re", O_NOFOLLOW
|O_NOCTTY
, &f
);
669 log_warning_errno(r
, "Failed to open %s/%s, ignoring: %m", full
, de
->d_name
);
673 r
= config_check_inode_relevant_and_unseen(config
, fileno(f
), de
->d_name
);
676 if (r
== 0) /* inode already seen or otherwise not relevant */
679 r
= boot_config_load_type1(config
, f
, root
, source
, full
, de
->d_name
);
680 if (r
== -ENOMEM
) /* ignore all other errors */
687 static int boot_entry_load_unified(
689 const BootEntrySource source
,
692 const char *osrelease_text
,
693 const char *profile_text
,
694 const char *cmdline_text
,
697 _cleanup_free_
char *fname
= NULL
, *os_pretty_name
= NULL
, *os_image_id
= NULL
, *os_name
= NULL
, *os_id
= NULL
,
698 *os_image_version
= NULL
, *os_version
= NULL
, *os_version_id
= NULL
, *os_build_id
= NULL
;
699 const char *k
, *good_name
, *good_version
, *good_sort_key
;
700 _cleanup_fclose_
FILE *f
= NULL
;
705 assert(osrelease_text
);
708 k
= path_startswith(path
, root
);
710 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Path is not below root: %s", path
);
712 f
= fmemopen_unlocked((void*) osrelease_text
, strlen(osrelease_text
), "r");
716 r
= parse_env_file(f
, "os-release",
717 "PRETTY_NAME", &os_pretty_name
,
718 "IMAGE_ID", &os_image_id
,
721 "IMAGE_VERSION", &os_image_version
,
722 "VERSION", &os_version
,
723 "VERSION_ID", &os_version_id
,
724 "BUILD_ID", &os_build_id
);
726 return log_error_errno(r
, "Failed to parse os-release data from unified kernel image %s: %m", path
);
728 if (!bootspec_pick_name_version_sort_key(
740 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Missing fields in os-release data from unified kernel image %s, refusing.", path
);
742 _cleanup_free_
char *profile_id
= NULL
, *profile_title
= NULL
;
746 f
= fmemopen_unlocked((void*) profile_text
, strlen(profile_text
), "r");
753 "TITLE", &profile_title
);
755 return log_error_errno(r
, "Failed to parse profile data from unified kernel image '%s': %m", path
);
758 r
= path_extract_filename(path
, &fname
);
760 return log_error_errno(r
, "Failed to extract file name from '%s': %m", path
);
762 _cleanup_(boot_entry_free
) BootEntry tmp
= BOOT_ENTRY_INIT(BOOT_ENTRY_UNIFIED
, source
);
764 r
= boot_filename_extract_tries(fname
, &tmp
.id
, &tmp
.tries_left
, &tmp
.tries_done
);
768 if (!efi_loader_entry_name_valid(tmp
.id
))
769 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry name: %s", tmp
.id
);
771 tmp
.profile
= profile
;
773 if (profile_id
|| profile
> 0) {
774 tmp
.id_without_profile
= TAKE_PTR(tmp
.id
);
777 tmp
.id
= strjoin(tmp
.id_without_profile
, "@", profile_id
);
779 (void) asprintf(&tmp
.id
, "%s@%u", tmp
.id_without_profile
, profile
);
784 if (os_id
&& os_version_id
) {
785 tmp
.id_old
= strjoin(os_id
, "-", os_version_id
);
790 tmp
.path
= strdup(path
);
794 tmp
.root
= strdup(root
);
798 tmp
.kernel
= path_make_absolute(k
, "/");
802 tmp
.options
= strv_new(cmdline_text
);
807 tmp
.title
= strjoin(good_name
, " (", profile_title
, ")");
809 tmp
.title
= strjoin(good_name
, " (", profile_id
, ")");
810 else if (profile
> 0)
811 (void) asprintf(&tmp
.title
, "%s (@%u)", good_name
, profile
);
813 tmp
.title
= strdup(good_name
);
818 tmp
.sort_key
= strdup(good_sort_key
);
824 tmp
.version
= strdup(good_version
);
829 *ret
= TAKE_STRUCT(tmp
);
833 static int pe_load_headers_and_sections(
836 IMAGE_SECTION_HEADER
**ret_sections
,
837 PeHeader
**ret_pe_header
) {
839 _cleanup_free_ IMAGE_SECTION_HEADER
*sections
= NULL
;
840 _cleanup_free_ IMAGE_DOS_HEADER
*dos_header
= NULL
;
841 _cleanup_free_ PeHeader
*pe_header
= NULL
;
847 r
= pe_load_headers(fd
, &dos_header
, &pe_header
);
849 return log_error_errno(r
, "Failed to parse PE file '%s': %m", path
);
851 r
= pe_load_sections(fd
, dos_header
, pe_header
, §ions
);
853 return log_error_errno(r
, "Failed to parse PE sections of '%s': %m", path
);
856 *ret_pe_header
= TAKE_PTR(pe_header
);
858 *ret_sections
= TAKE_PTR(sections
);
863 static const IMAGE_SECTION_HEADER
* pe_find_profile_section_table(
864 const PeHeader
*pe_header
,
865 const IMAGE_SECTION_HEADER
*sections
,
867 size_t *ret_n_sections
) {
871 /* Looks for the part of the section table that defines the specified profile. If 'profile' is
872 * specified as UINT_MAX this will look for the base profile. */
874 if (le16toh(pe_header
->pe
.NumberOfSections
) == 0)
879 const IMAGE_SECTION_HEADER
881 *e
= sections
+ le16toh(pe_header
->pe
.NumberOfSections
),
882 *start
= profile
== UINT_MAX
? sections
: NULL
,
884 unsigned current_profile
= UINT_MAX
;
887 p
= pe_section_table_find(p
, e
- p
, ".profile");
892 if (current_profile
== profile
) {
897 if (current_profile
== UINT_MAX
)
902 if (current_profile
== profile
)
905 p
++; /* Continue scanning after the .profile entry we just found */
912 *ret_n_sections
= end
- start
;
917 static int trim_cmdline(char **cmdline
) {
920 /* Strips leading and trailing whitespace from command line */
925 const char *skipped
= skip_leading_chars(*cmdline
, WHITESPACE
);
927 if (isempty(skipped
)) {
928 *cmdline
= mfree(*cmdline
);
932 if (skipped
!= *cmdline
) {
933 _cleanup_free_
char *c
= strdup(skipped
);
937 free_and_replace(*cmdline
, c
);
940 delete_trailing_chars(*cmdline
, WHITESPACE
);
944 /* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
945 * the ones we do care about and we are willing to load into memory have this size limit.) */
946 #define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
948 static int pe_find_uki_sections(
952 char **ret_osrelease
,
954 char **ret_cmdline
) {
956 _cleanup_free_
char *osrelease_text
= NULL
, *profile_text
= NULL
, *cmdline_text
= NULL
;
957 _cleanup_free_ IMAGE_SECTION_HEADER
*sections
= NULL
;
958 _cleanup_free_ PeHeader
*pe_header
= NULL
;
963 assert(profile
!= UINT_MAX
);
964 assert(ret_osrelease
);
968 r
= pe_load_headers_and_sections(fd
, path
, §ions
, &pe_header
);
972 if (!pe_is_uki(pe_header
, sections
))
973 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Parsed PE file '%s' is not a UKI.", path
);
975 if (!pe_is_native(pe_header
)) /* Don't process non-native UKIs */
978 /* Find part of the section table for this profile */
979 size_t n_psections
= 0;
980 const IMAGE_SECTION_HEADER
*psections
= pe_find_profile_section_table(pe_header
, sections
, profile
, &n_psections
);
981 if (!psections
&& profile
!= 0) /* Profile not found? (Profile @0 needs no explicit .profile!) */
984 /* Find base profile part of section table */
986 const IMAGE_SECTION_HEADER
*bsections
= ASSERT_PTR(pe_find_profile_section_table(pe_header
, sections
, UINT_MAX
, &n_bsections
));
992 { ".osrel", &osrelease_text
},
993 { ".profile", &profile_text
},
994 { ".cmdline", &cmdline_text
},
997 FOREACH_ELEMENT(t
, table
) {
998 const IMAGE_SECTION_HEADER
*found
;
1000 /* First look in the profile part of the section table, and if we don't find anything there, look into the base part */
1001 found
= pe_section_table_find(psections
, n_psections
, t
->name
);
1003 found
= pe_section_table_find(bsections
, n_bsections
, t
->name
);
1008 /* Permit "masking" of sections in the base profile */
1009 if (found
->VirtualSize
== 0)
1012 r
= pe_read_section_data(fd
, found
, PE_SECTION_SIZE_MAX
, (void**) t
->data
, /* ret_size= */ NULL
);
1014 return log_error_errno(r
, "Failed to load contents of section '%s': %m", t
->name
);
1017 if (!osrelease_text
)
1018 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Unified kernel image lacks .osrel data for profile @%u, refusing.", profile
);
1020 if (trim_cmdline(&cmdline_text
) < 0)
1023 *ret_osrelease
= TAKE_PTR(osrelease_text
);
1024 *ret_profile
= TAKE_PTR(profile_text
);
1025 *ret_cmdline
= TAKE_PTR(cmdline_text
);
1029 *ret_osrelease
= *ret_profile
= *ret_cmdline
= NULL
;
1033 static int pe_find_addon_sections(
1036 char **ret_cmdline
) {
1038 _cleanup_free_ IMAGE_SECTION_HEADER
*sections
= NULL
;
1039 _cleanup_free_ PeHeader
*pe_header
= NULL
;
1045 r
= pe_load_headers_and_sections(fd
, path
, §ions
, &pe_header
);
1049 if (!pe_is_addon(pe_header
, sections
))
1050 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Parse PE file '%s' is not an add-on.", path
);
1052 /* Define early, before the gotos below */
1053 _cleanup_free_
char *cmdline_text
= NULL
;
1055 if (!pe_is_native(pe_header
))
1058 const IMAGE_SECTION_HEADER
*found
= pe_section_table_find(sections
, le16toh(pe_header
->pe
.NumberOfSections
), ".cmdline");
1062 r
= pe_read_section_data(fd
, found
, PE_SECTION_SIZE_MAX
, (void**) &cmdline_text
, /* ret_size= */ NULL
);
1064 return log_error_errno(r
, "Failed to load contents of section '.cmdline': %m");
1066 if (trim_cmdline(&cmdline_text
) < 0)
1069 *ret_cmdline
= TAKE_PTR(cmdline_text
);
1073 *ret_cmdline
= NULL
;
1077 static int insert_boot_entry_addon(
1078 BootEntryAddons
*addons
,
1084 if (!GREEDY_REALLOC(addons
->items
, addons
->n_items
+ 1))
1087 addons
->items
[addons
->n_items
++] = (BootEntryAddon
) {
1088 .location
= location
,
1095 static int boot_entries_find_unified_addons(
1098 const char *addon_dir
,
1100 BootEntryAddons
*ret_addons
) {
1102 _cleanup_closedir_
DIR *d
= NULL
;
1103 _cleanup_free_
char *full
= NULL
;
1104 _cleanup_(boot_entry_addons_done
) BootEntryAddons addons
= {};
1110 r
= chase_and_opendirat(d_fd
, addon_dir
, CHASE_AT_RESOLVE_IN_ROOT
, &full
, &d
);
1114 return log_error_errno(r
, "Failed to open '%s/%s': %m", root
, skip_leading_slash(addon_dir
));
1116 FOREACH_DIRENT(de
, d
, return log_error_errno(errno
, "Failed to read %s: %m", full
)) {
1117 _cleanup_free_
char *j
= NULL
, *cmdline
= NULL
, *location
= NULL
;
1118 _cleanup_close_
int fd
= -EBADF
;
1120 if (!dirent_is_file(de
))
1123 if (!endswith_no_case(de
->d_name
, ".addon.efi"))
1126 fd
= openat(dirfd(d
), de
->d_name
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOFOLLOW
|O_NOCTTY
);
1128 log_warning_errno(errno
, "Failed to open %s/%s, ignoring: %m", full
, de
->d_name
);
1132 r
= config_check_inode_relevant_and_unseen(config
, fd
, de
->d_name
);
1135 if (r
== 0) /* inode already seen or otherwise not relevant */
1138 j
= path_join(full
, de
->d_name
);
1142 if (pe_find_addon_sections(fd
, j
, &cmdline
) <= 0)
1145 location
= strdup(j
);
1149 r
= insert_boot_entry_addon(&addons
, location
, cmdline
);
1157 *ret_addons
= TAKE_STRUCT(addons
);
1161 static int boot_entries_find_unified_global_addons(
1165 BootEntryAddons
*ret_addons
) {
1168 _cleanup_closedir_
DIR *d
= NULL
;
1172 r
= chase_and_opendir(root
, NULL
, CHASE_PROHIBIT_SYMLINKS
, NULL
, &d
);
1176 return log_error_errno(r
, "Failed to open '%s/%s': %m", root
, skip_leading_slash(d_name
));
1178 return boot_entries_find_unified_addons(config
, dirfd(d
), d_name
, root
, ret_addons
);
1181 static int boot_entries_find_unified_local_addons(
1188 _cleanup_free_
char *addon_dir
= NULL
;
1192 addon_dir
= strjoin(d_name
, ".extra.d");
1196 return boot_entries_find_unified_addons(config
, d_fd
, addon_dir
, root
, &ret
->local_addons
);
1199 static int boot_entries_find_unified(
1202 BootEntrySource source
,
1205 _cleanup_closedir_
DIR *d
= NULL
;
1206 _cleanup_free_
char *full
= NULL
;
1212 r
= chase_and_opendir(dir
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, &full
, &d
);
1216 return log_error_errno(r
, "Failed to open '%s/%s': %m", root
, skip_leading_slash(dir
));
1218 FOREACH_DIRENT(de
, d
, return log_error_errno(errno
, "Failed to read %s: %m", full
)) {
1219 if (!dirent_is_file(de
))
1222 if (!endswith_no_case(de
->d_name
, ".efi"))
1225 _cleanup_close_
int fd
= openat(dirfd(d
), de
->d_name
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOFOLLOW
|O_NOCTTY
);
1227 log_warning_errno(errno
, "Failed to open %s/%s, ignoring: %m", full
, de
->d_name
);
1231 r
= config_check_inode_relevant_and_unseen(config
, fd
, de
->d_name
);
1234 if (r
== 0) /* inode already seen or otherwise not relevant */
1237 _cleanup_free_
char *j
= path_join(full
, de
->d_name
);
1241 for (unsigned p
= 0; p
< UNIFIED_PROFILES_MAX
; p
++) {
1242 _cleanup_free_
char *osrelease
= NULL
, *profile
= NULL
, *cmdline
= NULL
;
1244 r
= pe_find_uki_sections(fd
, j
, p
, &osrelease
, &profile
, &cmdline
);
1245 if (r
== 0) /* this profile does not exist, we are done */
1250 if (!GREEDY_REALLOC(config
->entries
, config
->n_entries
+ 1))
1253 BootEntry
*entry
= config
->entries
+ config
->n_entries
;
1255 if (boot_entry_load_unified(root
, source
, j
, p
, osrelease
, profile
, cmdline
, entry
) < 0)
1258 /* look for .efi.extra.d */
1259 (void) boot_entries_find_unified_local_addons(config
, dirfd(d
), de
->d_name
, full
, entry
);
1261 /* Set up the backpointer, so that we can find the global addons */
1262 entry
->global_addons
= &config
->global_addons
[source
];
1264 config
->n_entries
++;
1271 static bool find_nonunique(const BootEntry
*entries
, size_t n_entries
, bool arr
[]) {
1272 bool non_unique
= false;
1274 assert(entries
|| n_entries
== 0);
1275 assert(arr
|| n_entries
== 0);
1277 for (size_t i
= 0; i
< n_entries
; i
++)
1280 for (size_t i
= 0; i
< n_entries
; i
++)
1281 for (size_t j
= 0; j
< n_entries
; j
++)
1282 if (i
!= j
&& streq(boot_entry_title(entries
+ i
),
1283 boot_entry_title(entries
+ j
)))
1284 non_unique
= arr
[i
] = arr
[j
] = true;
1289 static int boot_entries_uniquify(BootEntry
*entries
, size_t n_entries
) {
1290 _cleanup_free_
bool *arr
= NULL
;
1293 assert(entries
|| n_entries
== 0);
1298 arr
= new(bool, n_entries
);
1302 /* Find _all_ non-unique titles */
1303 if (!find_nonunique(entries
, n_entries
, arr
))
1306 /* Add version to non-unique titles */
1307 for (size_t i
= 0; i
< n_entries
; i
++)
1308 if (arr
[i
] && entries
[i
].version
) {
1309 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].version
) < 0)
1312 free_and_replace(entries
[i
].show_title
, s
);
1315 if (!find_nonunique(entries
, n_entries
, arr
))
1318 /* Add machine-id to non-unique titles */
1319 for (size_t i
= 0; i
< n_entries
; i
++)
1320 if (arr
[i
] && entries
[i
].machine_id
) {
1321 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].machine_id
) < 0)
1324 free_and_replace(entries
[i
].show_title
, s
);
1327 if (!find_nonunique(entries
, n_entries
, arr
))
1330 /* Add file name to non-unique titles */
1331 for (size_t i
= 0; i
< n_entries
; i
++)
1333 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].id
) < 0)
1336 free_and_replace(entries
[i
].show_title
, s
);
1342 static int boot_config_find(const BootConfig
*config
, const char *id
) {
1349 if (!strcaseeq(id
, "@saved"))
1351 if (!config
->entry_selected
)
1353 id
= config
->entry_selected
;
1356 for (size_t i
= 0; i
< config
->n_entries
; i
++)
1357 if (fnmatch(id
, config
->entries
[i
].id
, FNM_CASEFOLD
) == 0)
1363 static int boot_entries_select_default(const BootConfig
*config
) {
1367 assert(config
->entries
|| config
->n_entries
== 0);
1369 if (config
->n_entries
== 0) {
1370 log_debug("Found no default boot entry :(");
1371 return -1; /* -1 means "no default" */
1374 if (config
->entry_oneshot
) {
1375 i
= boot_config_find(config
, config
->entry_oneshot
);
1377 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
1378 config
->entries
[i
].id
);
1383 if (config
->entry_default
) {
1384 i
= boot_config_find(config
, config
->entry_default
);
1386 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
1387 config
->entries
[i
].id
);
1392 if (config
->default_pattern
) {
1393 i
= boot_config_find(config
, config
->default_pattern
);
1395 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
1396 config
->entries
[i
].id
, config
->default_pattern
);
1401 log_debug("Found default: first entry \"%s\"", config
->entries
[0].id
);
1405 static int boot_entries_select_selected(const BootConfig
*config
) {
1407 assert(config
->entries
|| config
->n_entries
== 0);
1409 if (!config
->entry_selected
|| config
->n_entries
== 0)
1412 return boot_config_find(config
, config
->entry_selected
);
1415 static int boot_load_efi_entry_pointers(BootConfig
*config
, bool skip_efivars
) {
1420 if (skip_efivars
|| !is_efi_boot())
1423 /* Loads the three "pointers" to boot loader entries from their EFI variables */
1425 r
= efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &config
->entry_oneshot
);
1428 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
))
1429 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m");
1431 r
= efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &config
->entry_default
);
1434 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
))
1435 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m");
1437 r
= efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), &config
->entry_selected
);
1440 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
))
1441 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m");
1443 r
= efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &config
->entry_sysfail
);
1446 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
))
1447 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntrySysFail\", ignoring: %m");
1452 int boot_config_select_special_entries(BootConfig
*config
, bool skip_efivars
) {
1457 r
= boot_load_efi_entry_pointers(config
, skip_efivars
);
1461 config
->default_entry
= boot_entries_select_default(config
);
1462 config
->selected_entry
= boot_entries_select_selected(config
);
1467 int boot_config_finalize(BootConfig
*config
) {
1470 typesafe_qsort(config
->entries
, config
->n_entries
, boot_entry_compare
);
1472 r
= boot_entries_uniquify(config
->entries
, config
->n_entries
);
1474 return log_error_errno(r
, "Failed to uniquify boot entries: %m");
1479 int boot_config_load(
1481 const char *esp_path
,
1482 const char *xbootldr_path
) {
1489 r
= boot_loader_read_conf_path(config
, esp_path
, "/loader/loader.conf");
1493 r
= boot_entries_find_type1(config
, esp_path
, BOOT_ENTRY_ESP
, "/loader/entries");
1497 r
= boot_entries_find_unified(config
, esp_path
, BOOT_ENTRY_ESP
, "/EFI/Linux/");
1501 r
= boot_entries_find_unified_global_addons(config
, esp_path
, "/loader/addons/",
1502 &config
->global_addons
[BOOT_ENTRY_ESP
]);
1507 if (xbootldr_path
) {
1508 r
= boot_entries_find_type1(config
, xbootldr_path
, BOOT_ENTRY_XBOOTLDR
, "/loader/entries");
1512 r
= boot_entries_find_unified(config
, xbootldr_path
, BOOT_ENTRY_XBOOTLDR
, "/EFI/Linux/");
1516 r
= boot_entries_find_unified_global_addons(config
, xbootldr_path
, "/loader/addons/",
1517 &config
->global_addons
[BOOT_ENTRY_XBOOTLDR
]);
1522 return boot_config_finalize(config
);
1525 int boot_config_load_auto(
1527 const char *override_esp_path
,
1528 const char *override_xbootldr_path
) {
1530 _cleanup_free_
char *esp_where
= NULL
, *xbootldr_where
= NULL
;
1531 dev_t esp_devid
= 0, xbootldr_devid
= 0;
1536 /* This function is similar to boot_entries_load_config(), however we automatically search for the
1537 * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
1538 * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
1539 * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
1540 * it). This allows other boot loaders to pass boot loader entry information to our tools if they
1543 if (!override_esp_path
&& !override_xbootldr_path
) {
1544 if (access("/run/boot-loader-entries/", F_OK
) >= 0)
1545 return boot_config_load(config
, "/run/boot-loader-entries/", NULL
);
1547 if (errno
!= ENOENT
)
1548 return log_error_errno(errno
,
1549 "Failed to determine whether /run/boot-loader-entries/ exists: %m");
1552 r
= find_esp_and_warn(NULL
, override_esp_path
, /* unprivileged_mode= */ false, &esp_where
, NULL
, NULL
, NULL
, NULL
, &esp_devid
);
1553 if (r
< 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
1556 r
= find_xbootldr_and_warn(NULL
, override_xbootldr_path
, /* unprivileged_mode= */ false, &xbootldr_where
, NULL
, &xbootldr_devid
);
1557 if (r
< 0 && r
!= -ENOKEY
)
1558 return r
; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
1560 /* If both paths actually refer to the same inode, suppress the xbootldr path */
1561 if (esp_where
&& xbootldr_where
&& devnum_set_and_equal(esp_devid
, xbootldr_devid
))
1562 xbootldr_where
= mfree(xbootldr_where
);
1564 return boot_config_load(config
, esp_where
, xbootldr_where
);
1567 int boot_config_augment_from_loader(
1569 char **found_by_loader
,
1572 static const BootEntryAddons no_addons
= (BootEntryAddons
) {};
1573 static const char *const title_table
[] = {
1574 /* Pretty names for a few well-known automatically discovered entries. */
1575 "auto-osx", "macOS",
1576 "auto-windows", "Windows Boot Manager",
1577 "auto-efi-shell", "EFI Shell",
1578 "auto-efi-default", "EFI Default Loader",
1579 "auto-poweroff", "Power Off The System",
1580 "auto-reboot", "Reboot The System",
1581 "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
1587 /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
1588 * already included there. */
1590 STRV_FOREACH(i
, found_by_loader
) {
1591 BootEntry
*existing
;
1592 _cleanup_free_
char *c
= NULL
, *t
= NULL
, *p
= NULL
;
1594 existing
= boot_config_find_entry(config
, *i
);
1596 existing
->reported_by_loader
= true;
1600 if (auto_only
&& !startswith(*i
, "auto-"))
1607 STRV_FOREACH_PAIR(a
, b
, title_table
)
1608 if (streq(*a
, *i
)) {
1615 p
= strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntries")));
1619 if (!GREEDY_REALLOC0(config
->entries
, config
->n_entries
+ 1))
1622 config
->entries
[config
->n_entries
++] = (BootEntry
) {
1623 .type
= startswith(*i
, "auto-") ? BOOT_ENTRY_LOADER_AUTO
: BOOT_ENTRY_LOADER
,
1625 .title
= TAKE_PTR(t
),
1626 .path
= TAKE_PTR(p
),
1627 .reported_by_loader
= true,
1628 .tries_left
= UINT_MAX
,
1629 .tries_done
= UINT_MAX
,
1630 .global_addons
= &no_addons
,
1637 BootEntry
* boot_config_find_entry(BootConfig
*config
, const char *id
) {
1641 for (size_t j
= 0; j
< config
->n_entries
; j
++)
1642 if (strcaseeq_ptr(config
->entries
[j
].id
, id
) ||
1643 strcaseeq_ptr(config
->entries
[j
].id_old
, id
))
1644 return config
->entries
+ j
;
1649 static void boot_entry_file_list(
1658 int status
= chase_and_access(p
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, F_OK
, NULL
);
1660 /* Note that this shows two '/' between the root and the file. This is intentional to highlight (in
1661 * the absence of color support) to the user that the boot loader is only interested in the second
1662 * part of the file. */
1663 printf("%13s%s %s%s/%s", strempty(field
), field
? ":" : " ", ansi_grey(), root
, ansi_normal());
1667 printf("%s%s%s (%m)\n", ansi_highlight_red(), p
, ansi_normal());
1671 if (*ret_status
== 0 && status
< 0)
1672 *ret_status
= status
;
1675 static void print_addon(
1676 BootEntryAddon
*addon
,
1677 const char *addon_str
) {
1679 printf(" %s: %s\n", addon_str
, addon
->location
);
1680 printf(" options: %s%s\n", glyph(GLYPH_TREE_RIGHT
), addon
->cmdline
);
1683 static int indent_embedded_newlines(char *cmdline
, char **ret_cmdline
) {
1684 _cleanup_free_
char *t
= NULL
;
1685 _cleanup_strv_free_
char **ts
= NULL
;
1687 assert(ret_cmdline
);
1689 ts
= strv_split_newlines(cmdline
);
1693 t
= strv_join(ts
, "\n ");
1697 *ret_cmdline
= TAKE_PTR(t
);
1702 static int print_cmdline(const BootEntry
*e
) {
1704 _cleanup_free_
char *options
= NULL
, *combined_cmdline
= NULL
, *t2
= NULL
;
1708 if (!strv_isempty(e
->options
)) {
1709 _cleanup_free_
char *t
= NULL
;
1711 options
= strv_join(e
->options
, " ");
1715 if (indent_embedded_newlines(options
, &t
) < 0)
1718 printf(" options: %s\n", t
);
1719 t2
= strdup(options
);
1724 FOREACH_ARRAY(addon
, e
->global_addons
->items
, e
->global_addons
->n_items
) {
1725 print_addon(addon
, "global-addon");
1726 if (!strextend(&t2
, " ", addon
->cmdline
))
1730 FOREACH_ARRAY(addon
, e
->local_addons
.items
, e
->local_addons
.n_items
) {
1731 /* Add space at the beginning of addon_str to align it correctly */
1732 print_addon(addon
, " local-addon");
1733 if (!strextend(&t2
, " ", addon
->cmdline
))
1737 /* Don't print the combined cmdline if it's same as options. */
1738 if (streq_ptr(t2
, options
))
1741 if (indent_embedded_newlines(t2
, &combined_cmdline
) < 0)
1744 if (combined_cmdline
)
1745 printf(" cmdline: %s\n", combined_cmdline
);
1750 static int json_addon(
1751 BootEntryAddon
*addon
,
1752 const char *addon_str
,
1753 sd_json_variant
**array
) {
1760 r
= sd_json_variant_append_arraybo(
1762 SD_JSON_BUILD_PAIR(addon_str
, SD_JSON_BUILD_STRING(addon
->location
)),
1763 SD_JSON_BUILD_PAIR("options", SD_JSON_BUILD_STRING(addon
->cmdline
)));
1770 static int json_cmdline(
1772 const char *def_cmdline
,
1773 sd_json_variant
**v
) {
1775 _cleanup_free_
char *combined_cmdline
= NULL
;
1776 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*addons_array
= NULL
;
1782 combined_cmdline
= strdup(def_cmdline
);
1783 if (!combined_cmdline
)
1787 FOREACH_ARRAY(addon
, e
->global_addons
->items
, e
->global_addons
->n_items
) {
1788 r
= json_addon(addon
, "globalAddon", &addons_array
);
1791 if (!strextend(&combined_cmdline
, " ", addon
->cmdline
))
1795 FOREACH_ARRAY(addon
, e
->local_addons
.items
, e
->local_addons
.n_items
) {
1796 r
= json_addon(addon
, "localAddon", &addons_array
);
1799 if (!strextend(&combined_cmdline
, " ", addon
->cmdline
))
1803 r
= sd_json_variant_merge_objectbo(
1805 SD_JSON_BUILD_PAIR("addons", SD_JSON_BUILD_VARIANT(addons_array
)),
1806 SD_JSON_BUILD_PAIR_CONDITION(!!combined_cmdline
, "cmdline", SD_JSON_BUILD_STRING(combined_cmdline
)));
1812 int show_boot_entry(
1814 bool show_as_default
,
1815 bool show_as_selected
,
1816 bool show_reported
) {
1818 int status
= 0, r
= 0;
1820 /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
1821 boot entry itself. */
1825 printf(" type: %s\n",
1826 boot_entry_type_to_string(e
->type
));
1828 printf(" title: %s%s%s",
1829 ansi_highlight(), boot_entry_title(e
), ansi_normal());
1831 if (show_as_default
)
1832 printf(" %s(default)%s",
1833 ansi_highlight_green(), ansi_normal());
1835 if (show_as_selected
)
1836 printf(" %s(selected)%s",
1837 ansi_highlight_magenta(), ansi_normal());
1839 if (show_reported
) {
1840 if (e
->type
== BOOT_ENTRY_LOADER
)
1841 printf(" %s(reported/absent)%s",
1842 ansi_highlight_red(), ansi_normal());
1843 else if (!e
->reported_by_loader
&& e
->type
!= BOOT_ENTRY_LOADER_AUTO
)
1844 printf(" %s(not reported/new)%s",
1845 ansi_highlight_green(), ansi_normal());
1851 printf(" id: %s", e
->id
);
1853 if (e
->id_without_profile
&& !streq_ptr(e
->id
, e
->id_without_profile
))
1854 printf(" (without profile: %s)\n", e
->id_without_profile
);
1859 _cleanup_free_
char *text
= NULL
, *link
= NULL
;
1861 const char *p
= e
->root
? path_startswith(e
->path
, e
->root
) : NULL
;
1863 text
= strjoin(ansi_grey(), e
->root
, "/", ansi_normal(), "/", p
);
1868 /* Let's urlify the link to make it easy to view in an editor, but only if it is a text
1869 * file. Unified images are binary ELFs, and EFI variables are not pure text either. */
1870 if (e
->type
== BOOT_ENTRY_CONF
)
1871 (void) terminal_urlify_path(e
->path
, text
, &link
);
1873 printf(" source: %s (on the %s)\n",
1874 link
?: text
?: e
->path
,
1875 boot_entry_source_to_string(e
->source
));
1877 if (e
->tries_left
!= UINT_MAX
) {
1878 printf(" tries: %u left", e
->tries_left
);
1880 if (e
->tries_done
!= UINT_MAX
)
1881 printf("; %u done\n", e
->tries_done
);
1887 printf(" sort-key: %s\n", e
->sort_key
);
1889 printf(" version: %s\n", e
->version
);
1891 printf(" machine-id: %s\n", e
->machine_id
);
1892 if (e
->architecture
)
1893 printf(" architecture: %s\n", e
->architecture
);
1895 boot_entry_file_list("linux", e
->root
, e
->kernel
, &status
);
1897 boot_entry_file_list("efi", e
->root
, e
->efi
, &status
);
1899 STRV_FOREACH(s
, e
->initrd
)
1900 boot_entry_file_list(s
== e
->initrd
? "initrd" : NULL
,
1905 r
= print_cmdline(e
);
1910 boot_entry_file_list("devicetree", e
->root
, e
->device_tree
, &status
);
1912 STRV_FOREACH(s
, e
->device_tree_overlay
)
1913 boot_entry_file_list(s
== e
->device_tree_overlay
? "devicetree-overlay" : NULL
,
1921 int boot_entry_to_json(const BootConfig
*c
, size_t i
, sd_json_variant
**ret
) {
1922 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*v
= NULL
;
1923 _cleanup_free_
char *opts
= NULL
;
1930 if (i
>= c
->n_entries
) {
1937 if (!strv_isempty(e
->options
)) {
1938 opts
= strv_join(e
->options
, " ");
1943 r
= sd_json_variant_merge_objectbo(
1945 SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(boot_entry_type_json_to_string(e
->type
))),
1946 SD_JSON_BUILD_PAIR("source", SD_JSON_BUILD_STRING(boot_entry_source_json_to_string(e
->source
))),
1947 SD_JSON_BUILD_PAIR_CONDITION(!!e
->id
, "id", SD_JSON_BUILD_STRING(e
->id
)),
1948 SD_JSON_BUILD_PAIR_CONDITION(!!e
->path
, "path", SD_JSON_BUILD_STRING(e
->path
)),
1949 SD_JSON_BUILD_PAIR_CONDITION(!!e
->root
, "root", SD_JSON_BUILD_STRING(e
->root
)),
1950 SD_JSON_BUILD_PAIR_CONDITION(!!e
->title
, "title", SD_JSON_BUILD_STRING(e
->title
)),
1951 SD_JSON_BUILD_PAIR_CONDITION(!!boot_entry_title(e
), "showTitle", SD_JSON_BUILD_STRING(boot_entry_title(e
))),
1952 SD_JSON_BUILD_PAIR_CONDITION(!!e
->sort_key
, "sortKey", SD_JSON_BUILD_STRING(e
->sort_key
)),
1953 SD_JSON_BUILD_PAIR_CONDITION(!!e
->version
, "version", SD_JSON_BUILD_STRING(e
->version
)),
1954 SD_JSON_BUILD_PAIR_CONDITION(!!e
->machine_id
, "machineId", SD_JSON_BUILD_STRING(e
->machine_id
)),
1955 SD_JSON_BUILD_PAIR_CONDITION(!!e
->architecture
, "architecture", SD_JSON_BUILD_STRING(e
->architecture
)),
1956 SD_JSON_BUILD_PAIR_CONDITION(!!opts
, "options", SD_JSON_BUILD_STRING(opts
)),
1957 SD_JSON_BUILD_PAIR_CONDITION(!!e
->kernel
, "linux", SD_JSON_BUILD_STRING(e
->kernel
)),
1958 SD_JSON_BUILD_PAIR_CONDITION(!!e
->efi
, "efi", SD_JSON_BUILD_STRING(e
->efi
)),
1959 SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e
->initrd
), "initrd", SD_JSON_BUILD_STRV(e
->initrd
)),
1960 SD_JSON_BUILD_PAIR_CONDITION(!!e
->device_tree
, "devicetree", SD_JSON_BUILD_STRING(e
->device_tree
)),
1961 SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e
->device_tree_overlay
), "devicetreeOverlay", SD_JSON_BUILD_STRV(e
->device_tree_overlay
)));
1965 /* Sanitizers (only memory sanitizer?) do not like function call with too many
1966 * arguments and trigger false positive warnings. Let's not add too many json objects
1968 r
= sd_json_variant_merge_objectbo(
1970 SD_JSON_BUILD_PAIR("isReported", SD_JSON_BUILD_BOOLEAN(e
->reported_by_loader
)),
1971 SD_JSON_BUILD_PAIR_CONDITION(e
->tries_left
!= UINT_MAX
, "triesLeft", SD_JSON_BUILD_UNSIGNED(e
->tries_left
)),
1972 SD_JSON_BUILD_PAIR_CONDITION(e
->tries_done
!= UINT_MAX
, "triesDone", SD_JSON_BUILD_UNSIGNED(e
->tries_done
)),
1973 SD_JSON_BUILD_PAIR_CONDITION(c
->default_entry
>= 0, "isDefault", SD_JSON_BUILD_BOOLEAN(i
== (size_t) c
->default_entry
)),
1974 SD_JSON_BUILD_PAIR_CONDITION(c
->selected_entry
>= 0, "isSelected", SD_JSON_BUILD_BOOLEAN(i
== (size_t) c
->selected_entry
)));
1978 r
= json_cmdline(e
, opts
, &v
);
1986 int show_boot_entries(const BootConfig
*config
, sd_json_format_flags_t json_format
) {
1991 if (sd_json_format_enabled(json_format
)) {
1992 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*array
= NULL
;
1994 for (size_t i
= 0; i
< config
->n_entries
; i
++) {
1995 _cleanup_(sd_json_variant_unrefp
) sd_json_variant
*v
= NULL
;
1997 r
= boot_entry_to_json(config
, i
, &v
);
2001 r
= sd_json_variant_append_array(&array
, v
);
2006 return sd_json_variant_dump(array
, json_format
| SD_JSON_FORMAT_EMPTY_ARRAY
, NULL
, NULL
);
2008 for (size_t n
= 0; n
< config
->n_entries
; n
++) {
2009 r
= show_boot_entry(
2010 config
->entries
+ n
,
2011 /* show_as_default= */ n
== (size_t) config
->default_entry
,
2012 /* show_as_selected= */ n
== (size_t) config
->selected_entry
,
2013 /* show_reported= */ true);
2017 if (n
+1 < config
->n_entries
)