bootctl: rework/modernize "unlink" and add Varlink API for it
Among other things this changes tracking of the location of resources during GC from using the BootEntrySource enum rather than a path, since we have that and it is more efficient and easier to grok.
This commit is contained in:
+21
-4
@@ -102,11 +102,17 @@
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>unlink</option> <replaceable>ID</replaceable></term>
|
||||
<term><option>unlink</option> <optional><replaceable>ID</replaceable></optional></term>
|
||||
|
||||
<listitem><para>Removes a boot loader entry including the files it refers to. Takes a single boot
|
||||
loader entry ID string or a glob pattern as argument. Referenced files such as kernel or initrd are
|
||||
only removed if no other entry refers to them.</para>
|
||||
<listitem><para>Removes a boot loader entry including the files it refers to. Takes an optional boot
|
||||
loader entry ID string or a glob pattern as argument. Referenced files such as the kernel, initrds,
|
||||
system extensions (sysexts), configuration extensions (confexts) or credential files are only removed
|
||||
if no other entry refers to them.</para>
|
||||
|
||||
<para>If no <replaceable>ID</replaceable> argument is specified, the <option>--oldest</option> option
|
||||
must be specified, in which case the boot loader entry with the lowest version is removed (for
|
||||
robustness reasons the currently booted menu entry is never removed, nor is the last existing boot
|
||||
loader entry).</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
||||
</varlistentry>
|
||||
@@ -560,6 +566,17 @@
|
||||
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--oldest</option></term>
|
||||
|
||||
<listitem><para>When used with <command>unlink</command>, selects the oldest installed boot loader
|
||||
entry matching the boot entry token for removal (rather than passing an explicit entry ID). This is
|
||||
useful for pruning older installed boot loader entries. Note that the currently booted entry is never
|
||||
removed, nor is the last remaining one.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--secure-boot-auto-enroll=yes|no</option></term>
|
||||
<term><option>--private-key=<replaceable>PATH/URI</replaceable></option></term>
|
||||
|
||||
@@ -40,8 +40,10 @@ _bootctl() {
|
||||
--dry-run'
|
||||
[ARG]='--esp-path --boot-path --root --image --image-policy --install-source
|
||||
--variables --random-seed --make-entry-directory --entry-token --json
|
||||
--efi-boot-option-description --secure-boot-auto-enroll --private-key
|
||||
--private-key-source --certificate --certificate-source'
|
||||
--efi-boot-option-description --efi-boot-option-description-with-device
|
||||
--secure-boot-auto-enroll --private-key
|
||||
--private-key-source --certificate --certificate-source
|
||||
--oldest'
|
||||
)
|
||||
|
||||
if __contains_word "$prev" ${OPTS[ARG]}; then
|
||||
@@ -67,7 +69,7 @@ _bootctl() {
|
||||
--install-source)
|
||||
comps="image host auto"
|
||||
;;
|
||||
--random-seed|--variables|--secure-boot-auto-enroll)
|
||||
--random-seed|--variables|--secure-boot-auto-enroll|--oldest|--efi-boot-option-description-with-device)
|
||||
comps="yes no"
|
||||
;;
|
||||
--json)
|
||||
@@ -85,7 +87,7 @@ _bootctl() {
|
||||
|
||||
local -A VERBS=(
|
||||
[STANDALONE]='help status install update remove is-installed random-seed list set-timeout set-timeout-oneshot cleanup'
|
||||
[BOOTENTRY]='set-default set-oneshot set-sysfail unlink'
|
||||
[BOOTENTRY]='set-default set-oneshot set-sysfail set-preferred unlink'
|
||||
[BOOLEAN]='reboot-to-firmware'
|
||||
[FILE]='kernel-identify kernel-inspect'
|
||||
)
|
||||
|
||||
@@ -24,10 +24,26 @@ _bootctl_set-oneshot() {
|
||||
_bootctl_comp_ids
|
||||
}
|
||||
|
||||
_bootctl_set-sysfail() {
|
||||
_bootctl_comp_ids
|
||||
}
|
||||
|
||||
_bootctl_set-preferred() {
|
||||
_bootctl_comp_ids
|
||||
}
|
||||
|
||||
_bootctl_unlink() {
|
||||
_bootctl_comp_ids
|
||||
}
|
||||
|
||||
_bootctl_kernel-identify() {
|
||||
_files
|
||||
}
|
||||
|
||||
_bootctl_kernel-inspect() {
|
||||
_files
|
||||
}
|
||||
|
||||
_bootctl_reboot-to-firmware() {
|
||||
local -a _completions
|
||||
_completions=( yes no )
|
||||
@@ -49,10 +65,14 @@ _bootctl_reboot-to-firmware() {
|
||||
"list:List boot loader entries"
|
||||
"set-default:Set the default boot loader entry"
|
||||
"set-oneshot:Set the default boot loader entry only for the next boot"
|
||||
"set-sysfail:Set boot loader entry used in case of a system failure"
|
||||
"set-preferred:Set the preferred boot loader entry"
|
||||
"set-timeout:Set the menu timeout"
|
||||
"set-timeout-oneshot:Set the menu timeout for the next boot only"
|
||||
"unlink:Remove boot loader entry"
|
||||
"cleanup:Remove files in ESP not referenced in any boot entry"
|
||||
"kernel-identify:Identify kernel image type"
|
||||
"kernel-inspect:Print details about the kernel image"
|
||||
)
|
||||
if (( CURRENT == 1 )); then
|
||||
_describe -t commands 'bootctl command' _bootctl_cmds || compadd "$@"
|
||||
@@ -79,6 +99,7 @@ _arguments \
|
||||
'--no-pager[Do not pipe output into a pager]' \
|
||||
'--graceful[Do not fail when locating ESP or writing fails]' \
|
||||
'--dry-run[Dry run (unlink and cleanup)]' \
|
||||
'--oldest=[Delete oldest boot menu entry]:options:(yes no)' \
|
||||
'--root=[Operate under the specified directory]:PATH' \
|
||||
'--image=[Operate on the specified image]:PATH' \
|
||||
'--install-source[Where to pick files when using --root=/--image=]:options:(image host auto)' \
|
||||
|
||||
@@ -49,6 +49,7 @@ static int list_remove_orphaned_file(
|
||||
|
||||
static int cleanup_orphaned_files(
|
||||
const BootConfig *config,
|
||||
BootEntrySource source,
|
||||
const char *root) {
|
||||
|
||||
_cleanup_hashmap_free_ Hashmap *known_files = NULL;
|
||||
@@ -65,7 +66,7 @@ static int cleanup_orphaned_files(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = boot_config_count_known_files(config, root, &known_files);
|
||||
r = boot_config_count_known_files(config, source, &known_files);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to count files in %s: %m", root);
|
||||
|
||||
@@ -116,10 +117,10 @@ int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
return r;
|
||||
|
||||
r = 0;
|
||||
RET_GATHER(r, cleanup_orphaned_files(&config, arg_esp_path));
|
||||
RET_GATHER(r, cleanup_orphaned_files(&config, BOOT_ENTRY_ESP, arg_esp_path));
|
||||
|
||||
if (arg_xbootldr_path && xbootldr_devid != esp_devid)
|
||||
RET_GATHER(r, cleanup_orphaned_files(&config, arg_xbootldr_path));
|
||||
RET_GATHER(r, cleanup_orphaned_files(&config, BOOT_ENTRY_XBOOTLDR, arg_xbootldr_path));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
+517
-110
@@ -3,20 +3,70 @@
|
||||
#include <fcntl.h>
|
||||
#include <fnmatch.h>
|
||||
|
||||
#include "sd-id128.h"
|
||||
#include "sd-json.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "boot-entry.h"
|
||||
#include "bootctl.h"
|
||||
#include "bootctl-unlink.h"
|
||||
#include "bootspec.h"
|
||||
#include "bootspec-util.h"
|
||||
#include "chase.h"
|
||||
#include "efi-loader.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "find-esp.h"
|
||||
#include "hashmap.h"
|
||||
#include "id128-util.h"
|
||||
#include "json-util.h"
|
||||
#include "log.h"
|
||||
#include "path-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
typedef struct UnlinkContext {
|
||||
char *root;
|
||||
int root_fd;
|
||||
|
||||
sd_id128_t machine_id;
|
||||
BootEntryTokenType entry_token_type;
|
||||
char *entry_token;
|
||||
|
||||
char *esp_path;
|
||||
dev_t esp_devid;
|
||||
int esp_fd;
|
||||
|
||||
char *xbootldr_path;
|
||||
dev_t xbootldr_devid;
|
||||
int xbootldr_fd;
|
||||
} UnlinkContext;
|
||||
|
||||
#define UNLINK_CONTEXT_NULL \
|
||||
(UnlinkContext) { \
|
||||
.root_fd = -EBADF, \
|
||||
.entry_token_type = _BOOT_ENTRY_TOKEN_TYPE_INVALID, \
|
||||
.esp_fd = -EBADF, \
|
||||
.xbootldr_fd = -EBADF, \
|
||||
}
|
||||
|
||||
static void unlink_context_done(UnlinkContext *c) {
|
||||
assert(c);
|
||||
|
||||
c->root = mfree(c->root);
|
||||
c->root_fd = safe_close(c->root_fd);
|
||||
|
||||
c->entry_token = mfree(c->entry_token);
|
||||
|
||||
c->esp_path = mfree(c->esp_path);
|
||||
c->esp_fd = safe_close(c->esp_fd);
|
||||
c->xbootldr_path = mfree(c->xbootldr_path);
|
||||
c->xbootldr_fd = safe_close(c->xbootldr_fd);
|
||||
}
|
||||
|
||||
static int ref_file(Hashmap **known_files, const char *fn, int increment) {
|
||||
char *k = NULL;
|
||||
int n, r;
|
||||
|
||||
assert(known_files);
|
||||
@@ -26,13 +76,15 @@ static int ref_file(Hashmap **known_files, const char *fn, int increment) {
|
||||
if (!fn)
|
||||
return 0;
|
||||
|
||||
char *k = NULL;
|
||||
n = PTR_TO_INT(hashmap_get2(*known_files, fn, (void**)&k));
|
||||
n += increment;
|
||||
if (!INC_SAFE(&n, increment))
|
||||
return -EOVERFLOW;
|
||||
|
||||
assert(n >= 0);
|
||||
|
||||
if (n == 0) {
|
||||
(void) hashmap_remove(*known_files, fn);
|
||||
(void) hashmap_remove(*known_files, k);
|
||||
free(k);
|
||||
} else if (!k) {
|
||||
_cleanup_free_ char *t = NULL;
|
||||
@@ -40,12 +92,14 @@ static int ref_file(Hashmap **known_files, const char *fn, int increment) {
|
||||
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));
|
||||
r = hashmap_update(*known_files, k, INT_TO_PTR(n));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -53,195 +107,548 @@ static int ref_file(Hashmap **known_files, const char *fn, int increment) {
|
||||
return n;
|
||||
}
|
||||
|
||||
static int boot_entry_ref_files(
|
||||
const BootEntry *e,
|
||||
Hashmap **known_files,
|
||||
int increment) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(e);
|
||||
assert(known_files);
|
||||
assert(increment != 0);
|
||||
|
||||
r = ref_file(known_files, e->kernel, increment);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = ref_file(known_files, e->efi, increment);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = ref_file(known_files, e->uki, increment);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH(s, e->initrd) {
|
||||
r = ref_file(known_files, *s, increment);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = ref_file(known_files, e->device_tree, increment);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH(s, e->device_tree_overlay) {
|
||||
r = ref_file(known_files, *s, increment);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int boot_config_count_known_files(
|
||||
const BootConfig *config,
|
||||
const char* root,
|
||||
BootEntrySource source,
|
||||
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;
|
||||
_cleanup_hashmap_free_ Hashmap *known_files = NULL;
|
||||
FOREACH_ARRAY(e, config->entries, config->n_entries) {
|
||||
|
||||
if (!path_equal(e->root, root))
|
||||
if (e->source != source)
|
||||
continue;
|
||||
|
||||
r = ref_file(&known_files, e->kernel, +1);
|
||||
r = boot_entry_ref_files(e, &known_files, +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;
|
||||
static int unref_unlink_file(
|
||||
Hashmap **known_files,
|
||||
const char *root,
|
||||
int root_fd,
|
||||
const char *path,
|
||||
bool dry_run) {
|
||||
|
||||
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;
|
||||
if (root_fd < 0 || !root || !path)
|
||||
return 0;
|
||||
|
||||
r = ref_file(known_files, fn, -1);
|
||||
r = ref_file(known_files, path, -1);
|
||||
if (r < 0)
|
||||
return (void) log_warning_errno(r, "Failed to deref \"%s\", ignoring: %m", fn);
|
||||
return log_error_errno(r, "Failed to unref '%s': %m", path);
|
||||
if (r > 0)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
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);
|
||||
if (dry_run) {
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
r = chase_and_accessat(
|
||||
/* root_fd= */ root_fd,
|
||||
/* dir_fd= */ root_fd,
|
||||
path,
|
||||
CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_REGULAR,
|
||||
F_OK,
|
||||
&resolved);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Unable to determine whether '%s' exists, ignoring: %m", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_info("Would remove '%s'", resolved);
|
||||
return 1;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
r = chase_and_unlinkat(
|
||||
/* root_fd= */ root_fd,
|
||||
/* dir_fd= */ root_fd,
|
||||
path,
|
||||
CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS,
|
||||
/* unlink_flags= */ 0,
|
||||
&resolved);
|
||||
if (r == -ENOENT)
|
||||
log_debug("Resource '%s' is already removed, skipping.", path);
|
||||
else if (r < 0) {
|
||||
log_warning_errno(r, "Failed to remove '%s', ignoring: %m", path);
|
||||
return 0;
|
||||
} else
|
||||
log_info("Removed '%s'", resolved);
|
||||
|
||||
_cleanup_free_ char *parent = NULL;
|
||||
r = path_extract_directory(path, &parent);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to extract parent directory of '%s', ignoring.", path);
|
||||
else {
|
||||
_cleanup_free_ char *resolved_parent = NULL;
|
||||
r = chase_and_unlinkat(
|
||||
/* root_fd= */ root_fd,
|
||||
/* dir_fd= */ root_fd,
|
||||
parent,
|
||||
CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS,
|
||||
AT_REMOVEDIR,
|
||||
&resolved_parent);
|
||||
if (IN_SET(r, -ENOTEMPTY, -ENOENT))
|
||||
log_debug_errno(r, "Failed to remove directory '%s', ignoring: %m", parent);
|
||||
else if (r < 0)
|
||||
log_warning_errno(r, "Failed to remove directory '%s', ignoring: %m", parent);
|
||||
else
|
||||
log_info("Would remove \"%s\"", path);
|
||||
return;
|
||||
log_info("Removed '%s'.", resolved_parent);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int boot_config_find_in(const BootConfig *config, const char *root, const char *id) {
|
||||
assert(config);
|
||||
static ssize_t boot_config_find_in(
|
||||
const BootConfig *config,
|
||||
BootEntrySource source,
|
||||
const char *id) {
|
||||
|
||||
if (!root || !id)
|
||||
assert(config);
|
||||
assert(source >= 0);
|
||||
assert(source < _BOOT_ENTRY_SOURCE_MAX);
|
||||
|
||||
if (!id)
|
||||
return -ENOENT;
|
||||
|
||||
for (size_t i = 0; i < config->n_entries; i++)
|
||||
if (path_equal(config->entries[i].root, root) &&
|
||||
if (config->entries[i].source == source &&
|
||||
fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
|
||||
return i;
|
||||
return (ssize_t) 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 boot_entry_unlink(
|
||||
const BootEntry *e,
|
||||
const char *root,
|
||||
int root_fd,
|
||||
Hashmap *known_files,
|
||||
bool dry_run) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(config);
|
||||
assert(e);
|
||||
assert(root_fd >= 0);
|
||||
|
||||
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);
|
||||
(void) unref_unlink_file(&known_files, root, root_fd, e->kernel, dry_run);
|
||||
(void) unref_unlink_file(&known_files, root, root_fd, e->efi, dry_run);
|
||||
(void) unref_unlink_file(&known_files, root, root_fd, e->uki, dry_run);
|
||||
STRV_FOREACH(s, e->initrd)
|
||||
deref_unlink_file(&known_files, *s, e->root);
|
||||
deref_unlink_file(&known_files, e->device_tree, e->root);
|
||||
(void) unref_unlink_file(&known_files, root, root_fd, *s, dry_run);
|
||||
(void) unref_unlink_file(&known_files, root, root_fd, e->device_tree, dry_run);
|
||||
STRV_FOREACH(s, e->device_tree_overlay)
|
||||
deref_unlink_file(&known_files, *s, e->root);
|
||||
(void) unref_unlink_file(&known_files, root, root_fd, *s, dry_run);
|
||||
|
||||
if (arg_dry_run)
|
||||
if (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);
|
||||
const char *p = path_startswith(e->path, root);
|
||||
if (!p)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File '%s' is not inside root '%s', refusing.", e->path, root);
|
||||
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
r = chase_and_unlinkat(
|
||||
/* root_fd= */ root_fd,
|
||||
/* dir_fd= */ root_fd,
|
||||
p,
|
||||
CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS,
|
||||
/* unlink_flags= */ 0,
|
||||
&resolved);
|
||||
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);
|
||||
log_info("Removed '%s'.", resolved);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
dev_t esp_devid = 0, xbootldr_devid = 0;
|
||||
static int unlink_entry(
|
||||
const BootConfig *config,
|
||||
const char *root,
|
||||
int root_fd,
|
||||
BootEntrySource source,
|
||||
char **ids,
|
||||
bool dry_run) {
|
||||
|
||||
size_t n_removed = 0;
|
||||
int r;
|
||||
|
||||
assert(config);
|
||||
|
||||
_cleanup_hashmap_free_ Hashmap *known_files = NULL;
|
||||
r = boot_config_count_known_files(config, source, &known_files);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to count files in %s: %m", root);
|
||||
|
||||
int ret = 0;
|
||||
STRV_FOREACH(id, ids) {
|
||||
log_debug("Unlinking '%s'", *id);
|
||||
ssize_t idx = boot_config_find_in(config, source, *id);
|
||||
if (idx < 0)
|
||||
continue; /* There is nothing to remove. */
|
||||
|
||||
log_debug("Index %zi", idx);
|
||||
|
||||
if (idx == config->default_entry)
|
||||
log_warning("%s is the default boot entry", *id);
|
||||
if (idx == config->selected_entry)
|
||||
log_warning("%s is the selected boot entry", *id);
|
||||
|
||||
r = boot_entry_unlink(config->entries + idx, root, root_fd, known_files, dry_run);
|
||||
if (r < 0)
|
||||
RET_GATHER(ret, r);
|
||||
else
|
||||
n_removed++;
|
||||
}
|
||||
|
||||
if (n_removed == 0)
|
||||
log_info("No matching entries found or removed.");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int unlink_context_from_cmdline(UnlinkContext *ret) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
_cleanup_(unlink_context_done) UnlinkContext b = UNLINK_CONTEXT_NULL;
|
||||
b.entry_token_type = arg_entry_token_type;
|
||||
|
||||
if (strdup_to(&b.entry_token, arg_entry_token) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (arg_root) {
|
||||
b.root_fd = open(arg_root, O_CLOEXEC|O_DIRECTORY|O_PATH);
|
||||
if (b.root_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root);
|
||||
|
||||
if (strdup_to(&b.root, arg_root) < 0)
|
||||
return log_oom();
|
||||
} else
|
||||
b.root_fd = XAT_FDROOT;
|
||||
|
||||
r = acquire_esp(/* unprivileged_mode= */ false,
|
||||
/* graceful= */ false,
|
||||
/* ret_fd= */ NULL,
|
||||
&b.esp_fd,
|
||||
/* 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;
|
||||
&b.esp_devid);
|
||||
if (r < 0 && r != -ENOKEY)
|
||||
return r; /* About all other errors acquire_esp() logs on its own */
|
||||
if (r > 0) {
|
||||
if (arg_root) {
|
||||
const char *e = path_startswith(arg_esp_path, arg_root);
|
||||
if (!e)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root);
|
||||
|
||||
r = strdup_to(&b.esp_path, e);
|
||||
} else
|
||||
r = strdup_to(&b.esp_path, arg_esp_path);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = acquire_xbootldr(
|
||||
/* unprivileged_mode= */ false,
|
||||
/* ret_fd= */ NULL,
|
||||
&b.xbootldr_fd,
|
||||
/* ret_uuid= */ NULL,
|
||||
&xbootldr_devid);
|
||||
if (r == -EACCES)
|
||||
return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m");
|
||||
if (r < 0)
|
||||
&b.xbootldr_devid);
|
||||
if (r < 0 && r != -ENOKEY)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
if (arg_root) {
|
||||
const char *e = path_startswith(arg_xbootldr_path, arg_root);
|
||||
if (!e)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XBOOTLDR path '%s' not below specified root '%s', refusing.", arg_xbootldr_path, arg_root);
|
||||
|
||||
r = strdup_to(&b.xbootldr_path, e);
|
||||
} else
|
||||
r = strdup_to(&b.xbootldr_path, arg_xbootldr_path);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
/* Only if we found neither ESP nor XBOOTLDR let's fail. */
|
||||
if (!b.xbootldr_path && !b.esp_path)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Neither ESP nor XBOOTLDR found, refusing.");
|
||||
|
||||
*ret = TAKE_GENERIC(b, UnlinkContext, UNLINK_CONTEXT_NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run_unlink(
|
||||
UnlinkContext *c,
|
||||
char **_ids,
|
||||
bool dry_run) {
|
||||
|
||||
int r;
|
||||
assert(c);
|
||||
|
||||
_cleanup_free_ char *x = NULL, *y = NULL;
|
||||
if (c->root && c->esp_path) {
|
||||
x = path_join(c->root, c->esp_path);
|
||||
if (!x)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (c->root && c->xbootldr_path) {
|
||||
y = path_join(c->root, c->xbootldr_path);
|
||||
if (!y)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
_cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
|
||||
r = boot_config_load_and_select(
|
||||
&config,
|
||||
arg_root,
|
||||
arg_esp_path,
|
||||
esp_devid,
|
||||
arg_xbootldr_path,
|
||||
xbootldr_devid);
|
||||
c->root,
|
||||
x ?: c->esp_path,
|
||||
c->esp_devid,
|
||||
y ?: c->xbootldr_path,
|
||||
c->xbootldr_devid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = 0;
|
||||
RET_GATHER(r, unlink_entry(&config, arg_esp_path, argv[1]));
|
||||
_cleanup_(strv_freep) char **ids = NULL;
|
||||
if (strv_isempty(_ids)) {
|
||||
r = id128_get_machine_at(c->root_fd, &c->machine_id);
|
||||
if (r < 0 && !ERRNO_IS_NEG_MACHINE_ID_UNSET(r))
|
||||
return log_error_errno(r, "Failed to get machine-id: %m");
|
||||
|
||||
if (arg_xbootldr_path && xbootldr_devid != esp_devid)
|
||||
RET_GATHER(r, unlink_entry(&config, arg_xbootldr_path, argv[1]));
|
||||
const char *e = secure_getenv("KERNEL_INSTALL_CONF_ROOT");
|
||||
r = boot_entry_token_ensure_at(
|
||||
e ? XAT_FDROOT : c->root_fd,
|
||||
e,
|
||||
c->machine_id,
|
||||
/* machine_id_is_random= */ false,
|
||||
&c->entry_token_type,
|
||||
&c->entry_token);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = boot_config_find_oldest_commit(
|
||||
&config,
|
||||
c->entry_token,
|
||||
&ids);
|
||||
if (r == -ENXIO)
|
||||
return log_error_errno(r, "No suitable boot menu entry to delete found.");
|
||||
if (r == -EBUSY)
|
||||
return log_error_errno(r, "Refusing to remove currently booted boot menu entry.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to find suitable oldest boot menu entry: %m");
|
||||
|
||||
STRV_FOREACH(id, ids)
|
||||
log_info("Will unlink '%s'.", *id);
|
||||
} else {
|
||||
ids = strv_copy(_ids);
|
||||
if (!ids)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
strv_sort_uniq(ids);
|
||||
|
||||
r = 0;
|
||||
if (c->esp_path)
|
||||
RET_GATHER(r, unlink_entry(&config, x ?: c->esp_path, c->esp_fd, BOOT_ENTRY_ESP, ids, dry_run));
|
||||
|
||||
if (c->xbootldr_path && c->xbootldr_devid != c->esp_devid)
|
||||
RET_GATHER(r, unlink_entry(&config, y ?: c->xbootldr_path, c->xbootldr_fd, BOOT_ENTRY_XBOOTLDR, ids, dry_run));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
int r;
|
||||
|
||||
assert(argc < 3);
|
||||
|
||||
if (arg_oldest != isempty(argv[1]))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Exactly one of an entry ID or --oldest= must be specified.");
|
||||
|
||||
const char *id = empty_to_null(argv[1]);
|
||||
|
||||
_cleanup_(unlink_context_done) UnlinkContext c = UNLINK_CONTEXT_NULL;
|
||||
r = unlink_context_from_cmdline(&c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return run_unlink(&c, STRV_MAKE(id), arg_dry_run);
|
||||
}
|
||||
|
||||
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string);
|
||||
|
||||
typedef struct UnlinkParameters {
|
||||
UnlinkContext context;
|
||||
unsigned root_fd_index;
|
||||
const char *id;
|
||||
bool oldest;
|
||||
} UnlinkParameters;
|
||||
|
||||
static void unlink_parameters_done(UnlinkParameters *p) {
|
||||
assert(p);
|
||||
|
||||
unlink_context_done(&p->context);
|
||||
}
|
||||
|
||||
int vl_method_unlink(
|
||||
sd_varlink *link,
|
||||
sd_json_variant *parameters,
|
||||
sd_varlink_method_flags_t flags,
|
||||
void *userdata) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
_cleanup_(unlink_parameters_done) UnlinkParameters p = {
|
||||
.context = UNLINK_CONTEXT_NULL,
|
||||
.root_fd_index = UINT_MAX,
|
||||
};
|
||||
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "rootFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, root_fd_index), 0 },
|
||||
{ "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, voffsetof(p, context.root), 0 },
|
||||
{ "bootEntryTokenType", SD_JSON_VARIANT_STRING, json_dispatch_boot_entry_token_type, voffsetof(p, context.entry_token_type), 0 },
|
||||
{ "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, id), 0 },
|
||||
{ "oldest", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, voffsetof(p, oldest), 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
/* Only allow oldest *or* id to be set */
|
||||
if (p.oldest == !!p.id)
|
||||
return sd_varlink_error_invalid_parameter_name(link, "id");
|
||||
if (p.id && !efi_loader_entry_name_valid(p.id))
|
||||
return sd_varlink_error_invalid_parameter_name(link, "id");
|
||||
|
||||
if (p.root_fd_index != UINT_MAX) {
|
||||
p.context.root_fd = sd_varlink_peek_dup_fd(link, p.root_fd_index);
|
||||
if (p.context.root_fd < 0)
|
||||
return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m");
|
||||
|
||||
r = fd_verify_safe_flags_full(p.context.root_fd, O_DIRECTORY);
|
||||
if (r < 0)
|
||||
return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor");
|
||||
|
||||
r = fd_verify_directory(p.context.root_fd);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m");
|
||||
|
||||
if (!p.context.root) {
|
||||
r = fd_get_path(p.context.root_fd, &p.context.root);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to get path of file descriptor: %m");
|
||||
|
||||
if (empty_or_root(p.context.root))
|
||||
p.context.root = mfree(p.context.root);
|
||||
}
|
||||
} else if (p.context.root) {
|
||||
p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
||||
if (p.context.root_fd < 0)
|
||||
return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root);
|
||||
} else
|
||||
p.context.root_fd = XAT_FDROOT;
|
||||
|
||||
if (p.context.entry_token_type < 0)
|
||||
p.context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
|
||||
|
||||
r = find_esp_and_warn_at_full(
|
||||
p.context.root_fd,
|
||||
/* path= */ NULL,
|
||||
/* unprivileged_mode= */ false,
|
||||
&p.context.esp_path,
|
||||
&p.context.esp_fd,
|
||||
/* ret_part= */ NULL,
|
||||
/* ret_pstart= */ NULL,
|
||||
/* ret_psize= */ NULL,
|
||||
/* ret_uuid= */ NULL,
|
||||
&p.context.esp_devid);
|
||||
if (r < 0 && r != -ENOKEY)
|
||||
return r;
|
||||
r = find_xbootldr_and_warn_at_full(
|
||||
p.context.root_fd,
|
||||
/* path= */ NULL,
|
||||
/* unprivileged_mode= */ false,
|
||||
&p.context.xbootldr_path,
|
||||
&p.context.xbootldr_fd,
|
||||
/* ret_uuid= */ NULL,
|
||||
&p.context.xbootldr_devid);
|
||||
if (r < 0 && r != -ENOKEY)
|
||||
return r;
|
||||
|
||||
/* Only if we found neither ESP nor XBOOTLDR let's fail. */
|
||||
if (!p.context.xbootldr_path && !p.context.esp_path)
|
||||
return sd_varlink_error(link, "io.systemd.BootControl.NoDollarBootFound", NULL);
|
||||
|
||||
r = run_unlink(&p.context, STRV_MAKE(p.id), /* dry_run= */ false);
|
||||
if (r == -EUNATCH) /* no boot entry token is set */
|
||||
return sd_varlink_error(link, "io.systemd.BootControl.BootEntryTokenUnavailable", NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return sd_varlink_reply(link, NULL);
|
||||
}
|
||||
|
||||
@@ -5,4 +5,8 @@
|
||||
|
||||
int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata);
|
||||
|
||||
int boot_config_count_known_files(const BootConfig *config, const char* root, Hashmap **ret_known_files);
|
||||
int vl_method_unlink(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
|
||||
|
||||
int boot_config_count_known_files(const BootConfig *config, BootEntrySource source, Hashmap **ret_known_files);
|
||||
|
||||
int boot_entry_unlink(const BootEntry *e, const char *root, int root_fd, Hashmap *known_files, bool dry_run);
|
||||
|
||||
+13
-2
@@ -34,6 +34,7 @@
|
||||
#include "options.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "string-table.h"
|
||||
@@ -80,6 +81,7 @@ char *arg_certificate_source = NULL;
|
||||
char *arg_private_key = NULL;
|
||||
KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE;
|
||||
char *arg_private_key_source = NULL;
|
||||
bool arg_oldest = false;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
|
||||
@@ -361,7 +363,7 @@ VERB_GROUP("Boot Loader Specification Commands");
|
||||
VERB_SCOPE_NOARG(, verb_list, "list",
|
||||
"List boot loader entries");
|
||||
|
||||
VERB_SCOPE(, verb_unlink, "unlink", "ID", 2, 2, 0,
|
||||
VERB_SCOPE(, verb_unlink, "unlink", "ID", VERB_ANY, 2, 0,
|
||||
"Remove boot loader entry");
|
||||
|
||||
VERB_SCOPE_NOARG(, verb_cleanup, "cleanup",
|
||||
@@ -631,6 +633,14 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("oldest", "BOOL",
|
||||
"Delete oldest boot menu entry"):
|
||||
r = parse_boolean_argument("--oldest=", opts.arg, &arg_oldest);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
char **args = option_parser_get_args(&opts);
|
||||
@@ -700,7 +710,8 @@ static int vl_server(void) {
|
||||
"io.systemd.BootControl.ListBootEntries", vl_method_list_boot_entries,
|
||||
"io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware,
|
||||
"io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware,
|
||||
"io.systemd.BootControl.Install", vl_method_install);
|
||||
"io.systemd.BootControl.Install", vl_method_install,
|
||||
"io.systemd.BootControl.Unlink", vl_method_unlink);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind Varlink methods: %m");
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ extern char *arg_certificate_source;
|
||||
extern char *arg_private_key;
|
||||
extern KeySourceType arg_private_key_source_type;
|
||||
extern char *arg_private_key_source;
|
||||
extern bool arg_oldest;
|
||||
|
||||
static inline const char* arg_dollar_boot_path(void) {
|
||||
/* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */
|
||||
|
||||
@@ -14,6 +14,7 @@ struct in_addr_full;
|
||||
|
||||
typedef enum AskPasswordFlags AskPasswordFlags;
|
||||
typedef enum BootEntryTokenType BootEntryTokenType;
|
||||
typedef enum BootEntrySource BootEntrySource;
|
||||
typedef enum BusPrintPropertyFlags BusPrintPropertyFlags;
|
||||
typedef enum BusTransport BusTransport;
|
||||
typedef enum CatFlags CatFlags;
|
||||
@@ -48,6 +49,7 @@ typedef enum UserStorage UserStorage;
|
||||
typedef struct AskPasswordRequest AskPasswordRequest;
|
||||
typedef struct Bitmap Bitmap;
|
||||
typedef struct BootConfig BootConfig;
|
||||
typedef struct BootEntry BootEntry;
|
||||
typedef struct BPFProgram BPFProgram;
|
||||
typedef struct BusObjectImplementation BusObjectImplementation;
|
||||
typedef struct CalendarSpec CalendarSpec;
|
||||
|
||||
@@ -134,6 +134,19 @@ static SD_VARLINK_DEFINE_METHOD(
|
||||
SD_VARLINK_FIELD_COMMENT("If true the boot loader will be registered in an EFI boot entry via EFI variables, otherwise this is omitted"),
|
||||
SD_VARLINK_DEFINE_INPUT(touchVariables, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
Unlink,
|
||||
SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"),
|
||||
SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."),
|
||||
SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The ID of the boot loader entry to remove."),
|
||||
SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("If true, remove the oldest entry."),
|
||||
SD_VARLINK_DEFINE_INPUT(oldest, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_ERROR(
|
||||
RebootToFirmwareNotSupported);
|
||||
|
||||
@@ -143,6 +156,9 @@ static SD_VARLINK_DEFINE_ERROR(
|
||||
static SD_VARLINK_DEFINE_ERROR(
|
||||
NoESPFound);
|
||||
|
||||
static SD_VARLINK_DEFINE_ERROR(
|
||||
NoDollarBootFound);
|
||||
|
||||
static SD_VARLINK_DEFINE_ERROR(
|
||||
BootEntryTokenUnavailable);
|
||||
|
||||
@@ -170,11 +186,15 @@ SD_VARLINK_DEFINE_INTERFACE(
|
||||
&vl_type_BootEntryTokenType,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Install the boot loader on the ESP."),
|
||||
&vl_method_Install,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Unlink a boot menu item"),
|
||||
&vl_method_Unlink,
|
||||
SD_VARLINK_SYMBOL_COMMENT("SetRebootToFirmware() and GetRebootToFirmware() return this if the firmware does not actually support the reboot-to-firmware-UI concept."),
|
||||
&vl_error_RebootToFirmwareNotSupported,
|
||||
SD_VARLINK_SYMBOL_COMMENT("No boot entry defined."),
|
||||
&vl_error_NoSuchBootEntry,
|
||||
SD_VARLINK_SYMBOL_COMMENT("No EFI System Partition (ESP) found."),
|
||||
&vl_error_NoESPFound,
|
||||
SD_VARLINK_SYMBOL_COMMENT("The select boot entry token could not be determined."),
|
||||
SD_VARLINK_SYMBOL_COMMENT("Neither ESP nor XBOOTLDR found, hence no $BOOT location identified."),
|
||||
&vl_error_NoDollarBootFound,
|
||||
SD_VARLINK_SYMBOL_COMMENT("The selected boot entry token could not be determined."),
|
||||
&vl_error_BootEntryTokenUnavailable);
|
||||
|
||||
Reference in New Issue
Block a user