From: Lennart Poettering Date: Mon, 9 Feb 2026 13:20:27 +0000 (+0100) Subject: bootctl: decouple "list", "unlink", "cleanup" X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8b736d94cdf5f2f1281f7f58c77b9676de3add1e;p=thirdparty%2Fsystemd.git bootctl: decouple "list", "unlink", "cleanup" These operations to quite different things, they just share 2 common funcs. Let's split them out into separate files. This also splits up verb_list() into separate calls for the three operations. This actually fixes issues, as for status/list we want "unpriv" ESP discovery logic, but for the other two we really should have privileged discovery logic. This is preparation for adding "bootctl link" later, but this makes sense either way, I am sure. --- diff --git a/src/bootctl/bootctl-cleanup.c b/src/bootctl/bootctl-cleanup.c new file mode 100644 index 00000000000..8cc51dd597e --- /dev/null +++ b/src/bootctl/bootctl-cleanup.c @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "bootctl.h" +#include "bootctl-cleanup.h" +#include "bootctl-unlink.h" +#include "bootctl-util.h" +#include "bootspec.h" +#include "bootspec-util.h" +#include "chase.h" +#include "errno-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "log.h" +#include "path-util.h" +#include "recurse-dir.h" + +static int list_remove_orphaned_file( + RecurseDirEvent event, + const char *path, + int dir_fd, + int inode_fd, + const struct dirent *de, + const struct statx *sx, + void *userdata) { + + Hashmap *known_files = userdata; + + assert(path); + + if (event != RECURSE_DIR_ENTRY) + return RECURSE_DIR_CONTINUE; + + if (hashmap_get(known_files, path)) + return RECURSE_DIR_CONTINUE; /* keep! */ + + if (arg_dry_run) + log_info("Would remove %s", path); + else if (unlinkat(dir_fd, de->d_name, 0) < 0) + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, + "Failed to remove \"%s\", ignoring: %m", path); + else + log_info("Removed %s", path); + + return RECURSE_DIR_CONTINUE; +} + +static int cleanup_orphaned_files( + const BootConfig *config, + const char *root) { + + _cleanup_hashmap_free_ Hashmap *known_files = NULL; + _cleanup_free_ char *full = NULL, *p = NULL; + _cleanup_close_ int dir_fd = -EBADF; + int r; + + assert(config); + assert(root); + + log_info("Cleaning %s", root); + + r = settle_entry_token(); + if (r < 0) + return r; + + r = boot_config_count_known_files(config, root, &known_files); + if (r < 0) + return log_error_errno(r, "Failed to count files in %s: %m", root); + + dir_fd = chase_and_open(arg_entry_token, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, + O_DIRECTORY|O_CLOEXEC, &full); + if (dir_fd == -ENOENT) + return 0; + if (dir_fd < 0) + return log_error_errno(dir_fd, "Failed to open '%s/%s': %m", root, skip_leading_slash(arg_entry_token)); + + p = path_join("/", arg_entry_token); + if (!p) + return log_oom(); + + r = recurse_dir(dir_fd, p, 0, UINT_MAX, RECURSE_DIR_SORT, list_remove_orphaned_file, known_files); + if (r < 0) + return log_error_errno(r, "Failed to cleanup %s: %m", full); + + return r; +} + +int verb_cleanup(int argc, char *argv[], void *userdata) { + dev_t esp_devid = 0, xbootldr_devid = 0; + int r; + + r = acquire_esp(/* unprivileged_mode= */ false, + /* graceful= */ false, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + &esp_devid); + if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ + return log_error_errno(r, "Failed to determine ESP location: %m"); + if (r < 0) + return r; + + r = acquire_xbootldr( + /* unprivileged_mode= */ false, + /* ret_uuid= */ NULL, + &xbootldr_devid); + if (r == -EACCES) + return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); + if (r < 0) + return r; + + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); + if (r < 0) + return r; + + r = 0; + RET_GATHER(r, cleanup_orphaned_files(&config, arg_esp_path)); + + if (arg_xbootldr_path && xbootldr_devid != esp_devid) + RET_GATHER(r, cleanup_orphaned_files(&config, arg_xbootldr_path)); + + return r; +} diff --git a/src/bootctl/bootctl-cleanup.h b/src/bootctl/bootctl-cleanup.h new file mode 100644 index 00000000000..ffe930b9007 --- /dev/null +++ b/src/bootctl/bootctl-cleanup.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int verb_cleanup(int argc, char *argv[], void *userdata); diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 460c332ac73..044d581dcd0 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-varlink.h" @@ -10,56 +9,20 @@ #include "bootctl-status.h" #include "bootctl-util.h" #include "bootspec.h" +#include "bootspec-util.h" #include "chase.h" -#include "devnum-util.h" #include "dirent-util.h" #include "efi-api.h" #include "efi-loader.h" #include "efivars.h" #include "errno-util.h" #include "fd-util.h" -#include "hashmap.h" #include "log.h" #include "pager.h" -#include "path-util.h" #include "pretty-print.h" -#include "recurse-dir.h" #include "string-util.h" -#include "strv.h" #include "tpm2-util.h" -static int boot_config_load_and_select( - BootConfig *config, - const char *esp_path, - dev_t esp_devid, - const char *xbootldr_path, - dev_t xbootldr_devid) { - - int r; - - /* If XBOOTLDR and ESP actually refer to the same block device, suppress XBOOTLDR, since it would - * find the same entries twice. */ - bool same = esp_path && xbootldr_path && devnum_set_and_equal(esp_devid, xbootldr_devid); - - r = boot_config_load(config, esp_path, same ? NULL : xbootldr_path); - if (r < 0) - return r; - - if (!arg_root) { - _cleanup_strv_free_ char **efi_entries = NULL; - - r = efi_loader_get_entries(&efi_entries); - if (r == -ENOENT || ERRNO_IS_NEG_NOT_SUPPORTED(r)) - log_debug_errno(r, "Boot loader reported no entries."); - else if (r < 0) - log_warning_errno(r, "Failed to determine entries reported by boot loader, ignoring: %m"); - else - (void) boot_config_augment_from_loader(config, efi_entries, /* auto_only= */ false); - } - - return boot_config_select_special_entries(config, /* skip_efivars= */ !!arg_root); -} - static int status_entries( const BootConfig *config, const char *esp_path, @@ -637,256 +600,6 @@ int verb_status(int argc, char *argv[], void *userdata) { return r; } -static int ref_file(Hashmap **known_files, const char *fn, int increment) { - char *k = NULL; - int n, r; - - assert(known_files); - - /* just gracefully ignore this. This way the caller doesn't have to verify whether the bootloader - * entry is relevant. */ - if (!fn) - return 0; - - n = PTR_TO_INT(hashmap_get2(*known_files, fn, (void**)&k)); - n += increment; - - assert(n >= 0); - - if (n == 0) { - (void) hashmap_remove(*known_files, fn); - free(k); - } else if (!k) { - _cleanup_free_ char *t = NULL; - - t = strdup(fn); - if (!t) - return -ENOMEM; - r = hashmap_ensure_put(known_files, &path_hash_ops_free, t, INT_TO_PTR(n)); - if (r < 0) - return r; - TAKE_PTR(t); - } else { - r = hashmap_update(*known_files, fn, INT_TO_PTR(n)); - if (r < 0) - return r; - } - - return n; -} - -static void deref_unlink_file(Hashmap **known_files, const char *fn, const char *root) { - _cleanup_free_ char *path = NULL; - int r; - - assert(known_files); - - /* just gracefully ignore this. This way the caller doesn't - have to verify whether the bootloader entry is relevant */ - if (!fn || !root) - return; - - r = ref_file(known_files, fn, -1); - if (r < 0) - return (void) log_warning_errno(r, "Failed to deref \"%s\", ignoring: %m", fn); - if (r > 0) - return; - - if (arg_dry_run) { - r = chase_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, F_OK, &path); - if (r < 0) - log_info_errno(r, "Unable to determine whether \"%s\" exists, ignoring: %m", fn); - else - log_info("Would remove \"%s\"", path); - return; - } - - r = chase_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, 0, &path); - if (r >= 0) - log_info("Removed \"%s\"", path); - else if (r != -ENOENT) - return (void) log_warning_errno(r, "Failed to remove \"%s\", ignoring: %m", fn); - - _cleanup_free_ char *d = NULL; - if (path_extract_directory(fn, &d) >= 0 && !path_equal(d, "/")) { - r = chase_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, AT_REMOVEDIR, NULL); - if (r < 0 && !IN_SET(r, -ENOTEMPTY, -ENOENT)) - log_warning_errno(r, "Failed to remove directory \"%s\", ignoring: %m", d); - } -} - -static int count_known_files(const BootConfig *config, const char* root, Hashmap **ret_known_files) { - _cleanup_hashmap_free_ Hashmap *known_files = NULL; - int r; - - assert(config); - assert(ret_known_files); - - for (size_t i = 0; i < config->n_entries; i++) { - const BootEntry *e = config->entries + i; - - if (!path_equal(e->root, root)) - continue; - - r = ref_file(&known_files, e->kernel, +1); - if (r < 0) - return r; - r = ref_file(&known_files, e->efi, +1); - if (r < 0) - return r; - r = ref_file(&known_files, e->uki, +1); - if (r < 0) - return r; - STRV_FOREACH(s, e->initrd) { - r = ref_file(&known_files, *s, +1); - if (r < 0) - return r; - } - r = ref_file(&known_files, e->device_tree, +1); - if (r < 0) - return r; - STRV_FOREACH(s, e->device_tree_overlay) { - r = ref_file(&known_files, *s, +1); - if (r < 0) - return r; - } - } - - *ret_known_files = TAKE_PTR(known_files); - - return 0; -} - -static int boot_config_find_in(const BootConfig *config, const char *root, const char *id) { - assert(config); - - if (!root || !id) - return -ENOENT; - - for (size_t i = 0; i < config->n_entries; i++) - if (path_equal(config->entries[i].root, root) && - fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0) - return i; - - return -ENOENT; -} - -static int unlink_entry(const BootConfig *config, const char *root, const char *id) { - _cleanup_hashmap_free_ Hashmap *known_files = NULL; - const BootEntry *e = NULL; - int r; - - assert(config); - - r = count_known_files(config, root, &known_files); - if (r < 0) - return log_error_errno(r, "Failed to count files in %s: %m", root); - - r = boot_config_find_in(config, root, id); - if (r < 0) - return 0; /* There is nothing to remove. */ - - if (r == config->default_entry) - log_warning("%s is the default boot entry", id); - if (r == config->selected_entry) - log_warning("%s is the selected boot entry", id); - - e = &config->entries[r]; - - deref_unlink_file(&known_files, e->kernel, e->root); - deref_unlink_file(&known_files, e->efi, e->root); - deref_unlink_file(&known_files, e->uki, e->root); - STRV_FOREACH(s, e->initrd) - deref_unlink_file(&known_files, *s, e->root); - deref_unlink_file(&known_files, e->device_tree, e->root); - STRV_FOREACH(s, e->device_tree_overlay) - deref_unlink_file(&known_files, *s, e->root); - - if (arg_dry_run) - log_info("Would remove \"%s\"", e->path); - else { - r = chase_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, 0, NULL); - if (r == -ENOENT) - return 0; /* Already removed? */ - if (r < 0) - return log_error_errno(r, "Failed to remove \"%s\": %m", e->path); - - log_info("Removed %s", e->path); - } - - return 0; -} - -static int list_remove_orphaned_file( - RecurseDirEvent event, - const char *path, - int dir_fd, - int inode_fd, - const struct dirent *de, - const struct statx *sx, - void *userdata) { - - Hashmap *known_files = userdata; - - assert(path); - - if (event != RECURSE_DIR_ENTRY) - return RECURSE_DIR_CONTINUE; - - if (hashmap_get(known_files, path)) - return RECURSE_DIR_CONTINUE; /* keep! */ - - if (arg_dry_run) - log_info("Would remove %s", path); - else if (unlinkat(dir_fd, de->d_name, 0) < 0) - log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, - "Failed to remove \"%s\", ignoring: %m", path); - else - log_info("Removed %s", path); - - return RECURSE_DIR_CONTINUE; -} - -static int cleanup_orphaned_files( - const BootConfig *config, - const char *root) { - - _cleanup_hashmap_free_ Hashmap *known_files = NULL; - _cleanup_free_ char *full = NULL, *p = NULL; - _cleanup_close_ int dir_fd = -EBADF; - int r; - - assert(config); - assert(root); - - log_info("Cleaning %s", root); - - r = settle_entry_token(); - if (r < 0) - return r; - - r = count_known_files(config, root, &known_files); - if (r < 0) - return log_error_errno(r, "Failed to count files in %s: %m", root); - - dir_fd = chase_and_open(arg_entry_token, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, - O_DIRECTORY|O_CLOEXEC, &full); - if (dir_fd == -ENOENT) - return 0; - if (dir_fd < 0) - return log_error_errno(dir_fd, "Failed to open '%s/%s': %m", root, skip_leading_slash(arg_entry_token)); - - p = path_join("/", arg_entry_token); - if (!p) - return log_oom(); - - r = recurse_dir(dir_fd, p, 0, UINT_MAX, RECURSE_DIR_SORT, list_remove_orphaned_file, known_files); - if (r < 0) - return log_error_errno(r, "Failed to cleanup %s: %m", full); - - return r; -} - int verb_list(int argc, char *argv[], void *userdata) { _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; dev_t esp_devid = 0, xbootldr_devid = 0; @@ -918,19 +631,8 @@ int verb_list(int argc, char *argv[], void *userdata) { return 0; } - if (streq(argv[0], "list")) { - pager_open(arg_pager_flags); - return show_boot_entries(&config, arg_json_format_flags); - } else if (streq(argv[0], "cleanup")) { - if (arg_xbootldr_path && xbootldr_devid != esp_devid) - cleanup_orphaned_files(&config, arg_xbootldr_path); - return cleanup_orphaned_files(&config, arg_esp_path); - } else { - assert(streq(argv[0], "unlink")); - if (arg_xbootldr_path && xbootldr_devid != esp_devid) - r = unlink_entry(&config, arg_xbootldr_path, argv[1]); - return RET_GATHER(r, unlink_entry(&config, arg_esp_path, argv[1])); - } + pager_open(arg_pager_flags); + return show_boot_entries(&config, arg_json_format_flags); } int vl_method_list_boot_entries(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { diff --git a/src/bootctl/bootctl-unlink.c b/src/bootctl/bootctl-unlink.c new file mode 100644 index 00000000000..287b2b7904c --- /dev/null +++ b/src/bootctl/bootctl-unlink.c @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "bootctl.h" +#include "bootctl-unlink.h" +#include "bootspec.h" +#include "bootspec-util.h" +#include "chase.h" +#include "errno-util.h" +#include "hashmap.h" +#include "log.h" +#include "path-util.h" +#include "strv.h" + +static int ref_file(Hashmap **known_files, const char *fn, int increment) { + char *k = NULL; + int n, r; + + assert(known_files); + + /* just gracefully ignore this. This way the caller doesn't have to verify whether the bootloader + * entry is relevant. */ + if (!fn) + return 0; + + n = PTR_TO_INT(hashmap_get2(*known_files, fn, (void**)&k)); + n += increment; + + assert(n >= 0); + + if (n == 0) { + (void) hashmap_remove(*known_files, fn); + free(k); + } else if (!k) { + _cleanup_free_ char *t = NULL; + + t = strdup(fn); + if (!t) + return -ENOMEM; + r = hashmap_ensure_put(known_files, &path_hash_ops_free, t, INT_TO_PTR(n)); + if (r < 0) + return r; + TAKE_PTR(t); + } else { + r = hashmap_update(*known_files, fn, INT_TO_PTR(n)); + if (r < 0) + return r; + } + + return n; +} + +int boot_config_count_known_files( + const BootConfig *config, + const char* root, + Hashmap **ret_known_files) { + + _cleanup_hashmap_free_ Hashmap *known_files = NULL; + int r; + + assert(config); + assert(ret_known_files); + + for (size_t i = 0; i < config->n_entries; i++) { + const BootEntry *e = config->entries + i; + + if (!path_equal(e->root, root)) + continue; + + r = ref_file(&known_files, e->kernel, +1); + if (r < 0) + return r; + r = ref_file(&known_files, e->efi, +1); + if (r < 0) + return r; + r = ref_file(&known_files, e->uki, +1); + if (r < 0) + return r; + STRV_FOREACH(s, e->initrd) { + r = ref_file(&known_files, *s, +1); + if (r < 0) + return r; + } + r = ref_file(&known_files, e->device_tree, +1); + if (r < 0) + return r; + STRV_FOREACH(s, e->device_tree_overlay) { + r = ref_file(&known_files, *s, +1); + if (r < 0) + return r; + } + } + + *ret_known_files = TAKE_PTR(known_files); + + return 0; +} + +static void deref_unlink_file(Hashmap **known_files, const char *fn, const char *root) { + _cleanup_free_ char *path = NULL; + int r; + + assert(known_files); + + /* just gracefully ignore this. This way the caller doesn't + have to verify whether the bootloader entry is relevant */ + if (!fn || !root) + return; + + r = ref_file(known_files, fn, -1); + if (r < 0) + return (void) log_warning_errno(r, "Failed to deref \"%s\", ignoring: %m", fn); + if (r > 0) + return; + + if (arg_dry_run) { + r = chase_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, F_OK, &path); + if (r < 0) + log_info_errno(r, "Unable to determine whether \"%s\" exists, ignoring: %m", fn); + else + log_info("Would remove \"%s\"", path); + return; + } + + r = chase_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, 0, &path); + if (r >= 0) + log_info("Removed \"%s\"", path); + else if (r != -ENOENT) + return (void) log_warning_errno(r, "Failed to remove \"%s\", ignoring: %m", fn); + + _cleanup_free_ char *d = NULL; + if (path_extract_directory(fn, &d) >= 0 && !path_equal(d, "/")) { + r = chase_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, AT_REMOVEDIR, NULL); + if (r < 0 && !IN_SET(r, -ENOTEMPTY, -ENOENT)) + log_warning_errno(r, "Failed to remove directory \"%s\", ignoring: %m", d); + } +} + +static int boot_config_find_in(const BootConfig *config, const char *root, const char *id) { + assert(config); + + if (!root || !id) + return -ENOENT; + + for (size_t i = 0; i < config->n_entries; i++) + if (path_equal(config->entries[i].root, root) && + fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0) + return i; + + return -ENOENT; +} + +static int unlink_entry(const BootConfig *config, const char *root, const char *id) { + _cleanup_hashmap_free_ Hashmap *known_files = NULL; + const BootEntry *e = NULL; + int r; + + assert(config); + + r = boot_config_count_known_files(config, root, &known_files); + if (r < 0) + return log_error_errno(r, "Failed to count files in %s: %m", root); + + r = boot_config_find_in(config, root, id); + if (r < 0) + return 0; /* There is nothing to remove. */ + + if (r == config->default_entry) + log_warning("%s is the default boot entry", id); + if (r == config->selected_entry) + log_warning("%s is the selected boot entry", id); + + e = &config->entries[r]; + + deref_unlink_file(&known_files, e->kernel, e->root); + deref_unlink_file(&known_files, e->efi, e->root); + deref_unlink_file(&known_files, e->uki, e->root); + STRV_FOREACH(s, e->initrd) + deref_unlink_file(&known_files, *s, e->root); + deref_unlink_file(&known_files, e->device_tree, e->root); + STRV_FOREACH(s, e->device_tree_overlay) + deref_unlink_file(&known_files, *s, e->root); + + if (arg_dry_run) + log_info("Would remove \"%s\"", e->path); + else { + r = chase_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, 0, NULL); + if (r == -ENOENT) + return 0; /* Already removed? */ + if (r < 0) + return log_error_errno(r, "Failed to remove \"%s\": %m", e->path); + + log_info("Removed %s", e->path); + } + + return 0; +} + +int verb_unlink(int argc, char *argv[], void *userdata) { + dev_t esp_devid = 0, xbootldr_devid = 0; + int r; + + r = acquire_esp(/* unprivileged_mode= */ false, + /* graceful= */ false, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + &esp_devid); + if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ + return log_error_errno(r, "Failed to determine ESP location: %m"); + if (r < 0) + return r; + + r = acquire_xbootldr( + /* unprivileged_mode= */ false, + /* ret_uuid= */ NULL, + &xbootldr_devid); + if (r == -EACCES) + return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); + if (r < 0) + return r; + + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + r = boot_config_load_and_select( + &config, + arg_esp_path, + esp_devid, + arg_xbootldr_path, + xbootldr_devid); + if (r < 0) + return r; + + r = 0; + RET_GATHER(r, unlink_entry(&config, arg_esp_path, argv[1])); + + if (arg_xbootldr_path && xbootldr_devid != esp_devid) + RET_GATHER(r, unlink_entry(&config, arg_xbootldr_path, argv[1])); + + return r; +} diff --git a/src/bootctl/bootctl-unlink.h b/src/bootctl/bootctl-unlink.h new file mode 100644 index 00000000000..5737977ae5e --- /dev/null +++ b/src/bootctl/bootctl-unlink.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int verb_unlink(int argc, char *argv[], void *userdata); + +int boot_config_count_known_files(const BootConfig *config, const char* root, Hashmap **ret_known_files); diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 8d3cabc8650..e586c7f0dab 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -8,12 +8,14 @@ #include "blockdev-util.h" #include "boot-entry.h" #include "bootctl.h" +#include "bootctl-cleanup.h" #include "bootctl-install.h" #include "bootctl-random-seed.h" #include "bootctl-reboot-to-firmware.h" #include "bootctl-set-efivar.h" #include "bootctl-status.h" #include "bootctl-uki.h" +#include "bootctl-unlink.h" #include "build.h" #include "devnum-util.h" #include "dissect-image.h" @@ -697,8 +699,8 @@ static int bootctl_main(int argc, char *argv[]) { { "kernel-identify", 2, 2, 0, verb_kernel_identify }, { "kernel-inspect", 2, 2, 0, verb_kernel_inspect }, { "list", VERB_ANY, 1, 0, verb_list }, - { "unlink", 2, 2, 0, verb_list }, - { "cleanup", VERB_ANY, 1, 0, verb_list }, + { "unlink", 2, 2, 0, verb_unlink }, + { "cleanup", VERB_ANY, 1, 0, verb_cleanup }, { "set-default", 2, 2, 0, verb_set_efivar }, { "set-oneshot", 2, 2, 0, verb_set_efivar }, { "set-timeout", 2, 2, 0, verb_set_efivar }, diff --git a/src/bootctl/bootspec-util.c b/src/bootctl/bootspec-util.c new file mode 100644 index 00000000000..ec3339600bb --- /dev/null +++ b/src/bootctl/bootspec-util.c @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bootctl.h" +#include "bootspec-util.h" +#include "devnum-util.h" +#include "efi-loader.h" +#include "errno-util.h" +#include "log.h" +#include "strv.h" + +int boot_config_load_and_select( + BootConfig *config, + const char *esp_path, + dev_t esp_devid, + const char *xbootldr_path, + dev_t xbootldr_devid) { + + int r; + + /* If XBOOTLDR and ESP actually refer to the same block device, suppress XBOOTLDR, since it would + * find the same entries twice. */ + bool same = esp_path && xbootldr_path && devnum_set_and_equal(esp_devid, xbootldr_devid); + + r = boot_config_load(config, esp_path, same ? NULL : xbootldr_path); + if (r < 0) + return r; + + if (!arg_root) { + _cleanup_strv_free_ char **efi_entries = NULL; + + r = efi_loader_get_entries(&efi_entries); + if (r == -ENOENT || ERRNO_IS_NEG_NOT_SUPPORTED(r)) + log_debug_errno(r, "Boot loader reported no entries."); + else if (r < 0) + log_warning_errno(r, "Failed to determine entries reported by boot loader, ignoring: %m"); + else + (void) boot_config_augment_from_loader(config, efi_entries, /* auto_only= */ false); + } + + return boot_config_select_special_entries(config, /* skip_efivars= */ !!arg_root); +} diff --git a/src/bootctl/bootspec-util.h b/src/bootctl/bootspec-util.h new file mode 100644 index 00000000000..a00e002caaf --- /dev/null +++ b/src/bootctl/bootspec-util.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "bootspec.h" + +int boot_config_load_and_select(BootConfig *config, const char *esp_path, dev_t esp_devid, const char *xbootldr_path, dev_t xbootldr_devid); diff --git a/src/bootctl/meson.build b/src/bootctl/meson.build index 7522cd10a92..8cfbb7c14ac 100644 --- a/src/bootctl/meson.build +++ b/src/bootctl/meson.build @@ -1,14 +1,17 @@ # SPDX-License-Identifier: LGPL-2.1-or-later bootctl_sources = files( + 'bootctl.c', 'bootctl-install.c', 'bootctl-random-seed.c', 'bootctl-reboot-to-firmware.c', 'bootctl-set-efivar.c', 'bootctl-status.c', 'bootctl-uki.c', + 'bootctl-unlink.c', + 'bootctl-cleanup.c', 'bootctl-util.c', - 'bootctl.c', + 'bootspec-util.c', ) executables += [ diff --git a/src/shared/shared-forward.h b/src/shared/shared-forward.h index 3695d4e6714..4804dd9e49e 100644 --- a/src/shared/shared-forward.h +++ b/src/shared/shared-forward.h @@ -46,6 +46,7 @@ typedef enum UserStorage UserStorage; typedef struct Bitmap Bitmap; typedef struct BPFProgram BPFProgram; +typedef struct BootConfig BootConfig; typedef struct BusObjectImplementation BusObjectImplementation; typedef struct CalendarSpec CalendarSpec; typedef struct Condition Condition;