diff --git a/man/bootctl.xml b/man/bootctl.xml index ff49a8043c..8d5b1d3d2c 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -92,7 +92,10 @@ url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification, as well as any other entries discovered or automatically generated by a boot loader implementing the Boot Loader - Interface. + Interface. + + JSON output may be requested with . + @@ -315,6 +318,7 @@ + diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index cc79f51655..759b3ca86a 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -70,6 +70,7 @@ static enum { ARG_ENTRY_TOKEN_AUTO, } arg_entry_token_type = ARG_ENTRY_TOKEN_AUTO; static char *arg_entry_token = NULL; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); @@ -686,7 +687,7 @@ static int status_entries( const char *xbootldr_path, sd_id128_t xbootldr_partition_uuid) { - _cleanup_(boot_config_free) BootConfig config = {}; + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; sd_id128_t dollar_boot_partition_uuid; const char *dollar_boot_path; int r; @@ -708,7 +709,11 @@ static int status_entries( SD_ID128_FORMAT_VAL(dollar_boot_partition_uuid)); printf("\n\n"); - r = boot_entries_load_config(esp_path, xbootldr_path, &config); + r = boot_config_load(&config, esp_path, xbootldr_path); + if (r < 0) + return r; + + r = boot_config_select_special_entries(&config); if (r < 0) return r; @@ -718,7 +723,7 @@ static int status_entries( printf("Default Boot Loader Entry:\n"); r = boot_entry_show( - config.entries + config.default_entry, + boot_config_default_entry(&config), /* show_as_default= */ false, /* show_as_selected= */ false, /* show_discovered= */ false); @@ -1420,6 +1425,8 @@ static int help(int argc, char *argv[], void *userdata) { " Create $BOOT/ENTRY-TOKEN/ directory\n" " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" " Entry token to use for this installation\n" + " --json=pretty|short|off\n" + " Generate JSON output\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -1441,6 +1448,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_GRACEFUL, ARG_MAKE_ENTRY_DIRECTORY, ARG_ENTRY_TOKEN, + ARG_JSON, }; static const struct option options[] = { @@ -1458,6 +1466,7 @@ static int parse_argv(int argc, char *argv[]) { { "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, { "make-machine-id-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, /* Compatibility alias */ { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, + { "json", required_argument, NULL, ARG_JSON }, {} }; @@ -1552,6 +1561,13 @@ static int parse_argv(int argc, char *argv[]) { } break; + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + + break; + case '?': return -EINVAL; @@ -1776,7 +1792,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { } static int verb_list(int argc, char *argv[], void *userdata) { - _cleanup_(boot_config_free) BootConfig config = {}; + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; _cleanup_strv_free_ char **efi_entries = NULL; dev_t esp_devid = 0, xbootldr_devid = 0; int r; @@ -1800,7 +1816,7 @@ static int verb_list(int argc, char *argv[], void *userdata) { /* If XBOOTLDR and ESP actually refer to the same block device, suppress XBOOTLDR, since it would find the same entries twice */ bool same = arg_esp_path && arg_xbootldr_path && devid_set_and_equal(esp_devid, xbootldr_devid); - r = boot_entries_load_config(arg_esp_path, same ? NULL : arg_xbootldr_path, &config); + r = boot_config_load(&config, arg_esp_path, same ? NULL : arg_xbootldr_path); if (r < 0) return r; @@ -1810,9 +1826,50 @@ static int verb_list(int argc, char *argv[], void *userdata) { else if (r < 0) log_warning_errno(r, "Failed to determine entries reported by boot loader, ignoring: %m"); else - (void) boot_entries_augment_from_loader(&config, efi_entries, /* only_auto= */ false); + (void) boot_config_augment_from_loader(&config, efi_entries, /* only_auto= */ false); - if (config.n_entries == 0) + r = boot_config_select_special_entries(&config); + if (r < 0) + return r; + + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { + + pager_open(arg_pager_flags); + + for (size_t i = 0; i < config.n_entries; i++) { + _cleanup_free_ char *opts = NULL; + BootEntry *e = config.entries + i; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (!strv_isempty(e->options)) { + opts = strv_join(e->options, " "); + if (!opts) + return log_oom(); + } + + r = json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)), + JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)), + JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)), + JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)), + JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))), + JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)), + JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)), + JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)), + JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)), + JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)), + JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)), + JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)), + JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay)))); + if (r < 0) + return log_oom(); + + json_variant_dump(v, arg_json_format_flags, stdout, NULL); + } + + } else if (config.n_entries == 0) log_info("No boot loader entries found."); else { pager_open(arg_pager_flags); diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 7767096619..1212a1ac53 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -3002,19 +3002,19 @@ static int property_get_reboot_to_boot_loader_entry( } static int boot_loader_entry_exists(Manager *m, const char *id) { - _cleanup_(boot_config_free) BootConfig config = {}; + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; int r; assert(m); assert(id); - r = boot_entries_load_config_auto(NULL, NULL, &config); + r = boot_config_load_auto(&config, NULL, NULL); if (r < 0 && r != -ENOKEY) /* don't complain if no GPT is found, hence skip ENOKEY */ return r; r = manager_read_efi_boot_loader_entries(m); if (r >= 0) - (void) boot_entries_augment_from_loader(&config, m->efi_boot_loader_entries, /* auto_only= */ true); + (void) boot_config_augment_from_loader(&config, m->efi_boot_loader_entries, /* auto_only= */ true); return !!boot_config_find_entry(&config, id); } @@ -3157,7 +3157,7 @@ static int property_get_boot_loader_entries( void *userdata, sd_bus_error *error) { - _cleanup_(boot_config_free) BootConfig config = {}; + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; Manager *m = userdata; size_t i; int r; @@ -3166,13 +3166,13 @@ static int property_get_boot_loader_entries( assert(reply); assert(m); - r = boot_entries_load_config_auto(NULL, NULL, &config); + r = boot_config_load_auto(&config, NULL, NULL); if (r < 0 && r != -ENOKEY) /* don't complain if there's no GPT found */ return r; r = manager_read_efi_boot_loader_entries(m); if (r >= 0) - (void) boot_entries_augment_from_loader(&config, m->efi_boot_loader_entries, /* auto_only= */ true); + (void) boot_config_augment_from_loader(&config, m->efi_boot_loader_entries, /* auto_only= */ true); r = sd_bus_message_open_container(reply, 'a', "s"); if (r < 0) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 53e079bdd7..7016f3840e 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -13,6 +13,7 @@ #include "find-esp.h" #include "path-util.h" #include "pe-header.h" +#include "recurse-dir.h" #include "sort-util.h" #include "stat-util.h" #include "strv.h" @@ -39,40 +40,45 @@ static void boot_entry_free(BootEntry *entry) { strv_free(entry->device_tree_overlay); } -static int boot_entry_load( +static int boot_entry_load_type1( + FILE *f, const char *root, - const char *path, + const char *dir, + const char *id, BootEntry *entry) { _cleanup_(boot_entry_free) BootEntry tmp = { .type = BOOT_ENTRY_CONF, }; - _cleanup_fclose_ FILE *f = NULL; unsigned line = 1; char *c; int r; + assert(f); assert(root); - assert(path); + assert(dir); + assert(id); assert(entry); - r = path_extract_filename(path, &tmp.id); - if (r < 0) - return log_error_errno(r, "Failed to extract file name from path '%s': %m", path); + /* Loads a Type #1 boot menu entry from the specified FILE* object */ - c = endswith_no_case(tmp.id, ".conf"); + if (!efi_loader_entry_name_valid(id)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", id); + + c = endswith_no_case(id, ".conf"); if (!c) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", tmp.id); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", id); - if (!efi_loader_entry_name_valid(tmp.id)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id); + tmp.id = strdup(id); + if (!tmp.id) + return log_oom(); - tmp.id_old = strndup(tmp.id, c - tmp.id); + tmp.id_old = strndup(id, c - id); /* Without .conf suffix */ if (!tmp.id_old) return log_oom(); - tmp.path = strdup(path); + tmp.path = path_join(dir, id); if (!tmp.path) return log_oom(); @@ -80,10 +86,6 @@ static int boot_entry_load( if (!tmp.root) return log_oom(); - f = fopen(path, "re"); - if (!f) - return log_error_errno(errno, "Failed to open \"%s\": %m", path); - for (;;) { _cleanup_free_ char *buf = NULL, *field = NULL; const char *p; @@ -92,9 +94,9 @@ static int boot_entry_load( if (r == 0) break; if (r == -ENOBUFS) - return log_error_errno(r, "%s:%u: Line too long", path, line); + return log_error_errno(r, "%s:%u: Line too long", tmp.path, line); if (r < 0) - return log_error_errno(r, "%s:%u: Error while reading: %m", path, line); + return log_error_errno(r, "%s:%u: Error while reading: %m", tmp.path, line); line++; @@ -104,11 +106,11 @@ static int boot_entry_load( p = buf; r = extract_first_word(&p, &field, " \t", 0); if (r < 0) { - log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line); + log_error_errno(r, "Failed to parse config file %s line %u: %m", tmp.path, line); continue; } if (r == 0) { - log_warning("%s:%u: Bad syntax", path, line); + log_warning("%s:%u: Bad syntax", tmp.path, line); continue; } @@ -141,11 +143,11 @@ static int boot_entry_load( r = strv_extend_strv(&tmp.device_tree_overlay, l, false); } else { - log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field); + log_notice("%s:%u: Unknown line \"%s\", ignoring.", tmp.path, line, field); continue; } if (r < 0) - return log_error_errno(r, "%s:%u: Error while reading: %m", path, line); + return log_error_errno(r, "%s:%u: Error while reading: %m", tmp.path, line); } *entry = tmp; @@ -172,6 +174,8 @@ void boot_config_free(BootConfig *config) { for (size_t i = 0; i < config->n_entries; i++) boot_entry_free(config->entries + i); free(config->entries); + + set_free(config->inodes_seen); } static int boot_loader_read_conf(const char *path, BootConfig *config) { @@ -272,33 +276,113 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { return -strverscmp_improved(a->id, b->id); } -static int boot_entries_find( - const char *root, - const char *dir, - BootEntry **entries, - size_t *n_entries) { +static void inode_hash_func(const struct stat *q, struct siphash *state) { + siphash24_compress(&q->st_dev, sizeof(q->st_dev), state); + siphash24_compress(&q->st_ino, sizeof(q->st_ino), state); +} - _cleanup_strv_free_ char **files = NULL; +static int inode_compare_func(const struct stat *a, const struct stat *b) { int r; + r = CMP(a->st_dev, b->st_dev); + if (r != 0) + return r; + + return CMP(a->st_ino, b->st_ino); +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free); + +static int config_check_inode_relevant_and_unseen(BootConfig *config, int fd, const char *fname) { + _cleanup_free_ char *d = NULL; + struct stat st; + + assert(config); + assert(fd >= 0); + assert(fname); + + /* So, here's the thing: because of the mess around /efi/ vs. /boot/ vs. /boot/efi/ it might be that + * people have these dirs, or subdirs of them symlinked or bind mounted, and we might end up + * iterating though some dirs multiple times. Let's thus rather be safe than sorry, and track the + * inodes we already processed: let's ignore inodes we have seen already. This should be robust + * against any form of symlinking or bind mounting, and effectively suppress any such duplicates. */ + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat('%s'): %m", fname); + if (!S_ISREG(st.st_mode)) { + log_debug("File '%s' is not a reguar file, ignoring.", fname); + return false; + } + + if (set_contains(config->inodes_seen, &st)) { + log_debug("Inode '%s' already seen before, ignoring.", fname); + return false; + } + d = memdup(&st, sizeof(st)); + if (!d) + return log_oom(); + if (set_ensure_put(&config->inodes_seen, &inode_hash_ops, d) < 0) + return log_oom(); + + TAKE_PTR(d); + return true; +} + +static int boot_entries_find_type1( + BootConfig *config, + const char *root, + const char *dir) { + + _cleanup_free_ DirectoryEntries *dentries = NULL; + _cleanup_close_ int dir_fd = -1; + int r; + + assert(config); assert(root); assert(dir); - assert(entries); - assert(n_entries); - r = conf_files_list(&files, ".conf", NULL, 0, dir); + dir_fd = open(dir, O_DIRECTORY|O_CLOEXEC); + if (dir_fd < 0) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open '%s': %m", dir); + } + + r = readdir_all(dir_fd, RECURSE_DIR_IGNORE_DOT, &dentries); if (r < 0) - return log_error_errno(r, "Failed to list files in \"%s\": %m", dir); + return log_error_errno(r, "Failed to read directory '%s': %m", dir); - STRV_FOREACH(f, files) { - if (!GREEDY_REALLOC0(*entries, *n_entries + 1)) + for (size_t i = 0; i < dentries->n_entries; i++) { + const struct dirent *de = dentries->entries[i]; + _cleanup_fclose_ FILE *f = NULL; + + if (!dirent_is_file(de)) + continue; + + if (!endswith_no_case(de->d_name, ".conf")) + continue; + + r = xfopenat(dir_fd, de->d_name, "re", 0, &f); + if (r < 0) { + log_warning_errno(r, "Failed to open %s/%s, ignoring: %m", dir, de->d_name); + continue; + } + + r = config_check_inode_relevant_and_unseen(config, fileno(f), de->d_name); + if (r < 0) + return r; + if (r == 0) /* inode already seen or otherwise not relevant */ + continue; + + if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1)) return log_oom(); - r = boot_entry_load(root, *f, *entries + *n_entries); + r = boot_entry_load_type1(f, root, dir, de->d_name, config->entries + config->n_entries); if (r < 0) continue; - (*n_entries) ++; + config->n_entries++; } return 0; @@ -508,18 +592,15 @@ static int find_sections( } static int boot_entries_find_unified( + BootConfig *config, const char *root, - const char *dir, - BootEntry **entries, - size_t *n_entries) { + const char *dir) { _cleanup_(closedirp) DIR *d = NULL; int r; - assert(root); + assert(config); assert(dir); - assert(entries); - assert(n_entries); d = opendir(dir); if (!d) { @@ -539,7 +620,7 @@ static int boot_entries_find_unified( if (!endswith_no_case(de->d_name, ".efi")) continue; - if (!GREEDY_REALLOC0(*entries, *n_entries + 1)) + if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1)) return log_oom(); fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK); @@ -548,25 +629,24 @@ static int boot_entries_find_unified( continue; } - r = fd_verify_regular(fd); - if (r < 0) { - log_warning_errno(r, "File %s/%s is not regular, ignoring: %m", dir, de->d_name); - continue; - } - - r = find_sections(fd, &osrelease, &cmdline); + r = config_check_inode_relevant_and_unseen(config, fd, de->d_name); if (r < 0) + return r; + if (r == 0) /* inode already seen or otherwise not relevant */ + continue; + + if (find_sections(fd, &osrelease, &cmdline) < 0) continue; j = path_join(dir, de->d_name); if (!j) return log_oom(); - r = boot_entry_load_unified(root, j, osrelease, cmdline, *entries + *n_entries); + r = boot_entry_load_unified(root, j, osrelease, cmdline, config->entries + config->n_entries); if (r < 0) continue; - (*n_entries) ++; + config->n_entries++; } return 0; @@ -719,33 +799,45 @@ static int boot_load_efi_entry_pointers(BootConfig *config) { /* Loads the three "pointers" to boot loader entries from their EFI variables */ r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot), &config->entry_oneshot); - if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) { - log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\": %m"); - if (r == -ENOMEM) - return r; - } + if (r == -ENOMEM) + return log_oom(); + if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) + log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m"); r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryDefault), &config->entry_default); - if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) { - log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\": %m"); - if (r == -ENOMEM) - return r; - } + if (r == -ENOMEM) + return log_oom(); + if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) + log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m"); r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntrySelected), &config->entry_selected); - if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) { - log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\": %m"); - if (r == -ENOMEM) - return r; - } + if (r == -ENOMEM) + return log_oom(); + if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) + log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m"); return 1; } -int boot_entries_load_config( +int boot_config_select_special_entries(BootConfig *config) { + int r; + + assert(config); + + r = boot_load_efi_entry_pointers(config); + if (r < 0) + return r; + + config->default_entry = boot_entries_select_default(config); + config->selected_entry = boot_entries_select_selected(config); + + return 0; +} + +int boot_config_load( + BootConfig *config, const char *esp_path, - const char *xbootldr_path, - BootConfig *config) { + const char *xbootldr_path) { const char *p; int r; @@ -759,24 +851,24 @@ int boot_entries_load_config( return r; p = strjoina(esp_path, "/loader/entries"); - r = boot_entries_find(esp_path, p, &config->entries, &config->n_entries); + r = boot_entries_find_type1(config, esp_path, p); if (r < 0) return r; p = strjoina(esp_path, "/EFI/Linux/"); - r = boot_entries_find_unified(esp_path, p, &config->entries, &config->n_entries); + r = boot_entries_find_unified(config, esp_path, p); if (r < 0) return r; } if (xbootldr_path) { p = strjoina(xbootldr_path, "/loader/entries"); - r = boot_entries_find(xbootldr_path, p, &config->entries, &config->n_entries); + r = boot_entries_find_type1(config, xbootldr_path, p); if (r < 0) return r; p = strjoina(xbootldr_path, "/EFI/Linux/"); - r = boot_entries_find_unified(xbootldr_path, p, &config->entries, &config->n_entries); + r = boot_entries_find_unified(config, xbootldr_path, p); if (r < 0) return r; } @@ -787,20 +879,13 @@ int boot_entries_load_config( if (r < 0) return log_error_errno(r, "Failed to uniquify boot entries: %m"); - r = boot_load_efi_entry_pointers(config); - if (r < 0) - return r; - - config->default_entry = boot_entries_select_default(config); - config->selected_entry = boot_entries_select_selected(config); - return 0; } -int boot_entries_load_config_auto( +int boot_config_load_auto( + BootConfig *config, const char *override_esp_path, - const char *override_xbootldr_path, - BootConfig *config) { + const char *override_xbootldr_path) { _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL; dev_t esp_devid = 0, xbootldr_devid = 0; @@ -817,7 +902,7 @@ int boot_entries_load_config_auto( if (!override_esp_path && !override_xbootldr_path) { if (access("/run/boot-loader-entries/", F_OK) >= 0) - return boot_entries_load_config("/run/boot-loader-entries/", NULL, config); + return boot_config_load(config, "/run/boot-loader-entries/", NULL); if (errno != ENOENT) return log_error_errno(errno, @@ -836,10 +921,10 @@ int boot_entries_load_config_auto( if (esp_where && xbootldr_where && devid_set_and_equal(esp_devid, xbootldr_devid)) xbootldr_where = mfree(xbootldr_where); - return boot_entries_load_config(esp_where, xbootldr_where, config); + return boot_config_load(config, esp_where, xbootldr_where); } -int boot_entries_augment_from_loader( +int boot_config_augment_from_loader( BootConfig *config, char **found_by_loader, bool only_auto) { @@ -901,3 +986,15 @@ int boot_entries_augment_from_loader( return 0; } + +BootEntry* boot_config_find_entry(BootConfig *config, const char *id) { + assert(config); + assert(id); + + for (size_t j = 0; j < config->n_entries; j++) + if (streq_ptr(config->entries[j].id, id) || + streq_ptr(config->entries[j].id_old, id)) + return config->entries + j; + + return NULL; +} diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index 0f5e6c9ad5..99bc5f69a4 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -7,6 +7,7 @@ #include #include +#include "set.h" #include "string-util.h" typedef enum BootEntryType { @@ -55,21 +56,20 @@ typedef struct BootConfig { BootEntry *entries; size_t n_entries; + ssize_t default_entry; ssize_t selected_entry; + + Set *inodes_seen; } BootConfig; -static inline BootEntry* boot_config_find_entry(BootConfig *config, const char *id) { - assert(config); - assert(id); +#define BOOT_CONFIG_NULL \ + { \ + .default_entry = -1, \ + .selected_entry = -1, \ + } - for (size_t j = 0; j < config->n_entries; j++) - if (streq_ptr(config->entries[j].id, id) || - streq_ptr(config->entries[j].id_old, id)) - return config->entries + j; - - return NULL; -} +BootEntry* boot_config_find_entry(BootConfig *config, const char *id); static inline BootEntry* boot_config_default_entry(BootConfig *config) { assert(config); @@ -77,13 +77,17 @@ static inline BootEntry* boot_config_default_entry(BootConfig *config) { if (config->default_entry < 0) return NULL; + assert((size_t) config->default_entry < config->n_entries); return config->entries + config->default_entry; } void boot_config_free(BootConfig *config); -int boot_entries_load_config(const char *esp_path, const char *xbootldr_path, BootConfig *config); -int boot_entries_load_config_auto(const char *override_esp_path, const char *override_xbootldr_path, BootConfig *config); -int boot_entries_augment_from_loader(BootConfig *config, char **list, bool only_auto); + +int boot_config_load(BootConfig *config, const char *esp_path, const char *xbootldr_path); +int boot_config_load_auto(BootConfig *config, const char *override_esp_path, const char *override_xbootldr_path); +int boot_config_augment_from_loader(BootConfig *config, char **list, bool only_auto); + +int boot_config_select_special_entries(BootConfig *config); static inline const char* boot_entry_title(const BootEntry *entry) { assert(entry); diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index da2336d3a2..7a6cef3c5e 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -18,7 +18,7 @@ #include "systemctl.h" static int load_kexec_kernel(void) { - _cleanup_(boot_config_free) BootConfig config = {}; + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; _cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL; const BootEntry *e; pid_t pid; @@ -32,7 +32,7 @@ static int load_kexec_kernel(void) { if (access(KEXEC, X_OK) < 0) return log_error_errno(errno, KEXEC" is not available: %m"); - r = boot_entries_load_config_auto(NULL, NULL, &config); + r = boot_config_load_auto(&config, NULL, NULL); if (r == -ENOKEY) /* The call doesn't log about ENOKEY, let's do so here. */ return log_error_errno(r, @@ -43,6 +43,10 @@ static int load_kexec_kernel(void) { if (r < 0) return r; + r = boot_config_select_special_entries(&config); + if (r < 0) + return r; + e = boot_config_default_entry(&config); if (!e) return log_error_errno(SYNTHETIC_ERRNO(ENOENT), diff --git a/src/test/test-bootspec.c b/src/test/test-bootspec.c index 7ba44744ba..46acd60e0d 100644 --- a/src/test/test-bootspec.c +++ b/src/test/test-bootspec.c @@ -61,7 +61,7 @@ TEST_RET(bootspec_sort) { }; _cleanup_(rm_rf_physical_and_freep) char *d = NULL; - _cleanup_(boot_config_free) BootConfig config = {}; + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; assert_se(mkdtemp_malloc("/tmp/bootspec-testXXXXXX", &d) >= 0); @@ -74,7 +74,7 @@ TEST_RET(bootspec_sort) { assert_se(write_string_file(j, entries[i].contents, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); } - assert_se(boot_entries_load_config(d, NULL, &config) >= 0); + assert_se(boot_config_load(&config, d, NULL) >= 0); assert_se(config.n_entries == 6);