1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "bootspec-fundamental.h"
7 #include "chase-symlinks.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-header.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 void boot_entry_free(BootEntry
*entry
) {
45 free(entry
->show_title
);
46 free(entry
->sort_key
);
48 free(entry
->machine_id
);
49 free(entry
->architecture
);
50 strv_free(entry
->options
);
53 strv_free(entry
->initrd
);
54 free(entry
->device_tree
);
55 strv_free(entry
->device_tree_overlay
);
58 static int mangle_path(
65 _cleanup_free_
char *c
= NULL
;
71 /* Spec leaves open if prefixed with "/" or not, let's normalize that */
72 if (path_is_absolute(p
))
79 /* We only reference files, never directories */
80 if (endswith(c
, "/")) {
81 log_syntax(NULL
, LOG_WARNING
, fname
, line
, 0, "Path in field '%s' has trailing slash, ignoring: %s", field
, c
);
86 /* Remove duplicate "/" */
89 /* No ".." or "." or so */
90 if (!path_is_normalized(c
)) {
91 log_syntax(NULL
, LOG_WARNING
, fname
, line
, 0, "Path in field '%s' is not normalized, ignoring: %s", field
, c
);
100 static int parse_path_one(
107 _cleanup_free_
char *c
= NULL
;
114 r
= mangle_path(fname
, line
, field
, p
, &c
);
118 return free_and_replace(*s
, c
);
121 static int parse_path_strv(
135 r
= mangle_path(fname
, line
, field
, p
, &c
);
139 return strv_consume(s
, c
);
142 static int parse_path_many(
149 _cleanup_strv_free_
char **l
= NULL
, **f
= NULL
;
152 l
= strv_split(p
, NULL
);
159 r
= mangle_path(fname
, line
, field
, *i
, &c
);
165 r
= strv_consume(&f
, c
);
170 return strv_extend_strv(s
, f
, /* filter_duplicates= */ false);
173 static int parse_tries(const char *fname
, const char **p
, unsigned *ret
) {
174 _cleanup_free_
char *d
= NULL
;
184 n
= strspn(*p
, DIGITS
);
194 r
= safe_atou_full(d
, 10, &tries
);
195 if (r
>= 0 && tries
> INT_MAX
) /* sd-boot allows INT_MAX, let's use the same limit */
198 return log_error_errno(r
, "Failed to parse tries counter of filename '%s': %m", fname
);
205 int boot_filename_extract_tries(
208 unsigned *ret_tries_left
,
209 unsigned *ret_tries_done
) {
211 unsigned tries_left
= UINT_MAX
, tries_done
= UINT_MAX
;
212 _cleanup_free_
char *stripped
= NULL
;
213 const char *p
, *suffix
, *m
;
217 assert(ret_stripped
);
218 assert(ret_tries_left
);
219 assert(ret_tries_done
);
221 /* Be liberal with suffix, only insist on a dot. After all we want to cover any capitalization here
222 * (vfat is case insensitive after all), and at least .efi and .conf as suffix. */
223 suffix
= strrchr(fname
, '.');
227 p
= m
= memrchr(fname
, '+', suffix
- fname
);
232 r
= parse_tries(fname
, &p
, &tries_left
);
241 r
= parse_tries(fname
, &p
, &tries_done
);
251 stripped
= strndup(fname
, m
- fname
);
255 if (!strextend(&stripped
, suffix
))
258 *ret_stripped
= TAKE_PTR(stripped
);
259 *ret_tries_left
= tries_left
;
260 *ret_tries_done
= tries_done
;
265 stripped
= strdup(fname
);
269 *ret_stripped
= TAKE_PTR(stripped
);
270 *ret_tries_left
= *ret_tries_done
= UINT_MAX
;
274 static int boot_entry_load_type1(
281 _cleanup_(boot_entry_free
) BootEntry tmp
= BOOT_ENTRY_INIT(BOOT_ENTRY_CONF
);
292 /* Loads a Type #1 boot menu entry from the specified FILE* object */
294 r
= boot_filename_extract_tries(fname
, &tmp
.id
, &tmp
.tries_left
, &tmp
.tries_done
);
298 if (!efi_loader_entry_name_valid(tmp
.id
))
299 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry name: %s", fname
);
301 c
= endswith_no_case(tmp
.id
, ".conf");
303 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry file suffix: %s", fname
);
305 tmp
.id_old
= strndup(tmp
.id
, c
- tmp
.id
); /* Without .conf suffix */
309 tmp
.path
= path_join(dir
, fname
);
313 tmp
.root
= strdup(root
);
318 _cleanup_free_
char *buf
= NULL
, *field
= NULL
;
321 r
= read_line(f
, LONG_LINE_MAX
, &buf
);
325 return log_syntax(NULL
, LOG_ERR
, tmp
.path
, line
, r
, "Line too long.");
327 return log_syntax(NULL
, LOG_ERR
, tmp
.path
, line
, r
, "Error while reading: %m");
332 if (IN_SET(p
[0], '#', '\0'))
335 r
= extract_first_word(&p
, &field
, NULL
, 0);
337 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, r
, "Failed to parse, ignoring line: %m");
341 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, 0, "Bad syntax, ignoring line.");
346 /* Some fields can reasonably have an empty value. In other cases warn. */
347 if (!STR_IN_SET(field
, "options", "devicetree-overlay"))
348 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, 0, "Field '%s' without value, ignoring line.", field
);
353 if (streq(field
, "title"))
354 r
= free_and_strdup(&tmp
.title
, p
);
355 else if (streq(field
, "sort-key"))
356 r
= free_and_strdup(&tmp
.sort_key
, p
);
357 else if (streq(field
, "version"))
358 r
= free_and_strdup(&tmp
.version
, p
);
359 else if (streq(field
, "machine-id"))
360 r
= free_and_strdup(&tmp
.machine_id
, p
);
361 else if (streq(field
, "architecture"))
362 r
= free_and_strdup(&tmp
.architecture
, p
);
363 else if (streq(field
, "options"))
364 r
= strv_extend(&tmp
.options
, p
);
365 else if (streq(field
, "linux"))
366 r
= parse_path_one(tmp
.path
, line
, field
, &tmp
.kernel
, p
);
367 else if (streq(field
, "efi"))
368 r
= parse_path_one(tmp
.path
, line
, field
, &tmp
.efi
, p
);
369 else if (streq(field
, "initrd"))
370 r
= parse_path_strv(tmp
.path
, line
, field
, &tmp
.initrd
, p
);
371 else if (streq(field
, "devicetree"))
372 r
= parse_path_one(tmp
.path
, line
, field
, &tmp
.device_tree
, p
);
373 else if (streq(field
, "devicetree-overlay"))
374 r
= parse_path_many(tmp
.path
, line
, field
, &tmp
.device_tree_overlay
, p
);
376 log_syntax(NULL
, LOG_WARNING
, tmp
.path
, line
, 0, "Unknown line '%s', ignoring.", field
);
380 return log_syntax(NULL
, LOG_ERR
, tmp
.path
, line
, r
, "Error while parsing: %m");
384 tmp
= (BootEntry
) {};
388 int boot_config_load_type1(
402 if (!GREEDY_REALLOC0(config
->entries
, config
->n_entries
+ 1))
405 r
= boot_entry_load_type1(f
, root
, dir
, fname
, config
->entries
+ config
->n_entries
);
413 void boot_config_free(BootConfig
*config
) {
416 free(config
->default_pattern
);
417 free(config
->timeout
);
418 free(config
->editor
);
419 free(config
->auto_entries
);
420 free(config
->auto_firmware
);
421 free(config
->console_mode
);
424 free(config
->entry_oneshot
);
425 free(config
->entry_default
);
426 free(config
->entry_selected
);
428 for (size_t i
= 0; i
< config
->n_entries
; i
++)
429 boot_entry_free(config
->entries
+ i
);
430 free(config
->entries
);
432 set_free(config
->inodes_seen
);
435 int boot_loader_read_conf(BootConfig
*config
, FILE *file
, const char *path
) {
444 _cleanup_free_
char *buf
= NULL
, *field
= NULL
;
447 r
= read_line(file
, LONG_LINE_MAX
, &buf
);
451 return log_syntax(NULL
, LOG_ERR
, path
, line
, r
, "Line too long.");
453 return log_syntax(NULL
, LOG_ERR
, path
, line
, r
, "Error while reading: %m");
458 if (IN_SET(p
[0], '#', '\0'))
461 r
= extract_first_word(&p
, &field
, NULL
, 0);
463 log_syntax(NULL
, LOG_WARNING
, path
, line
, r
, "Failed to parse, ignoring line: %m");
467 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "Bad syntax, ignoring line.");
471 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "Field '%s' without value, ignoring line.", field
);
475 if (streq(field
, "default"))
476 r
= free_and_strdup(&config
->default_pattern
, p
);
477 else if (streq(field
, "timeout"))
478 r
= free_and_strdup(&config
->timeout
, p
);
479 else if (streq(field
, "editor"))
480 r
= free_and_strdup(&config
->editor
, p
);
481 else if (streq(field
, "auto-entries"))
482 r
= free_and_strdup(&config
->auto_entries
, p
);
483 else if (streq(field
, "auto-firmware"))
484 r
= free_and_strdup(&config
->auto_firmware
, p
);
485 else if (streq(field
, "console-mode"))
486 r
= free_and_strdup(&config
->console_mode
, p
);
487 else if (streq(field
, "random-seed-mode"))
488 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "'random-seed-mode' has been deprecated, ignoring.");
489 else if (streq(field
, "beep"))
490 r
= free_and_strdup(&config
->beep
, p
);
492 log_syntax(NULL
, LOG_WARNING
, path
, line
, 0, "Unknown line '%s', ignoring.", field
);
496 return log_syntax(NULL
, LOG_ERR
, path
, line
, r
, "Error while parsing: %m");
502 static int boot_loader_read_conf_path(BootConfig
*config
, const char *root
, const char *path
) {
503 _cleanup_free_
char *full
= NULL
;
504 _cleanup_fclose_
FILE *f
= NULL
;
510 r
= chase_symlinks_and_fopen_unlocked(path
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, "re", &full
, &f
);
514 return log_error_errno(r
, "Failed to open '%s/%s': %m", root
, path
);
516 return boot_loader_read_conf(config
, f
, full
);
519 static int boot_entry_compare(const BootEntry
*a
, const BootEntry
*b
) {
525 r
= CMP(!a
->sort_key
, !b
->sort_key
);
529 if (a
->sort_key
&& b
->sort_key
) {
530 r
= strcmp(a
->sort_key
, b
->sort_key
);
534 r
= strcmp_ptr(a
->machine_id
, b
->machine_id
);
538 r
= -strverscmp_improved(a
->version
, b
->version
);
543 return -strverscmp_improved(a
->id
, b
->id
);
546 static int config_check_inode_relevant_and_unseen(BootConfig
*config
, int fd
, const char *fname
) {
547 _cleanup_free_
char *d
= NULL
;
554 /* So, here's the thing: because of the mess around /efi/ vs. /boot/ vs. /boot/efi/ it might be that
555 * people have these dirs, or subdirs of them symlinked or bind mounted, and we might end up
556 * iterating though some dirs multiple times. Let's thus rather be safe than sorry, and track the
557 * inodes we already processed: let's ignore inodes we have seen already. This should be robust
558 * against any form of symlinking or bind mounting, and effectively suppress any such duplicates. */
560 if (fstat(fd
, &st
) < 0)
561 return log_error_errno(errno
, "Failed to stat('%s'): %m", fname
);
562 if (!S_ISREG(st
.st_mode
)) {
563 log_debug("File '%s' is not a reguar file, ignoring.", fname
);
567 if (set_contains(config
->inodes_seen
, &st
)) {
568 log_debug("Inode '%s' already seen before, ignoring.", fname
);
571 d
= memdup(&st
, sizeof(st
));
574 if (set_ensure_put(&config
->inodes_seen
, &inode_hash_ops
, d
) < 0)
581 static int boot_entries_find_type1(
586 _cleanup_free_ DirectoryEntries
*dentries
= NULL
;
587 _cleanup_free_
char *full
= NULL
;
588 _cleanup_close_
int dir_fd
= -1;
595 dir_fd
= chase_symlinks_and_open(dir
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, O_DIRECTORY
|O_CLOEXEC
, &full
);
596 if (dir_fd
== -ENOENT
)
599 return log_error_errno(dir_fd
, "Failed to open '%s/%s': %m", root
, dir
);
601 r
= readdir_all(dir_fd
, RECURSE_DIR_IGNORE_DOT
, &dentries
);
603 return log_error_errno(r
, "Failed to read directory '%s': %m", full
);
605 for (size_t i
= 0; i
< dentries
->n_entries
; i
++) {
606 const struct dirent
*de
= dentries
->entries
[i
];
607 _cleanup_fclose_
FILE *f
= NULL
;
609 if (!dirent_is_file(de
))
612 if (!endswith_no_case(de
->d_name
, ".conf"))
615 r
= xfopenat(dir_fd
, de
->d_name
, "re", O_NOFOLLOW
|O_NOCTTY
, &f
);
617 log_warning_errno(r
, "Failed to open %s/%s, ignoring: %m", full
, de
->d_name
);
621 r
= config_check_inode_relevant_and_unseen(config
, fileno(f
), de
->d_name
);
624 if (r
== 0) /* inode already seen or otherwise not relevant */
627 r
= boot_config_load_type1(config
, f
, root
, full
, de
->d_name
);
628 if (r
== -ENOMEM
) /* ignore all other errors */
635 static int boot_entry_load_unified(
638 const char *osrelease
,
642 _cleanup_free_
char *fname
= NULL
, *os_pretty_name
= NULL
, *os_image_id
= NULL
, *os_name
= NULL
, *os_id
= NULL
,
643 *os_image_version
= NULL
, *os_version
= NULL
, *os_version_id
= NULL
, *os_build_id
= NULL
;
644 _cleanup_(boot_entry_free
) BootEntry tmp
= BOOT_ENTRY_INIT(BOOT_ENTRY_UNIFIED
);
645 const char *k
, *good_name
, *good_version
, *good_sort_key
;
646 _cleanup_fclose_
FILE *f
= NULL
;
653 k
= path_startswith(path
, root
);
655 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Path is not below root: %s", path
);
657 f
= fmemopen_unlocked((void*) osrelease
, strlen(osrelease
), "r");
659 return log_error_errno(errno
, "Failed to open os-release buffer: %m");
661 r
= parse_env_file(f
, "os-release",
662 "PRETTY_NAME", &os_pretty_name
,
663 "IMAGE_ID", &os_image_id
,
666 "IMAGE_VERSION", &os_image_version
,
667 "VERSION", &os_version
,
668 "VERSION_ID", &os_version_id
,
669 "BUILD_ID", &os_build_id
);
671 return log_error_errno(r
, "Failed to parse os-release data from unified kernel image %s: %m", path
);
673 if (!bootspec_pick_name_version_sort_key(
685 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Missing fields in os-release data from unified kernel image %s, refusing.", path
);
687 r
= path_extract_filename(path
, &fname
);
689 return log_error_errno(r
, "Failed to extract file name from '%s': %m", path
);
691 r
= boot_filename_extract_tries(fname
, &tmp
.id
, &tmp
.tries_left
, &tmp
.tries_done
);
695 if (!efi_loader_entry_name_valid(tmp
.id
))
696 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry name: %s", tmp
.id
);
698 if (os_id
&& os_version_id
) {
699 tmp
.id_old
= strjoin(os_id
, "-", os_version_id
);
704 tmp
.path
= strdup(path
);
708 tmp
.root
= strdup(root
);
712 tmp
.kernel
= path_make_absolute(k
, "/");
716 tmp
.options
= strv_new(skip_leading_chars(cmdline
, WHITESPACE
));
720 delete_trailing_chars(tmp
.options
[0], WHITESPACE
);
722 tmp
.title
= strdup(good_name
);
726 tmp
.sort_key
= strdup(good_sort_key
);
731 tmp
.version
= strdup(good_version
);
737 tmp
= (BootEntry
) {};
741 /* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
742 * the ones we do care about and we are willing to load into memory have this size limit.) */
743 #define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
745 static int find_sections(
748 char **ret_osrelease
,
749 char **ret_cmdline
) {
751 _cleanup_free_
struct PeSectionHeader
*sections
= NULL
;
752 _cleanup_free_
char *osrelease
= NULL
, *cmdline
= NULL
;
755 struct DosFileHeader dos
;
756 n
= pread(fd
, &dos
, sizeof(dos
), 0);
758 return log_warning_errno(errno
, "%s: Failed to read DOS header, ignoring: %m", path
);
759 if (n
!= sizeof(dos
))
760 return log_warning_errno(SYNTHETIC_ERRNO(EIO
), "%s: Short read while reading DOS header, ignoring.", path
);
762 if (dos
.Magic
[0] != 'M' || dos
.Magic
[1] != 'Z')
763 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG
), "%s: DOS executable magic missing, ignoring.", path
);
765 uint64_t start
= unaligned_read_le32(&dos
.ExeHeader
);
768 n
= pread(fd
, &pe
, sizeof(pe
), start
);
770 return log_warning_errno(errno
, "%s: Failed to read PE header, ignoring: %m", path
);
772 return log_warning_errno(SYNTHETIC_ERRNO(EIO
), "%s: Short read while reading PE header, ignoring.", path
);
774 if (pe
.Magic
[0] != 'P' || pe
.Magic
[1] != 'E' || pe
.Magic
[2] != 0 || pe
.Magic
[3] != 0)
775 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG
), "%s: PE executable magic missing, ignoring.", path
);
777 size_t n_sections
= unaligned_read_le16(&pe
.FileHeader
.NumberOfSections
);
779 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG
), "%s: PE header has too many sections, ignoring.", path
);
781 sections
= new(struct PeSectionHeader
, n_sections
);
785 n
= pread(fd
, sections
,
786 n_sections
* sizeof(struct PeSectionHeader
),
787 start
+ sizeof(pe
) + unaligned_read_le16(&pe
.FileHeader
.SizeOfOptionalHeader
));
789 return log_warning_errno(errno
, "%s: Failed to read section data, ignoring: %m", path
);
790 if ((size_t) n
!= n_sections
* sizeof(struct PeSectionHeader
))
791 return log_warning_errno(SYNTHETIC_ERRNO(EIO
), "%s: Short read while reading sections, ignoring.", path
);
793 for (size_t i
= 0; i
< n_sections
; i
++) {
794 _cleanup_free_
char *k
= NULL
;
795 uint32_t offset
, size
;
798 if (strneq((char*) sections
[i
].Name
, ".osrel", sizeof(sections
[i
].Name
)))
800 else if (strneq((char*) sections
[i
].Name
, ".cmdline", sizeof(sections
[i
].Name
)))
806 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG
), "%s: Duplicate section %s, ignoring.", path
, sections
[i
].Name
);
808 offset
= unaligned_read_le32(§ions
[i
].PointerToRawData
);
809 size
= unaligned_read_le32(§ions
[i
].VirtualSize
);
811 if (size
> PE_SECTION_SIZE_MAX
)
812 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG
), "%s: Section %s too large, ignoring.", path
, sections
[i
].Name
);
814 k
= new(char, size
+1);
818 n
= pread(fd
, k
, size
, offset
);
820 return log_warning_errno(errno
, "%s: Failed to read section payload, ignoring: %m", path
);
821 if ((size_t) n
!= size
)
822 return log_warning_errno(SYNTHETIC_ERRNO(EIO
), "%s: Short read while reading section payload, ignoring:", path
);
824 /* Allow one trailing NUL byte, but nothing more. */
825 if (size
> 0 && memchr(k
, 0, size
- 1))
826 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG
), "%s: Section contains embedded NUL byte, ignoring.", path
);
833 return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG
), "%s: Image lacks .osrel section, ignoring.", path
);
836 *ret_osrelease
= TAKE_PTR(osrelease
);
838 *ret_cmdline
= TAKE_PTR(cmdline
);
843 static int boot_entries_find_unified(
848 _cleanup_(closedirp
) DIR *d
= NULL
;
849 _cleanup_free_
char *full
= NULL
;
855 r
= chase_symlinks_and_opendir(dir
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, &full
, &d
);
859 return log_error_errno(r
, "Failed to open '%s/%s': %m", root
, dir
);
861 FOREACH_DIRENT(de
, d
, return log_error_errno(errno
, "Failed to read %s: %m", full
)) {
862 _cleanup_free_
char *j
= NULL
, *osrelease
= NULL
, *cmdline
= NULL
;
863 _cleanup_close_
int fd
= -1;
865 if (!dirent_is_file(de
))
868 if (!endswith_no_case(de
->d_name
, ".efi"))
871 if (!GREEDY_REALLOC0(config
->entries
, config
->n_entries
+ 1))
874 fd
= openat(dirfd(d
), de
->d_name
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOFOLLOW
|O_NOCTTY
);
876 log_warning_errno(errno
, "Failed to open %s/%s, ignoring: %m", full
, de
->d_name
);
880 r
= config_check_inode_relevant_and_unseen(config
, fd
, de
->d_name
);
883 if (r
== 0) /* inode already seen or otherwise not relevant */
886 j
= path_join(full
, de
->d_name
);
890 if (find_sections(fd
, j
, &osrelease
, &cmdline
) < 0)
893 r
= boot_entry_load_unified(root
, j
, osrelease
, cmdline
, config
->entries
+ config
->n_entries
);
903 static bool find_nonunique(const BootEntry
*entries
, size_t n_entries
, bool arr
[]) {
904 bool non_unique
= false;
906 assert(entries
|| n_entries
== 0);
907 assert(arr
|| n_entries
== 0);
909 for (size_t i
= 0; i
< n_entries
; i
++)
912 for (size_t i
= 0; i
< n_entries
; i
++)
913 for (size_t j
= 0; j
< n_entries
; j
++)
914 if (i
!= j
&& streq(boot_entry_title(entries
+ i
),
915 boot_entry_title(entries
+ j
)))
916 non_unique
= arr
[i
] = arr
[j
] = true;
921 static int boot_entries_uniquify(BootEntry
*entries
, size_t n_entries
) {
922 _cleanup_free_
bool *arr
= NULL
;
925 assert(entries
|| n_entries
== 0);
930 arr
= new(bool, n_entries
);
934 /* Find _all_ non-unique titles */
935 if (!find_nonunique(entries
, n_entries
, arr
))
938 /* Add version to non-unique titles */
939 for (size_t i
= 0; i
< n_entries
; i
++)
940 if (arr
[i
] && entries
[i
].version
) {
941 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].version
) < 0)
944 free_and_replace(entries
[i
].show_title
, s
);
947 if (!find_nonunique(entries
, n_entries
, arr
))
950 /* Add machine-id to non-unique titles */
951 for (size_t i
= 0; i
< n_entries
; i
++)
952 if (arr
[i
] && entries
[i
].machine_id
) {
953 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].machine_id
) < 0)
956 free_and_replace(entries
[i
].show_title
, s
);
959 if (!find_nonunique(entries
, n_entries
, arr
))
962 /* Add file name to non-unique titles */
963 for (size_t i
= 0; i
< n_entries
; i
++)
965 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].id
) < 0)
968 free_and_replace(entries
[i
].show_title
, s
);
974 static int boot_config_find(const BootConfig
*config
, const char *id
) {
981 if (!strcaseeq(id
, "@saved"))
983 if (!config
->entry_selected
)
985 id
= config
->entry_selected
;
988 for (size_t i
= 0; i
< config
->n_entries
; i
++)
989 if (fnmatch(id
, config
->entries
[i
].id
, FNM_CASEFOLD
) == 0)
995 static int boot_entries_select_default(const BootConfig
*config
) {
999 assert(config
->entries
|| config
->n_entries
== 0);
1001 if (config
->n_entries
== 0) {
1002 log_debug("Found no default boot entry :(");
1003 return -1; /* -1 means "no default" */
1006 if (config
->entry_oneshot
) {
1007 i
= boot_config_find(config
, config
->entry_oneshot
);
1009 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
1010 config
->entries
[i
].id
);
1015 if (config
->entry_default
) {
1016 i
= boot_config_find(config
, config
->entry_default
);
1018 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
1019 config
->entries
[i
].id
);
1024 if (config
->default_pattern
) {
1025 i
= boot_config_find(config
, config
->default_pattern
);
1027 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
1028 config
->entries
[i
].id
, config
->default_pattern
);
1033 log_debug("Found default: first entry \"%s\"", config
->entries
[0].id
);
1037 static int boot_entries_select_selected(const BootConfig
*config
) {
1039 assert(config
->entries
|| config
->n_entries
== 0);
1041 if (!config
->entry_selected
|| config
->n_entries
== 0)
1044 return boot_config_find(config
, config
->entry_selected
);
1047 static int boot_load_efi_entry_pointers(BootConfig
*config
, bool skip_efivars
) {
1052 if (skip_efivars
|| !is_efi_boot())
1055 /* Loads the three "pointers" to boot loader entries from their EFI variables */
1057 r
= efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot
), &config
->entry_oneshot
);
1060 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
))
1061 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m");
1063 r
= efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryDefault
), &config
->entry_default
);
1066 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
))
1067 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m");
1069 r
= efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntrySelected
), &config
->entry_selected
);
1072 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
))
1073 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m");
1078 int boot_config_select_special_entries(BootConfig
*config
, bool skip_efivars
) {
1083 r
= boot_load_efi_entry_pointers(config
, skip_efivars
);
1087 config
->default_entry
= boot_entries_select_default(config
);
1088 config
->selected_entry
= boot_entries_select_selected(config
);
1093 int boot_config_finalize(BootConfig
*config
) {
1096 typesafe_qsort(config
->entries
, config
->n_entries
, boot_entry_compare
);
1098 r
= boot_entries_uniquify(config
->entries
, config
->n_entries
);
1100 return log_error_errno(r
, "Failed to uniquify boot entries: %m");
1105 int boot_config_load(
1107 const char *esp_path
,
1108 const char *xbootldr_path
) {
1115 r
= boot_loader_read_conf_path(config
, esp_path
, "/loader/loader.conf");
1119 r
= boot_entries_find_type1(config
, esp_path
, "/loader/entries");
1123 r
= boot_entries_find_unified(config
, esp_path
, "/EFI/Linux/");
1128 if (xbootldr_path
) {
1129 r
= boot_entries_find_type1(config
, xbootldr_path
, "/loader/entries");
1133 r
= boot_entries_find_unified(config
, xbootldr_path
, "/EFI/Linux/");
1138 return boot_config_finalize(config
);
1141 int boot_config_load_auto(
1143 const char *override_esp_path
,
1144 const char *override_xbootldr_path
) {
1146 _cleanup_free_
char *esp_where
= NULL
, *xbootldr_where
= NULL
;
1147 dev_t esp_devid
= 0, xbootldr_devid
= 0;
1152 /* This function is similar to boot_entries_load_config(), however we automatically search for the
1153 * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
1154 * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
1155 * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
1156 * it). This allows other boot loaders to pass boot loader entry information to our tools if they
1159 if (!override_esp_path
&& !override_xbootldr_path
) {
1160 if (access("/run/boot-loader-entries/", F_OK
) >= 0)
1161 return boot_config_load(config
, "/run/boot-loader-entries/", NULL
);
1163 if (errno
!= ENOENT
)
1164 return log_error_errno(errno
,
1165 "Failed to determine whether /run/boot-loader-entries/ exists: %m");
1168 r
= find_esp_and_warn(NULL
, override_esp_path
, /* unprivileged_mode= */ false, &esp_where
, NULL
, NULL
, NULL
, NULL
, &esp_devid
);
1169 if (r
< 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
1172 r
= find_xbootldr_and_warn(NULL
, override_xbootldr_path
, /* unprivileged_mode= */ false, &xbootldr_where
, NULL
, &xbootldr_devid
);
1173 if (r
< 0 && r
!= -ENOKEY
)
1174 return r
; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
1176 /* If both paths actually refer to the same inode, suppress the xbootldr path */
1177 if (esp_where
&& xbootldr_where
&& devnum_set_and_equal(esp_devid
, xbootldr_devid
))
1178 xbootldr_where
= mfree(xbootldr_where
);
1180 return boot_config_load(config
, esp_where
, xbootldr_where
);
1183 int boot_config_augment_from_loader(
1185 char **found_by_loader
,
1188 static const char *const title_table
[] = {
1189 /* Pretty names for a few well-known automatically discovered entries. */
1190 "auto-osx", "macOS",
1191 "auto-windows", "Windows Boot Manager",
1192 "auto-efi-shell", "EFI Shell",
1193 "auto-efi-default", "EFI Default Loader",
1194 "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
1200 /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
1201 * already included there. */
1203 STRV_FOREACH(i
, found_by_loader
) {
1204 BootEntry
*existing
;
1205 _cleanup_free_
char *c
= NULL
, *t
= NULL
, *p
= NULL
;
1207 existing
= boot_config_find_entry(config
, *i
);
1209 existing
->reported_by_loader
= true;
1213 if (only_auto
&& !startswith(*i
, "auto-"))
1220 STRV_FOREACH_PAIR(a
, b
, title_table
)
1221 if (streq(*a
, *i
)) {
1228 p
= strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderEntries
)));
1232 if (!GREEDY_REALLOC0(config
->entries
, config
->n_entries
+ 1))
1235 config
->entries
[config
->n_entries
++] = (BootEntry
) {
1236 .type
= startswith(*i
, "auto-") ? BOOT_ENTRY_LOADER_AUTO
: BOOT_ENTRY_LOADER
,
1238 .title
= TAKE_PTR(t
),
1239 .path
= TAKE_PTR(p
),
1240 .reported_by_loader
= true,
1241 .tries_left
= UINT_MAX
,
1242 .tries_done
= UINT_MAX
,
1249 BootEntry
* boot_config_find_entry(BootConfig
*config
, const char *id
) {
1253 for (size_t j
= 0; j
< config
->n_entries
; j
++)
1254 if (streq_ptr(config
->entries
[j
].id
, id
) ||
1255 streq_ptr(config
->entries
[j
].id_old
, id
))
1256 return config
->entries
+ j
;
1261 static void boot_entry_file_list(
1270 int status
= chase_symlinks_and_access(p
, root
, CHASE_PREFIX_ROOT
|CHASE_PROHIBIT_SYMLINKS
, F_OK
, NULL
, NULL
);
1272 /* Note that this shows two '/' between the root and the file. This is intentional to highlight (in
1273 * the absence of color support) to the user that the boot loader is only interested in the second
1274 * part of the file. */
1275 printf("%13s%s %s%s/%s", strempty(field
), field
? ":" : " ", ansi_grey(), root
, ansi_normal());
1279 printf("%s%s%s (%m)\n", ansi_highlight_red(), p
, ansi_normal());
1283 if (*ret_status
== 0 && status
< 0)
1284 *ret_status
= status
;
1287 int show_boot_entry(
1289 bool show_as_default
,
1290 bool show_as_selected
,
1291 bool show_reported
) {
1295 /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
1296 boot entry itself. */
1300 printf(" type: %s\n",
1301 boot_entry_type_to_string(e
->type
));
1303 printf(" title: %s%s%s",
1304 ansi_highlight(), boot_entry_title(e
), ansi_normal());
1306 if (show_as_default
)
1307 printf(" %s(default)%s",
1308 ansi_highlight_green(), ansi_normal());
1310 if (show_as_selected
)
1311 printf(" %s(selected)%s",
1312 ansi_highlight_magenta(), ansi_normal());
1314 if (show_reported
) {
1315 if (e
->type
== BOOT_ENTRY_LOADER
)
1316 printf(" %s(reported/absent)%s",
1317 ansi_highlight_red(), ansi_normal());
1318 else if (!e
->reported_by_loader
&& e
->type
!= BOOT_ENTRY_LOADER_AUTO
)
1319 printf(" %s(not reported/new)%s",
1320 ansi_highlight_green(), ansi_normal());
1326 printf(" id: %s\n", e
->id
);
1328 _cleanup_free_
char *text
= NULL
, *link
= NULL
;
1330 const char *p
= e
->root
? path_startswith(e
->path
, e
->root
) : NULL
;
1332 text
= strjoin(ansi_grey(), e
->root
, "/", ansi_normal(), "/", p
);
1337 /* Let's urlify the link to make it easy to view in an editor, but only if it is a text
1338 * file. Unified images are binary ELFs, and EFI variables are not pure text either. */
1339 if (e
->type
== BOOT_ENTRY_CONF
)
1340 (void) terminal_urlify_path(e
->path
, text
, &link
);
1342 printf(" source: %s\n", link
?: text
?: e
->path
);
1344 if (e
->tries_left
!= UINT_MAX
) {
1345 printf(" tries: %u left", e
->tries_left
);
1347 if (e
->tries_done
!= UINT_MAX
)
1348 printf("; %u done\n", e
->tries_done
);
1354 printf(" sort-key: %s\n", e
->sort_key
);
1356 printf(" version: %s\n", e
->version
);
1358 printf(" machine-id: %s\n", e
->machine_id
);
1359 if (e
->architecture
)
1360 printf(" architecture: %s\n", e
->architecture
);
1362 boot_entry_file_list("linux", e
->root
, e
->kernel
, &status
);
1364 STRV_FOREACH(s
, e
->initrd
)
1365 boot_entry_file_list(s
== e
->initrd
? "initrd" : NULL
,
1370 if (!strv_isempty(e
->options
)) {
1371 _cleanup_free_
char *t
= NULL
, *t2
= NULL
;
1372 _cleanup_strv_free_
char **ts
= NULL
;
1374 t
= strv_join(e
->options
, " ");
1378 ts
= strv_split_newlines(t
);
1382 t2
= strv_join(ts
, "\n ");
1386 printf(" options: %s\n", t2
);
1390 boot_entry_file_list("devicetree", e
->root
, e
->device_tree
, &status
);
1392 STRV_FOREACH(s
, e
->device_tree_overlay
)
1393 boot_entry_file_list(s
== e
->device_tree_overlay
? "devicetree-overlay" : NULL
,
1401 int show_boot_entries(const BootConfig
*config
, JsonFormatFlags json_format
) {
1406 if (!FLAGS_SET(json_format
, JSON_FORMAT_OFF
)) {
1407 _cleanup_(json_variant_unrefp
) JsonVariant
*array
= NULL
;
1409 for (size_t i
= 0; i
< config
->n_entries
; i
++) {
1410 _cleanup_free_
char *opts
= NULL
;
1411 const BootEntry
*e
= config
->entries
+ i
;
1412 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
1414 if (!strv_isempty(e
->options
)) {
1415 opts
= strv_join(e
->options
, " ");
1420 r
= json_append(&v
, JSON_BUILD_OBJECT(
1421 JSON_BUILD_PAIR_CONDITION(e
->id
, "id", JSON_BUILD_STRING(e
->id
)),
1422 JSON_BUILD_PAIR_CONDITION(e
->path
, "path", JSON_BUILD_STRING(e
->path
)),
1423 JSON_BUILD_PAIR_CONDITION(e
->root
, "root", JSON_BUILD_STRING(e
->root
)),
1424 JSON_BUILD_PAIR_CONDITION(e
->title
, "title", JSON_BUILD_STRING(e
->title
)),
1425 JSON_BUILD_PAIR_CONDITION(boot_entry_title(e
), "showTitle", JSON_BUILD_STRING(boot_entry_title(e
))),
1426 JSON_BUILD_PAIR_CONDITION(e
->sort_key
, "sortKey", JSON_BUILD_STRING(e
->sort_key
)),
1427 JSON_BUILD_PAIR_CONDITION(e
->version
, "version", JSON_BUILD_STRING(e
->version
)),
1428 JSON_BUILD_PAIR_CONDITION(e
->machine_id
, "machineId", JSON_BUILD_STRING(e
->machine_id
)),
1429 JSON_BUILD_PAIR_CONDITION(e
->architecture
, "architecture", JSON_BUILD_STRING(e
->architecture
)),
1430 JSON_BUILD_PAIR_CONDITION(opts
, "options", JSON_BUILD_STRING(opts
)),
1431 JSON_BUILD_PAIR_CONDITION(e
->kernel
, "linux", JSON_BUILD_STRING(e
->kernel
)),
1432 JSON_BUILD_PAIR_CONDITION(e
->efi
, "efi", JSON_BUILD_STRING(e
->efi
)),
1433 JSON_BUILD_PAIR_CONDITION(!strv_isempty(e
->initrd
), "initrd", JSON_BUILD_STRV(e
->initrd
)),
1434 JSON_BUILD_PAIR_CONDITION(e
->device_tree
, "devicetree", JSON_BUILD_STRING(e
->device_tree
)),
1435 JSON_BUILD_PAIR_CONDITION(!strv_isempty(e
->device_tree_overlay
), "devicetreeOverlay", JSON_BUILD_STRV(e
->device_tree_overlay
))));
1439 /* Sanitizers (only memory sanitizer?) do not like function call with too many
1440 * arguments and trigger false positive warnings. Let's not add too many json objects
1442 r
= json_append(&v
, JSON_BUILD_OBJECT(
1443 JSON_BUILD_PAIR_CONDITION(e
->tries_left
!= UINT_MAX
, "triesLeft", JSON_BUILD_UNSIGNED(e
->tries_left
)),
1444 JSON_BUILD_PAIR_CONDITION(e
->tries_done
!= UINT_MAX
, "triesDone", JSON_BUILD_UNSIGNED(e
->tries_done
))));
1448 r
= json_variant_append_array(&array
, v
);
1453 json_variant_dump(array
, json_format
| JSON_FORMAT_EMPTY_ARRAY
, NULL
, NULL
);
1456 for (size_t n
= 0; n
< config
->n_entries
; n
++) {
1457 r
= show_boot_entry(
1458 config
->entries
+ n
,
1459 /* show_as_default= */ n
== (size_t) config
->default_entry
,
1460 /* show_as_selected= */ n
== (size_t) config
->selected_entry
,
1461 /* show_discovered= */ true);
1465 if (n
+1 < config
->n_entries
)