1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "bootspec-fundamental.h"
8 #include "conf-files.h"
9 #include "devnum-util.h"
10 #include "dirent-util.h"
11 #include "efi-loader.h"
13 #include "errno-util.h"
17 #include "path-util.h"
18 #include "pe-binary.h"
19 #include "pretty-print.h"
20 #include "recurse-dir.h"
21 #include "sort-util.h"
22 #include "stat-util.h"
23 #include "string-table.h"
25 #include "terminal-util.h"
26 #include "unaligned.h"
28 static const char* const boot_entry_type_table
[_BOOT_ENTRY_TYPE_MAX
] = {
29 [BOOT_ENTRY_CONF
] = "Boot Loader Specification Type #1 (.conf)",
30 [BOOT_ENTRY_UNIFIED
] = "Boot Loader Specification Type #2 (.efi)",
31 [BOOT_ENTRY_LOADER
] = "Reported by Boot Loader",
32 [BOOT_ENTRY_LOADER_AUTO
] = "Automatic",
35 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type
, BootEntryType
);
37 static const char* const boot_entry_type_json_table
[_BOOT_ENTRY_TYPE_MAX
] = {
38 [BOOT_ENTRY_CONF
] = "type1",
39 [BOOT_ENTRY_UNIFIED
] = "type2",
40 [BOOT_ENTRY_LOADER
] = "loader",
41 [BOOT_ENTRY_LOADER_AUTO
] = "auto",
44 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_json
, BootEntryType
);
46 BootEntryAddon
* boot_entry_addon_free(BootEntryAddon
*addon
) {
50 free(addon
->location
);
55 static void boot_entry_free(BootEntry
*entry
) {
63 free(entry
->show_title
);
64 free(entry
->sort_key
);
66 free(entry
->machine_id
);
67 free(entry
->architecture
);
68 strv_free(entry
->options
);
69 free(entry
->local_addons
.items
);
72 strv_free(entry
->initrd
);
73 free(entry
->device_tree
);
74 strv_free(entry
->device_tree_overlay
);
77 static int mangle_path(
84 _cleanup_free_
char *c
= NULL
;
90 /* Spec leaves open if prefixed with "/" or not, let's normalize that */
91 if (path_is_absolute(p
))
98 /* We only reference files, never directories */
99 if (endswith(c
, "/")) {
100 log_syntax(NULL
, LOG_WARNING
, fname
, line
, 0, "Path in field '%s' has trailing slash, ignoring: %s", field
, c
);
105 /* Remove duplicate "/" */
108 /* No ".." or "." or so */
109 if (!path_is_normalized(c
)) {
110 log_syntax(NULL
, LOG_WARNING
, fname
, line
, 0, "Path in field '%s' is not normalized, ignoring: %s", field
, c
);
119 static int parse_path_one(
126 _cleanup_free_
char *c
= NULL
;
133 r
= mangle_path(fname
, line
, field
, p
, &c
);
137 return free_and_replace(*s
, c
);
140 static int parse_path_strv(
154 r
= mangle_path(fname
, line
, field
, p
, &c
);
158 return strv_consume(s
, c
);
161 static int parse_path_many(
168 _cleanup_strv_free_
char **l
= NULL
, **f
= NULL
;
171 l
= strv_split(p
, NULL
);
178 r
= mangle_path(fname
, line
, field
, *i
, &c
);
184 r
= strv_consume(&f
, c
);
189 return strv_extend_strv(s
, f
, /* filter_duplicates= */ false);
192 static int parse_tries(const char *fname
, const char **p
, unsigned *ret
) {
193 _cleanup_free_
char *d
= NULL
;
203 n
= strspn(*p
, DIGITS
);
213 r
= safe_atou_full(d
, 10, &tries
);
214 if (r
>= 0 && tries
> INT_MAX
) /* sd-boot allows INT_MAX, let's use the same limit */
217 return log_error_errno(r
, "Failed to parse tries counter of filename '%s': %m", fname
);
224 int boot_filename_extract_tries(
227 unsigned *ret_tries_left
,
228 unsigned *ret_tries_done
) {
230 unsigned tries_left
= UINT_MAX
, tries_done
= UINT_MAX
;
231 _cleanup_free_
char *stripped
= NULL
;
232 const char *p
, *suffix
, *m
;
236 assert(ret_stripped
);
237 assert(ret_tries_left
);
238 assert(ret_tries_done
);
240 /* Be liberal with suffix, only insist on a dot. After all we want to cover any capitalization here
241 * (vfat is case insensitive after all), and at least .efi and .conf as suffix. */
242 suffix
= strrchr(fname
, '.');
246 p
= m
= memrchr(fname
, '+', suffix
- fname
);
251 r
= parse_tries(fname
, &p
, &tries_left
);
260 r
= parse_tries(fname
, &p
, &tries_done
);
270 stripped
= strndup(fname
, m
- fname
);
274 if (!strextend(&stripped
, suffix
))
277 *ret_stripped
= TAKE_PTR(stripped
);
278 *ret_tries_left
= tries_left
;
279 *ret_tries_done
= tries_done
;
284 stripped
= strdup(fname
);
288 *ret_stripped
= TAKE_PTR(stripped
);
289 *ret_tries_left
= *ret_tries_done
= UINT_MAX
;
293 static int boot_entry_load_type1(
300 _cleanup_(boot_entry_free
) BootEntry tmp
= BOOT_ENTRY_INIT(BOOT_ENTRY_CONF
);
311 /* Loads a Type #1 boot menu entry from the specified FILE* object */
313 r
= boot_filename_extract_tries(fname
, &tmp
.id
, &tmp
.tries_left
, &tmp
.tries_done
);
317 if (!efi_loader_entry_name_valid(tmp
.id
))
318 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry name: %s", fname
);
320 c
= endswith_no_case(tmp
.id
, ".conf");
322 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry file suffix: %s", fname
);
324 tmp
.id_old
= strndup(tmp
.id
, c
- tmp
.id
); /* Without .conf suffix */
328 tmp
.path
= path_join(dir
, fname
);
332 tmp
.root
= strdup(root
);
337 _cleanup_free_
char *buf
= NULL
, *field
= NULL
;
339 r
= read_stripped_line(f
, LONG_LINE_MAX
, &buf
);
343 return log_syntax(NULL
, LOG_ERR
, tmp
.path
, line
, r
, "Line too long.");
345 return log_syntax(NULL
, LOG_ERR
, tmp
.path
, line
, r
, "Error while reading: %m");
349 if (IN_SET(buf
[0], '#', '\0'))
353 r
= extract_first_word(&p
, &field
, NULL
, 0);
355 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, r
, "Failed to parse, ignoring line: %m");
359 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, 0, "Bad syntax, ignoring line.");
364 /* Some fields can reasonably have an empty value. In other cases warn. */
365 if (!STR_IN_SET(field
, "options", "devicetree-overlay"))
366 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, 0, "Field '%s' without value, ignoring line.", field
);
371 if (streq(field
, "title"))
372 r
= free_and_strdup(&tmp
.title
, p
);
373 else if (streq(field
, "sort-key"))
374 r
= free_and_strdup(&tmp
.sort_key
, p
);
375 else if (streq(field
, "version"))
376 r
= free_and_strdup(&tmp
.version
, p
);
377 else if (streq(field
, "machine-id"))
378 r
= free_and_strdup(&tmp
.machine_id
, p
);
379 else if (streq(field
, "architecture"))
380 r
= free_and_strdup(&tmp
.architecture
, p
);
381 else if (streq(field
, "options"))
382 r
= strv_extend(&tmp
.options
, p
);
383 else if (streq(field
, "linux"))
384 r
= parse_path_one(tmp
.path
, line
, field
, &tmp
.kernel
, p
);
385 else if (streq(field
, "efi"))
386 r
= parse_path_one(tmp
.path
, line
, field
, &tmp
.efi
, p
);
387 else if (streq(field
, "initrd"))
388 r
= parse_path_strv(tmp
.path
, line
, field
, &tmp
.initrd
, p
);
389 else if (streq(field
, "devicetree"))
390 r
= parse_path_one(tmp
.path
, line
, field
, &tmp
.device_tree
, p
);
391 else if (streq(field
, "devicetree-overlay"))
392 r
= parse_path_many(tmp
.path
, line
, field
, &tmp
.device_tree_overlay
, p
);
394 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, 0, "Unknown line '%s', ignoring.", field
);
398 return log_syntax(NULL
, LOG_ERR
, tmp
.path
, line
, r
, "Error while parsing: %m");
401 *entry
= TAKE_STRUCT(tmp
);
405 int boot_config_load_type1(
419 if (!GREEDY_REALLOC0(config
->entries
, config
->n_entries
+ 1))
422 r
= boot_entry_load_type1(f
, root
, dir
, fname
, config
->entries
+ config
->n_entries
);
430 void boot_config_free(BootConfig
*config
) {
433 free(config
->default_pattern
);
434 free(config
->timeout
);
435 free(config
->editor
);
436 free(config
->auto_entries
);
437 free(config
->auto_firmware
);
438 free(config
->console_mode
);
441 free(config
->entry_oneshot
);
442 free(config
->entry_default
);
443 free(config
->entry_selected
);
445 for (size_t i
= 0; i
< config
->n_entries
; i
++)
446 boot_entry_free(config
->entries
+ i
);
447 free(config
->entries
);
448 free(config
->global_addons
.items
);
450 set_free(config
->inodes_seen
);
453 int boot_loader_read_conf(BootConfig
*config
, FILE *file
, const char *path
) {
462 _cleanup_free_
char *buf
= NULL
, *field
= NULL
;
464 r
= read_stripped_line(file
, LONG_LINE_MAX
, &buf
);
468 return log_syntax(NULL
, LOG_ERR
, path
, line
, r
, "Line too long.");
470 return log_syntax(NULL
, LOG_ERR
, path
, line
, r
, "Error while reading: %m");
474 if (IN_SET(buf
[0], '#', '\0'))
478 r
= extract_first_word(&p
, &field
, NULL
, 0);
480 log_syntax(NULL
, LOG_WARNING
, path
, line
, r
, "Failed to parse, ignoring line: %m");
484 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "Bad syntax, ignoring line.");
488 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "Field '%s' without value, ignoring line.", field
);
492 if (streq(field
, "default"))
493 r
= free_and_strdup(&config
->default_pattern
, p
);
494 else if (streq(field
, "timeout"))
495 r
= free_and_strdup(&config
->timeout
, p
);
496 else if (streq(field
, "editor"))
497 r
= free_and_strdup(&config
->editor
, p
);
498 else if (streq(field
, "auto-entries"))
499 r
= free_and_strdup(&config
->auto_entries
, p
);
500 else if (streq(field
, "auto-firmware"))
501 r
= free_and_strdup(&config
->auto_firmware
, p
);
502 else if (streq(field
, "console-mode"))
503 r
= free_and_strdup(&config
->console_mode
, p
);
504 else if (streq(field
, "random-seed-mode"))
505 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "'random-seed-mode' has been deprecated, ignoring.");
506 else if (streq(field
, "beep"))
507 r
= free_and_strdup(&config
->beep
, p
);
509 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "Unknown line '%s', ignoring.", field
);
513 return log_syntax(NULL
, LOG_ERR
, path
, line
, r
, "Error while parsing: %m");
519 static int boot_loader_read_conf_path(BootConfig
*config
, const char *root
, const char *path
) {
520 _cleanup_free_
char *full
= NULL
;
521 _cleanup_fclose_
FILE *f
= NULL
;
527 r
= chase_and_fopen_unlocked(path
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, "re", &full
, &f
);
531 return log_error_errno(r
, "Failed to open '%s/%s': %m", root
, path
);
533 return boot_loader_read_conf(config
, f
, full
);
536 static int boot_entry_compare(const BootEntry
*a
, const BootEntry
*b
) {
542 r
= CMP(!a
->sort_key
, !b
->sort_key
);
546 if (a
->sort_key
&& b
->sort_key
) {
547 r
= strcmp(a
->sort_key
, b
->sort_key
);
551 r
= strcmp_ptr(a
->machine_id
, b
->machine_id
);
555 r
= -strverscmp_improved(a
->version
, b
->version
);
560 return -strverscmp_improved(a
->id
, b
->id
);
563 static int config_check_inode_relevant_and_unseen(BootConfig
*config
, int fd
, const char *fname
) {
564 _cleanup_free_
char *d
= NULL
;
571 /* So, here's the thing: because of the mess around /efi/ vs. /boot/ vs. /boot/efi/ it might be that
572 * people have these dirs, or subdirs of them symlinked or bind mounted, and we might end up
573 * iterating though some dirs multiple times. Let's thus rather be safe than sorry, and track the
574 * inodes we already processed: let's ignore inodes we have seen already. This should be robust
575 * against any form of symlinking or bind mounting, and effectively suppress any such duplicates. */
577 if (fstat(fd
, &st
) < 0)
578 return log_error_errno(errno
, "Failed to stat('%s'): %m", fname
);
579 if (!S_ISREG(st
.st_mode
)) {
580 log_debug("File '%s' is not a regular file, ignoring.", fname
);
584 if (set_contains(config
->inodes_seen
, &st
)) {
585 log_debug("Inode '%s' already seen before, ignoring.", fname
);
589 d
= memdup(&st
, sizeof(st
));
593 if (set_ensure_consume(&config
->inodes_seen
, &inode_hash_ops
, TAKE_PTR(d
)) < 0)
599 static int boot_entries_find_type1(
604 _cleanup_free_ DirectoryEntries
*dentries
= NULL
;
605 _cleanup_free_
char *full
= NULL
;
606 _cleanup_close_
int dir_fd
= -EBADF
;
613 dir_fd
= chase_and_open(dir
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, O_DIRECTORY
|O_CLOEXEC
, &full
);
614 if (dir_fd
== -ENOENT
)
617 return log_error_errno(dir_fd
, "Failed to open '%s/%s': %m", root
, dir
);
619 r
= readdir_all(dir_fd
, RECURSE_DIR_IGNORE_DOT
, &dentries
);
621 return log_error_errno(r
, "Failed to read directory '%s': %m", full
);
623 for (size_t i
= 0; i
< dentries
->n_entries
; i
++) {
624 const struct dirent
*de
= dentries
->entries
[i
];
625 _cleanup_fclose_
FILE *f
= NULL
;
627 if (!dirent_is_file(de
))
630 if (!endswith_no_case(de
->d_name
, ".conf"))
633 r
= xfopenat(dir_fd
, de
->d_name
, "re", O_NOFOLLOW
|O_NOCTTY
, &f
);
635 log_warning_errno(r
, "Failed to open %s/%s, ignoring: %m", full
, de
->d_name
);
639 r
= config_check_inode_relevant_and_unseen(config
, fileno(f
), de
->d_name
);
642 if (r
== 0) /* inode already seen or otherwise not relevant */
645 r
= boot_config_load_type1(config
, f
, root
, full
, de
->d_name
);
646 if (r
== -ENOMEM
) /* ignore all other errors */
653 static int boot_entry_load_unified(
656 const char *osrelease
,
660 _cleanup_free_
char *fname
= NULL
, *os_pretty_name
= NULL
, *os_image_id
= NULL
, *os_name
= NULL
, *os_id
= NULL
,
661 *os_image_version
= NULL
, *os_version
= NULL
, *os_version_id
= NULL
, *os_build_id
= NULL
;
662 _cleanup_(boot_entry_free
) BootEntry tmp
= BOOT_ENTRY_INIT(BOOT_ENTRY_UNIFIED
);
663 const char *k
, *good_name
, *good_version
, *good_sort_key
;
664 _cleanup_fclose_
FILE *f
= NULL
;
671 k
= path_startswith(path
, root
);
673 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Path is not below root: %s", path
);
675 f
= fmemopen_unlocked((void*) osrelease
, strlen(osrelease
), "r");
677 return log_error_errno(errno
, "Failed to open os-release buffer: %m");
679 r
= parse_env_file(f
, "os-release",
680 "PRETTY_NAME", &os_pretty_name
,
681 "IMAGE_ID", &os_image_id
,
684 "IMAGE_VERSION", &os_image_version
,
685 "VERSION", &os_version
,
686 "VERSION_ID", &os_version_id
,
687 "BUILD_ID", &os_build_id
);
689 return log_error_errno(r
, "Failed to parse os-release data from unified kernel image %s: %m", path
);
691 if (!bootspec_pick_name_version_sort_key(
703 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Missing fields in os-release data from unified kernel image %s, refusing.", path
);
705 r
= path_extract_filename(path
, &fname
);
707 return log_error_errno(r
, "Failed to extract file name from '%s': %m", path
);
709 r
= boot_filename_extract_tries(fname
, &tmp
.id
, &tmp
.tries_left
, &tmp
.tries_done
);
713 if (!efi_loader_entry_name_valid(tmp
.id
))
714 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry name: %s", tmp
.id
);
716 if (os_id
&& os_version_id
) {
717 tmp
.id_old
= strjoin(os_id
, "-", os_version_id
);
722 tmp
.path
= strdup(path
);
726 tmp
.root
= strdup(root
);
730 tmp
.kernel
= path_make_absolute(k
, "/");
734 tmp
.options
= strv_new(skip_leading_chars(cmdline
, WHITESPACE
));
738 delete_trailing_chars(tmp
.options
[0], WHITESPACE
);
740 tmp
.title
= strdup(good_name
);
745 tmp
.sort_key
= strdup(good_sort_key
);
751 tmp
.version
= strdup(good_version
);
756 *ret
= TAKE_STRUCT(tmp
);
760 /* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
761 * the ones we do care about and we are willing to load into memory have this size limit.) */
762 #define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
764 static int find_sections(
767 IMAGE_SECTION_HEADER
**ret_sections
,
768 PeHeader
**ret_pe_header
) {
770 _cleanup_free_ IMAGE_DOS_HEADER
*dos_header
= NULL
;
771 IMAGE_SECTION_HEADER
*sections
;
778 r
= pe_load_headers(fd
, &dos_header
, &pe_header
);
780 return log_warning_errno(r
, "Failed to parse PE file '%s': %m", path
);
782 r
= pe_load_sections(fd
, dos_header
, pe_header
, §ions
);
784 return log_warning_errno(r
, "Failed to parse PE sections of '%s': %m", path
);
787 *ret_pe_header
= TAKE_PTR(pe_header
);
789 *ret_sections
= TAKE_PTR(sections
);
794 static int find_cmdline_section(
797 IMAGE_SECTION_HEADER
*sections
,
799 char **ret_cmdline
) {
802 char *cmdline
= NULL
;
807 r
= pe_read_section_data(fd
, pe_header
, sections
, ".cmdline", PE_SECTION_SIZE_MAX
, (void**) &cmdline
, NULL
);
808 if (r
== -ENXIO
) /* cmdline is optional */
811 return log_warning_errno(r
, "Failed to read .cmdline section of '%s': %m", path
);
812 else if (ret_cmdline
)
813 *ret_cmdline
= TAKE_PTR(cmdline
);
818 static int find_osrel_section(
821 IMAGE_SECTION_HEADER
*sections
,
823 char **ret_osrelease
) {
830 r
= pe_read_section_data(fd
, pe_header
, sections
, ".osrel", PE_SECTION_SIZE_MAX
, (void**) ret_osrelease
, NULL
);
832 return log_warning_errno(r
, "Failed to read .osrel section of '%s': %m", path
);
837 static int find_uki_sections(
840 char **ret_osrelease
,
841 char **ret_cmdline
) {
843 _cleanup_free_ IMAGE_SECTION_HEADER
*sections
= NULL
;
844 _cleanup_free_ PeHeader
*pe_header
= NULL
;
847 r
= find_sections(fd
, path
, §ions
, &pe_header
);
851 if (!pe_is_uki(pe_header
, sections
))
852 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG
), "Parsed PE file '%s' is not a UKI.", path
);
854 r
= find_osrel_section(fd
, path
, sections
, pe_header
, ret_osrelease
);
858 r
= find_cmdline_section(fd
, path
, sections
, pe_header
, ret_cmdline
);
865 static int find_addon_sections(
868 char **ret_cmdline
) {
870 _cleanup_free_ IMAGE_SECTION_HEADER
*sections
= NULL
;
871 _cleanup_free_ PeHeader
*pe_header
= NULL
;
874 r
= find_sections(fd
, path
, §ions
, &pe_header
);
878 return find_cmdline_section(fd
, path
, sections
, pe_header
, ret_cmdline
);
881 static int insert_boot_entry_addon(
882 BootEntryAddons
*addons
,
886 if (!GREEDY_REALLOC(addons
->items
, addons
->count
+ 1))
889 addons
->items
[addons
->count
] = (BootEntryAddon
) {
890 .location
= location
,
898 static int boot_entries_find_unified_addons(
901 const char *addon_dir
,
903 BootEntryAddons
*addons
) {
905 _cleanup_closedir_
DIR *d
= NULL
;
906 _cleanup_free_
char *full
= NULL
;
912 r
= chase_and_opendirat(d_fd
, addon_dir
, CHASE_AT_RESOLVE_IN_ROOT
, &full
, &d
);
916 return log_error_errno(r
, "Failed to open '%s/%s': %m", root
, addon_dir
);
918 *addons
= (BootEntryAddons
) {};
920 FOREACH_DIRENT(de
, d
, return log_error_errno(errno
, "Failed to read %s: %m", full
)) {
921 _cleanup_free_
char *j
= NULL
, *cmdline
= NULL
, *location
= NULL
;
922 _cleanup_close_
int fd
= -EBADF
;
924 if (!dirent_is_file(de
))
927 if (!endswith_no_case(de
->d_name
, ".addon.efi"))
930 fd
= openat(dirfd(d
), de
->d_name
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOFOLLOW
|O_NOCTTY
);
932 log_warning_errno(errno
, "Failed to open %s/%s, ignoring: %m", full
, de
->d_name
);
936 r
= config_check_inode_relevant_and_unseen(config
, fd
, de
->d_name
);
939 if (r
== 0) /* inode already seen or otherwise not relevant */
942 j
= path_join(full
, de
->d_name
);
946 if (find_addon_sections(fd
, j
, &cmdline
) < 0)
949 location
= strdup(j
);
953 r
= insert_boot_entry_addon(addons
, TAKE_PTR(location
), TAKE_PTR(cmdline
));
962 static int boot_entries_find_unified_global_addons(
965 const char *d_name
) {
968 _cleanup_closedir_
DIR *d
= NULL
;
970 r
= chase_and_opendir(root
, NULL
, CHASE_PROHIBIT_SYMLINKS
, NULL
, &d
);
974 return log_error_errno(r
, "Failed to open '%s/%s': %m", root
, d_name
);
976 return boot_entries_find_unified_addons(config
, dirfd(d
), d_name
, root
, &config
->global_addons
);
979 static int boot_entries_find_unified_local_addons(
986 _cleanup_free_
char *addon_dir
= NULL
;
990 addon_dir
= strjoin(d_name
, ".extra.d");
994 return boot_entries_find_unified_addons(config
, d_fd
, addon_dir
, root
, &ret
->local_addons
);
997 static int boot_entries_find_unified(
1002 _cleanup_closedir_
DIR *d
= NULL
;
1003 _cleanup_free_
char *full
= NULL
;
1009 r
= chase_and_opendir(dir
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, &full
, &d
);
1013 return log_error_errno(r
, "Failed to open '%s/%s': %m", root
, dir
);
1015 FOREACH_DIRENT(de
, d
, return log_error_errno(errno
, "Failed to read %s: %m", full
)) {
1016 _cleanup_free_
char *j
= NULL
, *osrelease
= NULL
, *cmdline
= NULL
;
1017 _cleanup_close_
int fd
= -EBADF
;
1019 if (!dirent_is_file(de
))
1022 if (!endswith_no_case(de
->d_name
, ".efi"))
1025 if (!GREEDY_REALLOC0(config
->entries
, config
->n_entries
+ 1))
1028 fd
= openat(dirfd(d
), de
->d_name
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOFOLLOW
|O_NOCTTY
);
1030 log_warning_errno(errno
, "Failed to open %s/%s, ignoring: %m", full
, de
->d_name
);
1034 r
= config_check_inode_relevant_and_unseen(config
, fd
, de
->d_name
);
1037 if (r
== 0) /* inode already seen or otherwise not relevant */
1040 j
= path_join(full
, de
->d_name
);
1044 if (find_uki_sections(fd
, j
, &osrelease
, &cmdline
) < 0)
1047 r
= boot_entry_load_unified(root
, j
, osrelease
, cmdline
, config
->entries
+ config
->n_entries
);
1051 /* look for .efi.extra.d */
1052 r
= boot_entries_find_unified_local_addons(config
, dirfd(d
), de
->d_name
, full
, config
->entries
+ config
->n_entries
);
1056 config
->n_entries
++;
1062 static bool find_nonunique(const BootEntry
*entries
, size_t n_entries
, bool arr
[]) {
1063 bool non_unique
= false;
1065 assert(entries
|| n_entries
== 0);
1066 assert(arr
|| n_entries
== 0);
1068 for (size_t i
= 0; i
< n_entries
; i
++)
1071 for (size_t i
= 0; i
< n_entries
; i
++)
1072 for (size_t j
= 0; j
< n_entries
; j
++)
1073 if (i
!= j
&& streq(boot_entry_title(entries
+ i
),
1074 boot_entry_title(entries
+ j
)))
1075 non_unique
= arr
[i
] = arr
[j
] = true;
1080 static int boot_entries_uniquify(BootEntry
*entries
, size_t n_entries
) {
1081 _cleanup_free_
bool *arr
= NULL
;
1084 assert(entries
|| n_entries
== 0);
1089 arr
= new(bool, n_entries
);
1093 /* Find _all_ non-unique titles */
1094 if (!find_nonunique(entries
, n_entries
, arr
))
1097 /* Add version to non-unique titles */
1098 for (size_t i
= 0; i
< n_entries
; i
++)
1099 if (arr
[i
] && entries
[i
].version
) {
1100 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].version
) < 0)
1103 free_and_replace(entries
[i
].show_title
, s
);
1106 if (!find_nonunique(entries
, n_entries
, arr
))
1109 /* Add machine-id to non-unique titles */
1110 for (size_t i
= 0; i
< n_entries
; i
++)
1111 if (arr
[i
] && entries
[i
].machine_id
) {
1112 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].machine_id
) < 0)
1115 free_and_replace(entries
[i
].show_title
, s
);
1118 if (!find_nonunique(entries
, n_entries
, arr
))
1121 /* Add file name to non-unique titles */
1122 for (size_t i
= 0; i
< n_entries
; i
++)
1124 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].id
) < 0)
1127 free_and_replace(entries
[i
].show_title
, s
);
1133 static int boot_config_find(const BootConfig
*config
, const char *id
) {
1140 if (!strcaseeq(id
, "@saved"))
1142 if (!config
->entry_selected
)
1144 id
= config
->entry_selected
;
1147 for (size_t i
= 0; i
< config
->n_entries
; i
++)
1148 if (fnmatch(id
, config
->entries
[i
].id
, FNM_CASEFOLD
) == 0)
1154 static int boot_entries_select_default(const BootConfig
*config
) {
1158 assert(config
->entries
|| config
->n_entries
== 0);
1160 if (config
->n_entries
== 0) {
1161 log_debug("Found no default boot entry :(");
1162 return -1; /* -1 means "no default" */
1165 if (config
->entry_oneshot
) {
1166 i
= boot_config_find(config
, config
->entry_oneshot
);
1168 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
1169 config
->entries
[i
].id
);
1174 if (config
->entry_default
) {
1175 i
= boot_config_find(config
, config
->entry_default
);
1177 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
1178 config
->entries
[i
].id
);
1183 if (config
->default_pattern
) {
1184 i
= boot_config_find(config
, config
->default_pattern
);
1186 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
1187 config
->entries
[i
].id
, config
->default_pattern
);
1192 log_debug("Found default: first entry \"%s\"", config
->entries
[0].id
);
1196 static int boot_entries_select_selected(const BootConfig
*config
) {
1198 assert(config
->entries
|| config
->n_entries
== 0);
1200 if (!config
->entry_selected
|| config
->n_entries
== 0)
1203 return boot_config_find(config
, config
->entry_selected
);
1206 static int boot_load_efi_entry_pointers(BootConfig
*config
, bool skip_efivars
) {
1211 if (skip_efivars
|| !is_efi_boot())
1214 /* Loads the three "pointers" to boot loader entries from their EFI variables */
1216 r
= efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot
), &config
->entry_oneshot
);
1219 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
))
1220 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m");
1222 r
= efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryDefault
), &config
->entry_default
);
1225 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
))
1226 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m");
1228 r
= efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntrySelected
), &config
->entry_selected
);
1231 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
))
1232 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m");
1237 int boot_config_select_special_entries(BootConfig
*config
, bool skip_efivars
) {
1242 r
= boot_load_efi_entry_pointers(config
, skip_efivars
);
1246 config
->default_entry
= boot_entries_select_default(config
);
1247 config
->selected_entry
= boot_entries_select_selected(config
);
1252 int boot_config_finalize(BootConfig
*config
) {
1255 typesafe_qsort(config
->entries
, config
->n_entries
, boot_entry_compare
);
1257 r
= boot_entries_uniquify(config
->entries
, config
->n_entries
);
1259 return log_error_errno(r
, "Failed to uniquify boot entries: %m");
1264 int boot_config_load(
1266 const char *esp_path
,
1267 const char *xbootldr_path
) {
1272 config
->global_addons
= (BootEntryAddons
) {};
1275 r
= boot_loader_read_conf_path(config
, esp_path
, "/loader/loader.conf");
1279 r
= boot_entries_find_type1(config
, esp_path
, "/loader/entries");
1283 r
= boot_entries_find_unified(config
, esp_path
, "/EFI/Linux/");
1287 r
= boot_entries_find_unified_global_addons(config
, esp_path
, "/loader/addons/");
1292 if (xbootldr_path
) {
1293 r
= boot_entries_find_type1(config
, xbootldr_path
, "/loader/entries");
1297 r
= boot_entries_find_unified(config
, xbootldr_path
, "/EFI/Linux/");
1302 return boot_config_finalize(config
);
1305 int boot_config_load_auto(
1307 const char *override_esp_path
,
1308 const char *override_xbootldr_path
) {
1310 _cleanup_free_
char *esp_where
= NULL
, *xbootldr_where
= NULL
;
1311 dev_t esp_devid
= 0, xbootldr_devid
= 0;
1316 /* This function is similar to boot_entries_load_config(), however we automatically search for the
1317 * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
1318 * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
1319 * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
1320 * it). This allows other boot loaders to pass boot loader entry information to our tools if they
1323 if (!override_esp_path
&& !override_xbootldr_path
) {
1324 if (access("/run/boot-loader-entries/", F_OK
) >= 0)
1325 return boot_config_load(config
, "/run/boot-loader-entries/", NULL
);
1327 if (errno
!= ENOENT
)
1328 return log_error_errno(errno
,
1329 "Failed to determine whether /run/boot-loader-entries/ exists: %m");
1332 r
= find_esp_and_warn(NULL
, override_esp_path
, /* unprivileged_mode= */ false, &esp_where
, NULL
, NULL
, NULL
, NULL
, &esp_devid
);
1333 if (r
< 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
1336 r
= find_xbootldr_and_warn(NULL
, override_xbootldr_path
, /* unprivileged_mode= */ false, &xbootldr_where
, NULL
, &xbootldr_devid
);
1337 if (r
< 0 && r
!= -ENOKEY
)
1338 return r
; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
1340 /* If both paths actually refer to the same inode, suppress the xbootldr path */
1341 if (esp_where
&& xbootldr_where
&& devnum_set_and_equal(esp_devid
, xbootldr_devid
))
1342 xbootldr_where
= mfree(xbootldr_where
);
1344 return boot_config_load(config
, esp_where
, xbootldr_where
);
1347 int boot_config_augment_from_loader(
1349 char **found_by_loader
,
1352 static const char *const title_table
[] = {
1353 /* Pretty names for a few well-known automatically discovered entries. */
1354 "auto-osx", "macOS",
1355 "auto-windows", "Windows Boot Manager",
1356 "auto-efi-shell", "EFI Shell",
1357 "auto-efi-default", "EFI Default Loader",
1358 "auto-poweroff", "Power Off The System",
1359 "auto-reboot", "Reboot The System",
1360 "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
1366 /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
1367 * already included there. */
1369 STRV_FOREACH(i
, found_by_loader
) {
1370 BootEntry
*existing
;
1371 _cleanup_free_
char *c
= NULL
, *t
= NULL
, *p
= NULL
;
1373 existing
= boot_config_find_entry(config
, *i
);
1375 existing
->reported_by_loader
= true;
1379 if (only_auto
&& !startswith(*i
, "auto-"))
1386 STRV_FOREACH_PAIR(a
, b
, title_table
)
1387 if (streq(*a
, *i
)) {
1394 p
= strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderEntries
)));
1398 if (!GREEDY_REALLOC0(config
->entries
, config
->n_entries
+ 1))
1401 config
->entries
[config
->n_entries
++] = (BootEntry
) {
1402 .type
= startswith(*i
, "auto-") ? BOOT_ENTRY_LOADER_AUTO
: BOOT_ENTRY_LOADER
,
1404 .title
= TAKE_PTR(t
),
1405 .path
= TAKE_PTR(p
),
1406 .reported_by_loader
= true,
1407 .tries_left
= UINT_MAX
,
1408 .tries_done
= UINT_MAX
,
1415 BootEntry
* boot_config_find_entry(BootConfig
*config
, const char *id
) {
1419 for (size_t j
= 0; j
< config
->n_entries
; j
++)
1420 if (strcaseeq_ptr(config
->entries
[j
].id
, id
) ||
1421 strcaseeq_ptr(config
->entries
[j
].id_old
, id
))
1422 return config
->entries
+ j
;
1427 static void boot_entry_file_list(
1436 int status
= chase_and_access(p
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, F_OK
, NULL
);
1438 /* Note that this shows two '/' between the root and the file. This is intentional to highlight (in
1439 * the absence of color support) to the user that the boot loader is only interested in the second
1440 * part of the file. */
1441 printf("%13s%s %s%s/%s", strempty(field
), field
? ":" : " ", ansi_grey(), root
, ansi_normal());
1445 printf("%s%s%s (%m)\n", ansi_highlight_red(), p
, ansi_normal());
1449 if (*ret_status
== 0 && status
< 0)
1450 *ret_status
= status
;
1453 static void print_addon(
1454 BootEntryAddon
*addon
,
1455 const char *addon_str
) {
1457 printf(" %s: %s\n", addon_str
, addon
->location
);
1458 printf(" cmdline: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT
), addon
->cmdline
);
1461 static int print_cmdline(
1463 const BootEntryAddons
*global_arr
) {
1465 _cleanup_free_
char *final_cmdline
= NULL
;
1469 if (!strv_isempty(e
->options
)) {
1470 _cleanup_free_
char *t
= NULL
, *t2
= NULL
;
1471 _cleanup_strv_free_
char **ts
= NULL
;
1473 t
= strv_join(e
->options
, " ");
1477 ts
= strv_split_newlines(t
);
1481 t2
= strv_join(ts
, "\n ");
1485 printf(" ukiCmdline: %s\n", t2
);
1486 final_cmdline
= TAKE_PTR(t2
);
1489 FOREACH_ARRAY(addon
, global_arr
->items
, global_arr
->count
) {
1490 print_addon(addon
, "globalAddon");
1491 if (!strextend(&final_cmdline
, " ", addon
->cmdline
))
1495 FOREACH_ARRAY(addon
, e
->local_addons
.items
, e
->local_addons
.count
) {
1496 /* Add space at the beginning of addon_str to align it correctly */
1497 print_addon(addon
, " localAddon");
1498 if (!strextend(&final_cmdline
, " ", addon
->cmdline
))
1503 printf(" finalCmdline: %s\n", final_cmdline
);
1508 static int json_addon(
1509 BootEntryAddon
*addon
,
1510 const char *addon_str
,
1511 JsonVariant
**array
) {
1515 r
= json_variant_append_arrayb(array
,
1517 JSON_BUILD_PAIR(addon_str
, JSON_BUILD_STRING(addon
->location
)),
1518 JSON_BUILD_PAIR("cmdline", JSON_BUILD_STRING(addon
->cmdline
))));
1525 static int json_cmdline(
1527 const BootEntryAddons
*global_arr
,
1530 _cleanup_free_
char *final_cmdline
= NULL
, *def_cmdline
= NULL
;
1531 _cleanup_(json_variant_unrefp
) JsonVariant
*addons_array
= NULL
;
1536 if (!strv_isempty(e
->options
)) {
1537 def_cmdline
= strv_join(e
->options
, " ");
1540 final_cmdline
= TAKE_PTR(def_cmdline
);
1543 FOREACH_ARRAY(addon
, global_arr
->items
, global_arr
->count
) {
1544 r
= json_addon(addon
, "globalAddon", &addons_array
);
1547 if (!strextend(&final_cmdline
, " ", addon
->cmdline
))
1551 FOREACH_ARRAY(addon
, e
->local_addons
.items
, e
->local_addons
.count
) {
1552 r
= json_addon(addon
, "localAddon", &addons_array
);
1555 if (!strextend(&final_cmdline
, " ", addon
->cmdline
))
1559 r
= json_variant_merge_objectb(
1560 v
, JSON_BUILD_OBJECT(
1561 JSON_BUILD_PAIR_CONDITION(def_cmdline
, "ukiCmdline", JSON_BUILD_STRING(def_cmdline
)),
1562 JSON_BUILD_PAIR("addons", JSON_BUILD_VARIANT(addons_array
)),
1563 JSON_BUILD_PAIR_CONDITION(final_cmdline
, "finalCmdline", JSON_BUILD_STRING(final_cmdline
))));
1569 int show_boot_entry(
1571 const BootEntryAddons
*global_addons
,
1572 bool show_as_default
,
1573 bool show_as_selected
,
1574 bool show_reported
) {
1576 int status
= 0, r
= 0;
1578 /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
1579 boot entry itself. */
1583 printf(" type: %s\n",
1584 boot_entry_type_to_string(e
->type
));
1586 printf(" title: %s%s%s",
1587 ansi_highlight(), boot_entry_title(e
), ansi_normal());
1589 if (show_as_default
)
1590 printf(" %s(default)%s",
1591 ansi_highlight_green(), ansi_normal());
1593 if (show_as_selected
)
1594 printf(" %s(selected)%s",
1595 ansi_highlight_magenta(), ansi_normal());
1597 if (show_reported
) {
1598 if (e
->type
== BOOT_ENTRY_LOADER
)
1599 printf(" %s(reported/absent)%s",
1600 ansi_highlight_red(), ansi_normal());
1601 else if (!e
->reported_by_loader
&& e
->type
!= BOOT_ENTRY_LOADER_AUTO
)
1602 printf(" %s(not reported/new)%s",
1603 ansi_highlight_green(), ansi_normal());
1609 printf(" id: %s\n", e
->id
);
1611 _cleanup_free_
char *text
= NULL
, *link
= NULL
;
1613 const char *p
= e
->root
? path_startswith(e
->path
, e
->root
) : NULL
;
1615 text
= strjoin(ansi_grey(), e
->root
, "/", ansi_normal(), "/", p
);
1620 /* Let's urlify the link to make it easy to view in an editor, but only if it is a text
1621 * file. Unified images are binary ELFs, and EFI variables are not pure text either. */
1622 if (e
->type
== BOOT_ENTRY_CONF
)
1623 (void) terminal_urlify_path(e
->path
, text
, &link
);
1625 printf(" source: %s\n", link
?: text
?: e
->path
);
1627 if (e
->tries_left
!= UINT_MAX
) {
1628 printf(" tries: %u left", e
->tries_left
);
1630 if (e
->tries_done
!= UINT_MAX
)
1631 printf("; %u done\n", e
->tries_done
);
1637 printf(" sort-key: %s\n", e
->sort_key
);
1639 printf(" version: %s\n", e
->version
);
1641 printf(" machine-id: %s\n", e
->machine_id
);
1642 if (e
->architecture
)
1643 printf(" architecture: %s\n", e
->architecture
);
1645 boot_entry_file_list("linux", e
->root
, e
->kernel
, &status
);
1647 boot_entry_file_list("efi", e
->root
, e
->efi
, &status
);
1649 STRV_FOREACH(s
, e
->initrd
)
1650 boot_entry_file_list(s
== e
->initrd
? "initrd" : NULL
,
1655 r
= print_cmdline(e
, global_addons
);
1660 boot_entry_file_list("devicetree", e
->root
, e
->device_tree
, &status
);
1662 STRV_FOREACH(s
, e
->device_tree_overlay
)
1663 boot_entry_file_list(s
== e
->device_tree_overlay
? "devicetree-overlay" : NULL
,
1671 int show_boot_entries(const BootConfig
*config
, JsonFormatFlags json_format
) {
1676 if (!FLAGS_SET(json_format
, JSON_FORMAT_OFF
)) {
1677 _cleanup_(json_variant_unrefp
) JsonVariant
*array
= NULL
;
1679 for (size_t i
= 0; i
< config
->n_entries
; i
++) {
1680 const BootEntry
*e
= config
->entries
+ i
;
1681 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
1683 r
= json_variant_merge_objectb(
1684 &v
, JSON_BUILD_OBJECT(
1685 JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e
->type
))),
1686 JSON_BUILD_PAIR_CONDITION(e
->id
, "id", JSON_BUILD_STRING(e
->id
)),
1687 JSON_BUILD_PAIR_CONDITION(e
->path
, "path", JSON_BUILD_STRING(e
->path
)),
1688 JSON_BUILD_PAIR_CONDITION(e
->root
, "root", JSON_BUILD_STRING(e
->root
)),
1689 JSON_BUILD_PAIR_CONDITION(e
->title
, "title", JSON_BUILD_STRING(e
->title
)),
1690 JSON_BUILD_PAIR_CONDITION(boot_entry_title(e
), "showTitle", JSON_BUILD_STRING(boot_entry_title(e
))),
1691 JSON_BUILD_PAIR_CONDITION(e
->sort_key
, "sortKey", JSON_BUILD_STRING(e
->sort_key
)),
1692 JSON_BUILD_PAIR_CONDITION(e
->version
, "version", JSON_BUILD_STRING(e
->version
)),
1693 JSON_BUILD_PAIR_CONDITION(e
->machine_id
, "machineId", JSON_BUILD_STRING(e
->machine_id
)),
1694 JSON_BUILD_PAIR_CONDITION(e
->architecture
, "architecture", JSON_BUILD_STRING(e
->architecture
)),
1695 JSON_BUILD_PAIR_CONDITION(e
->kernel
, "linux", JSON_BUILD_STRING(e
->kernel
)),
1696 JSON_BUILD_PAIR_CONDITION(e
->efi
, "efi", JSON_BUILD_STRING(e
->efi
)),
1697 JSON_BUILD_PAIR_CONDITION(!strv_isempty(e
->initrd
), "initrd", JSON_BUILD_STRV(e
->initrd
)),
1698 JSON_BUILD_PAIR_CONDITION(e
->device_tree
, "devicetree", JSON_BUILD_STRING(e
->device_tree
)),
1699 JSON_BUILD_PAIR_CONDITION(!strv_isempty(e
->device_tree_overlay
), "devicetreeOverlay", JSON_BUILD_STRV(e
->device_tree_overlay
))));
1703 r
= json_cmdline(e
, &config
->global_addons
, &v
);
1707 /* Sanitizers (only memory sanitizer?) do not like function call with too many
1708 * arguments and trigger false positive warnings. Let's not add too many json objects
1710 r
= json_variant_merge_objectb(
1711 &v
, JSON_BUILD_OBJECT(
1712 JSON_BUILD_PAIR("isReported", JSON_BUILD_BOOLEAN(e
->reported_by_loader
)),
1713 JSON_BUILD_PAIR_CONDITION(e
->tries_left
!= UINT_MAX
, "triesLeft", JSON_BUILD_UNSIGNED(e
->tries_left
)),
1714 JSON_BUILD_PAIR_CONDITION(e
->tries_done
!= UINT_MAX
, "triesDone", JSON_BUILD_UNSIGNED(e
->tries_done
)),
1715 JSON_BUILD_PAIR_CONDITION(config
->default_entry
>= 0, "isDefault", JSON_BUILD_BOOLEAN(i
== (size_t) config
->default_entry
)),
1716 JSON_BUILD_PAIR_CONDITION(config
->selected_entry
>= 0, "isSelected", JSON_BUILD_BOOLEAN(i
== (size_t) config
->selected_entry
))));
1721 r
= json_variant_append_array(&array
, v
);
1726 json_variant_dump(array
, json_format
| JSON_FORMAT_EMPTY_ARRAY
, NULL
, NULL
);
1729 for (size_t n
= 0; n
< config
->n_entries
; n
++) {
1730 r
= show_boot_entry(
1731 config
->entries
+ n
,
1732 &config
->global_addons
,
1733 /* show_as_default= */ n
== (size_t) config
->default_entry
,
1734 /* show_as_selected= */ n
== (size_t) config
->selected_entry
,
1735 /* show_discovered= */ true);
1739 if (n
+1 < config
->n_entries
)