]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bootspec.c
814720411684c7afd54b3aec01b058358168daff
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "bootspec-fundamental.h"
7 #include "conf-files.h"
8 #include "dirent-util.h"
9 #include "efi-loader.h"
14 #include "path-util.h"
15 #include "pe-header.h"
16 #include "sort-util.h"
17 #include "stat-util.h"
19 #include "unaligned.h"
21 static void boot_entry_free(BootEntry
*entry
) {
29 free(entry
->show_title
);
30 free(entry
->sort_key
);
32 free(entry
->machine_id
);
33 free(entry
->architecture
);
34 strv_free(entry
->options
);
37 strv_free(entry
->initrd
);
38 free(entry
->device_tree
);
39 strv_free(entry
->device_tree_overlay
);
42 static int boot_entry_load(
47 _cleanup_(boot_entry_free
) BootEntry tmp
= {
48 .type
= BOOT_ENTRY_CONF
,
51 _cleanup_fclose_
FILE *f
= NULL
;
60 r
= path_extract_filename(path
, &tmp
.id
);
62 return log_error_errno(r
, "Failed to extract file name from path '%s': %m", path
);
64 c
= endswith_no_case(tmp
.id
, ".conf");
66 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry file suffix: %s", tmp
.id
);
68 if (!efi_loader_entry_name_valid(tmp
.id
))
69 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry name: %s", tmp
.id
);
71 tmp
.id_old
= strndup(tmp
.id
, c
- tmp
.id
);
75 tmp
.path
= strdup(path
);
79 tmp
.root
= strdup(root
);
83 f
= fopen(path
, "re");
85 return log_error_errno(errno
, "Failed to open \"%s\": %m", path
);
88 _cleanup_free_
char *buf
= NULL
, *field
= NULL
;
91 r
= read_line(f
, LONG_LINE_MAX
, &buf
);
95 return log_error_errno(r
, "%s:%u: Line too long", path
, line
);
97 return log_error_errno(r
, "%s:%u: Error while reading: %m", path
, line
);
101 if (IN_SET(*strstrip(buf
), '#', '\0'))
105 r
= extract_first_word(&p
, &field
, " \t", 0);
107 log_error_errno(r
, "Failed to parse config file %s line %u: %m", path
, line
);
111 log_warning("%s:%u: Bad syntax", path
, line
);
115 if (streq(field
, "title"))
116 r
= free_and_strdup(&tmp
.title
, p
);
117 else if (streq(field
, "sort-key"))
118 r
= free_and_strdup(&tmp
.sort_key
, p
);
119 else if (streq(field
, "version"))
120 r
= free_and_strdup(&tmp
.version
, p
);
121 else if (streq(field
, "machine-id"))
122 r
= free_and_strdup(&tmp
.machine_id
, p
);
123 else if (streq(field
, "architecture"))
124 r
= free_and_strdup(&tmp
.architecture
, p
);
125 else if (streq(field
, "options"))
126 r
= strv_extend(&tmp
.options
, p
);
127 else if (streq(field
, "linux"))
128 r
= free_and_strdup(&tmp
.kernel
, p
);
129 else if (streq(field
, "efi"))
130 r
= free_and_strdup(&tmp
.efi
, p
);
131 else if (streq(field
, "initrd"))
132 r
= strv_extend(&tmp
.initrd
, p
);
133 else if (streq(field
, "devicetree"))
134 r
= free_and_strdup(&tmp
.device_tree
, p
);
135 else if (streq(field
, "devicetree-overlay")) {
136 _cleanup_strv_free_
char **l
= NULL
;
138 l
= strv_split(p
, NULL
);
142 r
= strv_extend_strv(&tmp
.device_tree_overlay
, l
, false);
144 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path
, line
, field
);
148 return log_error_errno(r
, "%s:%u: Error while reading: %m", path
, line
);
152 tmp
= (BootEntry
) {};
156 void boot_config_free(BootConfig
*config
) {
161 free(config
->default_pattern
);
162 free(config
->timeout
);
163 free(config
->editor
);
164 free(config
->auto_entries
);
165 free(config
->auto_firmware
);
166 free(config
->console_mode
);
167 free(config
->random_seed_mode
);
170 free(config
->entry_oneshot
);
171 free(config
->entry_default
);
172 free(config
->entry_selected
);
174 for (i
= 0; i
< config
->n_entries
; i
++)
175 boot_entry_free(config
->entries
+ i
);
176 free(config
->entries
);
179 static int boot_loader_read_conf(const char *path
, BootConfig
*config
) {
180 _cleanup_fclose_
FILE *f
= NULL
;
187 f
= fopen(path
, "re");
192 return log_error_errno(errno
, "Failed to open \"%s\": %m", path
);
196 _cleanup_free_
char *buf
= NULL
, *field
= NULL
;
199 r
= read_line(f
, LONG_LINE_MAX
, &buf
);
203 return log_error_errno(r
, "%s:%u: Line too long", path
, line
);
205 return log_error_errno(r
, "%s:%u: Error while reading: %m", path
, line
);
209 if (IN_SET(*strstrip(buf
), '#', '\0'))
213 r
= extract_first_word(&p
, &field
, " \t", 0);
215 log_error_errno(r
, "Failed to parse config file %s line %u: %m", path
, line
);
219 log_warning("%s:%u: Bad syntax", path
, line
);
223 if (streq(field
, "default"))
224 r
= free_and_strdup(&config
->default_pattern
, p
);
225 else if (streq(field
, "timeout"))
226 r
= free_and_strdup(&config
->timeout
, p
);
227 else if (streq(field
, "editor"))
228 r
= free_and_strdup(&config
->editor
, p
);
229 else if (streq(field
, "auto-entries"))
230 r
= free_and_strdup(&config
->auto_entries
, p
);
231 else if (streq(field
, "auto-firmware"))
232 r
= free_and_strdup(&config
->auto_firmware
, p
);
233 else if (streq(field
, "console-mode"))
234 r
= free_and_strdup(&config
->console_mode
, p
);
235 else if (streq(field
, "random-seed-mode"))
236 r
= free_and_strdup(&config
->random_seed_mode
, p
);
237 else if (streq(field
, "beep"))
238 r
= free_and_strdup(&config
->beep
, p
);
240 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path
, line
, field
);
244 return log_error_errno(r
, "%s:%u: Error while reading: %m", path
, line
);
250 static int boot_entry_compare(const BootEntry
*a
, const BootEntry
*b
) {
256 r
= CMP(!a
->sort_key
, !b
->sort_key
);
259 if (a
->sort_key
&& b
->sort_key
) {
260 r
= strcmp(a
->sort_key
, b
->sort_key
);
264 r
= strcmp_ptr(a
->machine_id
, b
->machine_id
);
268 r
= -strverscmp_improved(a
->version
, b
->version
);
273 return strverscmp_improved(a
->id
, b
->id
);
276 static int boot_entries_find(
282 _cleanup_strv_free_
char **files
= NULL
;
291 r
= conf_files_list(&files
, ".conf", NULL
, 0, dir
);
293 return log_error_errno(r
, "Failed to list files in \"%s\": %m", dir
);
295 STRV_FOREACH(f
, files
) {
296 if (!GREEDY_REALLOC0(*entries
, *n_entries
+ 1))
299 r
= boot_entry_load(root
, *f
, *entries
+ *n_entries
);
309 static int boot_entry_load_unified(
312 const char *osrelease
,
316 _cleanup_free_
char *os_pretty_name
= NULL
, *os_image_id
= NULL
, *os_name
= NULL
, *os_id
= NULL
,
317 *os_image_version
= NULL
, *os_version
= NULL
, *os_version_id
= NULL
, *os_build_id
= NULL
;
318 _cleanup_(boot_entry_free
) BootEntry tmp
= {
319 .type
= BOOT_ENTRY_UNIFIED
,
321 const char *k
, *good_name
, *good_version
, *good_sort_key
;
322 _cleanup_fclose_
FILE *f
= NULL
;
329 k
= path_startswith(path
, root
);
331 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Path is not below root: %s", path
);
333 f
= fmemopen_unlocked((void*) osrelease
, strlen(osrelease
), "r");
335 return log_error_errno(errno
, "Failed to open os-release buffer: %m");
337 r
= parse_env_file(f
, "os-release",
338 "PRETTY_NAME", &os_pretty_name
,
339 "IMAGE_ID", &os_image_id
,
342 "IMAGE_VERSION", &os_image_version
,
343 "VERSION", &os_version
,
344 "VERSION_ID", &os_version_id
,
345 "BUILD_ID", &os_build_id
);
347 return log_error_errno(r
, "Failed to parse os-release data from unified kernel image %s: %m", path
);
349 if (!bootspec_pick_name_version_sort_key(
361 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Missing fields in os-release data from unified kernel image %s, refusing.", path
);
363 r
= path_extract_filename(path
, &tmp
.id
);
365 return log_error_errno(r
, "Failed to extract file name from '%s': %m", path
);
367 if (!efi_loader_entry_name_valid(tmp
.id
))
368 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid loader entry name: %s", tmp
.id
);
370 if (os_id
&& os_version_id
) {
371 tmp
.id_old
= strjoin(os_id
, "-", os_version_id
);
376 tmp
.path
= strdup(path
);
380 tmp
.root
= strdup(root
);
384 tmp
.kernel
= strdup(skip_leading_chars(k
, "/"));
388 tmp
.options
= strv_new(skip_leading_chars(cmdline
, WHITESPACE
));
392 delete_trailing_chars(tmp
.options
[0], WHITESPACE
);
394 tmp
.title
= strdup(good_name
);
398 tmp
.sort_key
= strdup(good_sort_key
);
402 tmp
.version
= strdup(good_version
);
407 tmp
= (BootEntry
) {};
411 /* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
412 * the ones we do care about and we are willing to load into memory have this size limit.) */
413 #define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
415 static int find_sections(
417 char **ret_osrelease
,
418 char **ret_cmdline
) {
420 _cleanup_free_
struct PeSectionHeader
*sections
= NULL
;
421 _cleanup_free_
char *osrelease
= NULL
, *cmdline
= NULL
;
422 size_t i
, n_sections
;
423 struct DosFileHeader dos
;
428 n
= pread(fd
, &dos
, sizeof(dos
), 0);
430 return log_error_errno(errno
, "Failed read DOS header: %m");
431 if (n
!= sizeof(dos
))
432 return log_error_errno(SYNTHETIC_ERRNO(EIO
), "Short read while reading DOS header, refusing.");
434 if (dos
.Magic
[0] != 'M' || dos
.Magic
[1] != 'Z')
435 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "DOS executable magic missing, refusing.");
437 start
= unaligned_read_le32(&dos
.ExeHeader
);
438 n
= pread(fd
, &pe
, sizeof(pe
), start
);
440 return log_error_errno(errno
, "Failed to read PE header: %m");
442 return log_error_errno(SYNTHETIC_ERRNO(EIO
), "Short read while reading PE header, refusing.");
444 if (pe
.Magic
[0] != 'P' || pe
.Magic
[1] != 'E' || pe
.Magic
[2] != 0 || pe
.Magic
[3] != 0)
445 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "PE executable magic missing, refusing.");
447 n_sections
= unaligned_read_le16(&pe
.FileHeader
.NumberOfSections
);
449 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "PE header has too many sections, refusing.");
451 sections
= new(struct PeSectionHeader
, n_sections
);
455 n
= pread(fd
, sections
,
456 n_sections
* sizeof(struct PeSectionHeader
),
457 start
+ sizeof(pe
) + unaligned_read_le16(&pe
.FileHeader
.SizeOfOptionalHeader
));
459 return log_error_errno(errno
, "Failed to read section data: %m");
460 if ((size_t) n
!= n_sections
* sizeof(struct PeSectionHeader
))
461 return log_error_errno(SYNTHETIC_ERRNO(EIO
), "Short read while reading sections, refusing.");
463 for (i
= 0; i
< n_sections
; i
++) {
464 _cleanup_free_
char *k
= NULL
;
465 uint32_t offset
, size
;
468 if (strneq((char*) sections
[i
].Name
, ".osrel", sizeof(sections
[i
].Name
)))
470 else if (strneq((char*) sections
[i
].Name
, ".cmdline", sizeof(sections
[i
].Name
)))
476 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Duplicate section %s, refusing.", sections
[i
].Name
);
478 offset
= unaligned_read_le32(§ions
[i
].PointerToRawData
);
479 size
= unaligned_read_le32(§ions
[i
].VirtualSize
);
481 if (size
> PE_SECTION_SIZE_MAX
)
482 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Section %s too large, refusing.", sections
[i
].Name
);
484 k
= new(char, size
+1);
488 n
= pread(fd
, k
, size
, offset
);
490 return log_error_errno(errno
, "Failed to read section payload: %m");
491 if ((size_t) n
!= size
)
492 return log_error_errno(SYNTHETIC_ERRNO(EIO
), "Short read while reading section payload, refusing:");
494 /* Allow one trailing NUL byte, but nothing more. */
495 if (size
> 0 && memchr(k
, 0, size
- 1))
496 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Section contains embedded NUL byte: %m");
503 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG
), "Image lacks .osrel section, refusing.");
506 *ret_osrelease
= TAKE_PTR(osrelease
);
508 *ret_cmdline
= TAKE_PTR(cmdline
);
513 static int boot_entries_find_unified(
519 _cleanup_(closedirp
) DIR *d
= NULL
;
532 return log_error_errno(errno
, "Failed to open %s: %m", dir
);
535 FOREACH_DIRENT(de
, d
, return log_error_errno(errno
, "Failed to read %s: %m", dir
)) {
536 _cleanup_free_
char *j
= NULL
, *osrelease
= NULL
, *cmdline
= NULL
;
537 _cleanup_close_
int fd
= -1;
539 if (!dirent_is_file(de
))
542 if (!endswith_no_case(de
->d_name
, ".efi"))
545 if (!GREEDY_REALLOC0(*entries
, *n_entries
+ 1))
548 fd
= openat(dirfd(d
), de
->d_name
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
);
550 log_warning_errno(errno
, "Failed to open %s/%s, ignoring: %m", dir
, de
->d_name
);
554 r
= fd_verify_regular(fd
);
556 log_warning_errno(r
, "File %s/%s is not regular, ignoring: %m", dir
, de
->d_name
);
560 r
= find_sections(fd
, &osrelease
, &cmdline
);
564 j
= path_join(dir
, de
->d_name
);
568 r
= boot_entry_load_unified(root
, j
, osrelease
, cmdline
, *entries
+ *n_entries
);
578 static bool find_nonunique(const BootEntry
*entries
, size_t n_entries
, bool arr
[]) {
579 bool non_unique
= false;
581 assert(entries
|| n_entries
== 0);
582 assert(arr
|| n_entries
== 0);
584 for (size_t i
= 0; i
< n_entries
; i
++)
587 for (size_t i
= 0; i
< n_entries
; i
++)
588 for (size_t j
= 0; j
< n_entries
; j
++)
589 if (i
!= j
&& streq(boot_entry_title(entries
+ i
),
590 boot_entry_title(entries
+ j
)))
591 non_unique
= arr
[i
] = arr
[j
] = true;
596 static int boot_entries_uniquify(BootEntry
*entries
, size_t n_entries
) {
597 _cleanup_free_
bool *arr
= NULL
;
600 assert(entries
|| n_entries
== 0);
605 arr
= new(bool, n_entries
);
609 /* Find _all_ non-unique titles */
610 if (!find_nonunique(entries
, n_entries
, arr
))
613 /* Add version to non-unique titles */
614 for (size_t i
= 0; i
< n_entries
; i
++)
615 if (arr
[i
] && entries
[i
].version
) {
616 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].version
) < 0)
619 free_and_replace(entries
[i
].show_title
, s
);
622 if (!find_nonunique(entries
, n_entries
, arr
))
625 /* Add machine-id to non-unique titles */
626 for (size_t i
= 0; i
< n_entries
; i
++)
627 if (arr
[i
] && entries
[i
].machine_id
) {
628 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].machine_id
) < 0)
631 free_and_replace(entries
[i
].show_title
, s
);
634 if (!find_nonunique(entries
, n_entries
, arr
))
637 /* Add file name to non-unique titles */
638 for (size_t i
= 0; i
< n_entries
; i
++)
640 if (asprintf(&s
, "%s (%s)", boot_entry_title(entries
+ i
), entries
[i
].id
) < 0)
643 free_and_replace(entries
[i
].show_title
, s
);
649 static int boot_config_find(const BootConfig
*config
, const char *id
) {
655 for (size_t i
= 0; i
< config
->n_entries
; i
++)
656 if (fnmatch(id
, config
->entries
[i
].id
, FNM_CASEFOLD
) == 0)
662 static int boot_entries_select_default(const BootConfig
*config
) {
666 assert(config
->entries
|| config
->n_entries
== 0);
668 if (config
->n_entries
== 0) {
669 log_debug("Found no default boot entry :(");
670 return -1; /* -1 means "no default" */
673 if (config
->entry_oneshot
) {
674 i
= boot_config_find(config
, config
->entry_oneshot
);
676 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
677 config
->entries
[i
].id
);
682 if (config
->entry_default
) {
683 i
= boot_config_find(config
, config
->entry_default
);
685 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
686 config
->entries
[i
].id
);
691 if (config
->default_pattern
) {
692 i
= boot_config_find(config
, config
->default_pattern
);
694 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
695 config
->entries
[i
].id
, config
->default_pattern
);
700 log_debug("Found default: first entry \"%s\"", config
->entries
[0].id
);
704 static int boot_entries_select_selected(const BootConfig
*config
) {
706 assert(config
->entries
|| config
->n_entries
== 0);
708 if (!config
->entry_selected
|| config
->n_entries
== 0)
711 return boot_config_find(config
, config
->entry_selected
);
714 static int boot_load_efi_entry_pointers(BootConfig
*config
) {
722 /* Loads the three "pointers" to boot loader entries from their EFI variables */
724 r
= efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot
), &config
->entry_oneshot
);
725 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
)) {
726 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntryOneShot\": %m");
731 r
= efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryDefault
), &config
->entry_default
);
732 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
)) {
733 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntryDefault\": %m");
738 r
= efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntrySelected
), &config
->entry_selected
);
739 if (r
< 0 && !IN_SET(r
, -ENOENT
, -ENODATA
)) {
740 log_warning_errno(r
, "Failed to read EFI variable \"LoaderEntrySelected\": %m");
748 int boot_entries_load_config(
749 const char *esp_path
,
750 const char *xbootldr_path
,
751 BootConfig
*config
) {
759 p
= strjoina(esp_path
, "/loader/loader.conf");
760 r
= boot_loader_read_conf(p
, config
);
764 p
= strjoina(esp_path
, "/loader/entries");
765 r
= boot_entries_find(esp_path
, p
, &config
->entries
, &config
->n_entries
);
769 p
= strjoina(esp_path
, "/EFI/Linux/");
770 r
= boot_entries_find_unified(esp_path
, p
, &config
->entries
, &config
->n_entries
);
776 p
= strjoina(xbootldr_path
, "/loader/entries");
777 r
= boot_entries_find(xbootldr_path
, p
, &config
->entries
, &config
->n_entries
);
781 p
= strjoina(xbootldr_path
, "/EFI/Linux/");
782 r
= boot_entries_find_unified(xbootldr_path
, p
, &config
->entries
, &config
->n_entries
);
787 typesafe_qsort(config
->entries
, config
->n_entries
, boot_entry_compare
);
789 r
= boot_entries_uniquify(config
->entries
, config
->n_entries
);
791 return log_error_errno(r
, "Failed to uniquify boot entries: %m");
793 r
= boot_load_efi_entry_pointers(config
);
797 config
->default_entry
= boot_entries_select_default(config
);
798 config
->selected_entry
= boot_entries_select_selected(config
);
803 int boot_entries_load_config_auto(
804 const char *override_esp_path
,
805 const char *override_xbootldr_path
,
806 BootConfig
*config
) {
808 _cleanup_free_
char *esp_where
= NULL
, *xbootldr_where
= NULL
;
809 dev_t esp_devid
= 0, xbootldr_devid
= 0;
814 /* This function is similar to boot_entries_load_config(), however we automatically search for the
815 * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
816 * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
817 * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
818 * it). This allows other boot loaders to pass boot loader entry information to our tools if they
821 if (!override_esp_path
&& !override_xbootldr_path
) {
822 if (access("/run/boot-loader-entries/", F_OK
) >= 0)
823 return boot_entries_load_config("/run/boot-loader-entries/", NULL
, config
);
826 return log_error_errno(errno
,
827 "Failed to determine whether /run/boot-loader-entries/ exists: %m");
830 r
= find_esp_and_warn(override_esp_path
, /* unprivileged_mode= */ false, &esp_where
, NULL
, NULL
, NULL
, NULL
, &esp_devid
);
831 if (r
< 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
834 r
= find_xbootldr_and_warn(override_xbootldr_path
, /* unprivileged_mode= */ false, &xbootldr_where
, NULL
, &xbootldr_devid
);
835 if (r
< 0 && r
!= -ENOKEY
)
836 return r
; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
838 /* If both paths actually refer to the same inode, suppress the xbootldr path */
839 if (esp_where
&& xbootldr_where
&& devid_set_and_equal(esp_devid
, xbootldr_devid
))
840 xbootldr_where
= mfree(xbootldr_where
);
842 return boot_entries_load_config(esp_where
, xbootldr_where
, config
);
845 int boot_entries_augment_from_loader(
847 char **found_by_loader
,
850 static const char *const title_table
[] = {
851 /* Pretty names for a few well-known automatically discovered entries. */
853 "auto-windows", "Windows Boot Manager",
854 "auto-efi-shell", "EFI Shell",
855 "auto-efi-default", "EFI Default Loader",
856 "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
863 /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
864 * already included there. */
866 STRV_FOREACH(i
, found_by_loader
) {
868 _cleanup_free_
char *c
= NULL
, *t
= NULL
, *p
= NULL
;
871 existing
= boot_config_find_entry(config
, *i
);
873 existing
->reported_by_loader
= true;
877 if (only_auto
&& !startswith(*i
, "auto-"))
884 STRV_FOREACH_PAIR(a
, b
, (char**) title_table
)
892 p
= strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderEntries
)));
896 if (!GREEDY_REALLOC0(config
->entries
, config
->n_entries
+ 1))
899 config
->entries
[config
->n_entries
++] = (BootEntry
) {
900 .type
= startswith(*i
, "auto-") ? BOOT_ENTRY_LOADER_AUTO
: BOOT_ENTRY_LOADER
,
902 .title
= TAKE_PTR(t
),
904 .reported_by_loader
= true,