]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
bootctl: decouple "list", "unlink", "cleanup"
authorLennart Poettering <lennart@amutable.com>
Mon, 9 Feb 2026 13:20:27 +0000 (14:20 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 10 Feb 2026 19:43:04 +0000 (19:43 +0000)
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.

src/bootctl/bootctl-cleanup.c [new file with mode: 0644]
src/bootctl/bootctl-cleanup.h [new file with mode: 0644]
src/bootctl/bootctl-status.c
src/bootctl/bootctl-unlink.c [new file with mode: 0644]
src/bootctl/bootctl-unlink.h [new file with mode: 0644]
src/bootctl/bootctl.c
src/bootctl/bootspec-util.c [new file with mode: 0644]
src/bootctl/bootspec-util.h [new file with mode: 0644]
src/bootctl/meson.build
src/shared/shared-forward.h

diff --git a/src/bootctl/bootctl-cleanup.c b/src/bootctl/bootctl-cleanup.c
new file mode 100644 (file)
index 0000000..8cc51dd
--- /dev/null
@@ -0,0 +1,127 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+
+#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 (file)
index 0000000..ffe930b
--- /dev/null
@@ -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);
index 460c332ac7382b95064fa6246018599ea911adc9..044d581dcd08d030dbc231c70f5d1205b4c12b38 100644 (file)
@@ -1,6 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include <fnmatch.h>
 #include <unistd.h>
 
 #include "sd-varlink.h"
 #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 (file)
index 0000000..287b2b7
--- /dev/null
@@ -0,0 +1,244 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+#include <fnmatch.h>
+
+#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 (file)
index 0000000..5737977
--- /dev/null
@@ -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);
index 8d3cabc86501a98c6e842fef08f42e5e5fa42e86..e586c7f0dabc7c11f51da0b390d49f51b77e3127 100644 (file)
@@ -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 (file)
index 0000000..ec33396
--- /dev/null
@@ -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 (file)
index 0000000..a00e002
--- /dev/null
@@ -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);
index 7522cd10a92caa4cc481acb4a4f4f07fe6f6d655..8cfbb7c14acb092eeb95d5b1ca842424d2f08f62 100644 (file)
@@ -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 += [
index 3695d4e6714084e3f2df9aff96e03167b5426d46..4804dd9e49ed52323e5c0f303432f5e2b4605582 100644 (file)
@@ -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;