bootctl: add Install() varlink API

Fixes: #11221
This commit is contained in:
Lennart Poettering
2026-02-06 23:15:49 +01:00
parent 38433a6d06
commit aea76373b2
8 changed files with 198 additions and 7 deletions
+124
View File
@@ -3,6 +3,8 @@
#include <stdlib.h>
#include <unistd.h>
#include "sd-varlink.h"
#include "alloc-util.h"
#include "boot-entry.h"
#include "bootctl.h"
@@ -18,11 +20,13 @@
#include "env-file.h"
#include "fd-util.h"
#include "fileio.h"
#include "find-esp.h"
#include "fs-util.h"
#include "glyph-util.h"
#include "id128-util.h"
#include "install-file.h"
#include "io-util.h"
#include "json-util.h"
#include "kernel-config.h"
#include "log.h"
#include "openssl-util.h"
@@ -89,6 +93,15 @@ typedef struct InstallContext {
.touch_variables = -1, \
}
static const char* install_operation_table[_INSTALL_OPERATION_MAX] = {
[INSTALL_NEW] = "new",
[INSTALL_UPDATE] = "update",
[INSTALL_REMOVE] = "remove",
[INSTALL_TEST] = "test",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(install_operation, InstallOperation);
static void install_context_done(InstallContext *c) {
assert(c);
@@ -1912,3 +1925,114 @@ int verb_is_installed(int argc, char *argv[], void *userdata) {
return EXIT_FAILURE;
}
}
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_install_operation, InstallOperation, install_operation_from_string);
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string);
typedef struct InstallParameters {
InstallContext context;
unsigned root_fd_index;
} InstallParameters;
static void install_parameters_done(InstallParameters *p) {
assert(p);
install_context_done(&p->context);
}
int vl_method_install(
sd_varlink *link,
sd_json_variant *parameters,
sd_varlink_method_flags_t flags,
void *userdata) {
int r;
assert(link);
_cleanup_(install_parameters_done) InstallParameters p = {
.context = INSTALL_CONTEXT_NULL,
.root_fd_index = UINT_MAX,
};
static const sd_json_dispatch_field dispatch_table[] = {
{ "operation", SD_JSON_VARIANT_STRING, json_dispatch_install_operation, voffsetof(p, context.operation), SD_JSON_MANDATORY },
{ "graceful", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, voffsetof(p, context.graceful), 0 },
{ "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 },
{ "touchVariables", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(p, context.touch_variables), 0 },
{},
};
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
if (!IN_SET(p.context.operation, INSTALL_NEW, INSTALL_UPDATE))
return sd_varlink_error_invalid_parameter_name(link, "operation");
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_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);
}
}
if (p.context.root_fd < 0 && 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);
}
if (p.context.root_fd < 0)
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(
p.context.root_fd,
/* path= */ NULL,
/* unprivileged_mode= */ false,
&p.context.esp_path,
&p.context.esp_part,
&p.context.esp_pstart,
&p.context.esp_psize,
&p.context.esp_uuid,
/* ret_devid= */ NULL);
if (r == -ENOKEY)
return sd_varlink_error(link, "io.systemd.BootControl.NoESPFound", NULL);
if (r < 0)
return r;
r = find_xbootldr_and_warn_at(
p.context.root_fd,
/* path= */ NULL,
/* unprivileged_mode= */ false,
&p.context.xbootldr_path,
/* ret_uuid= */ NULL,
/* ret_devid= */ NULL);
if (r == -ENOKEY)
log_debug_errno(r, "Didn't find an XBOOTLDR partition, using ESP as $BOOT.");
else if (r < 0)
return r;
r = run_install(&p.context);
if (r < 0)
return r;
return sd_varlink_reply(link, NULL);
}
+4
View File
@@ -1,6 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "shared-forward.h"
int verb_install(int argc, char *argv[], void *userdata);
int verb_remove(int argc, char *argv[], void *userdata);
int verb_is_installed(int argc, char *argv[], void *userdata);
int vl_method_install(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+3 -2
View File
@@ -711,7 +711,7 @@ static int vl_server(void) {
r = varlink_server_new(
&varlink_server,
SD_VARLINK_SERVER_ROOT_ONLY,
SD_VARLINK_SERVER_ROOT_ONLY|SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT,
/* userdata= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
@@ -724,7 +724,8 @@ static int vl_server(void) {
varlink_server,
"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.GetRebootToFirmware", vl_method_get_reboot_to_firmware,
"io.systemd.BootControl.Install", vl_method_install);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %m");
+1 -1
View File
@@ -291,4 +291,4 @@ static const char *const boot_entry_token_type_table[] = {
[BOOT_ENTRY_TOKEN_AUTO] = "auto",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_token_type, BootEntryTokenType);
DEFINE_STRING_TABLE_LOOKUP(boot_entry_token_type, BootEntryTokenType);
+1 -1
View File
@@ -34,4 +34,4 @@ int boot_entry_token_ensure_at(
int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char **token);
DECLARE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_token_type, BootEntryTokenType);
DECLARE_STRING_TABLE_LOOKUP(boot_entry_token_type, BootEntryTokenType);
+63 -1
View File
@@ -36,21 +36,37 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_DEFINE_FIELD_BY_TYPE(source, BootEntrySource, 0),
SD_VARLINK_FIELD_COMMENT("The string identifier of the entry"),
SD_VARLINK_DEFINE_FIELD(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Path to the primary definition file for the entry"),
SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Directory path of the file system root the entry was found on"),
SD_VARLINK_DEFINE_FIELD(root, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The entry's title string"),
SD_VARLINK_DEFINE_FIELD(title, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The possibly mangled/augmented title to show for the entry"),
SD_VARLINK_DEFINE_FIELD(showTitle, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("An explicitly configured sorting key for the enry"),
SD_VARLINK_DEFINE_FIELD(sortKey, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The version of the entry"),
SD_VARLINK_DEFINE_FIELD(version, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Machine ID of the OS installation belonging to the entry, if known"),
SD_VARLINK_DEFINE_FIELD(machineId, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("EFI architecture name for this entry"),
SD_VARLINK_DEFINE_FIELD(architecture, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Command line options to pass to the invoked kernel or EFI binary"),
SD_VARLINK_DEFINE_FIELD(options, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Path to the Linux kernel to invoke, relative to the root directory of the file system containing the entry file"),
SD_VARLINK_DEFINE_FIELD(linux, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Path to the EFI binary to invoke, relative to the root directory of the file system containing the entry file"),
SD_VARLINK_DEFINE_FIELD(efi, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Path to an UKI EFI binary to invoke, relative to the root directory of the file system containing the entry file"),
SD_VARLINK_DEFINE_FIELD(uki, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("An UKI profile index to invoke. If not specified defaults to the first profile."),
SD_VARLINK_DEFINE_FIELD(profile, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Path to the initrd image to pass to the invoked kernel, relative to the root directory of the file system containing the entry file"),
SD_VARLINK_DEFINE_FIELD(initrd, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Devicetree file to pass to the invoked kernel, relative to the root directory of the file system containing the entry file"),
SD_VARLINK_DEFINE_FIELD(devicetree, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Devicetree overlay file to pass to the invoked kernel, relative to the root directory of the file system containing the entry file"),
SD_VARLINK_DEFINE_FIELD(devicetreeOverlay, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Indicates whether the boot loader reported this entry on the current boot"),
SD_VARLINK_DEFINE_FIELD(isReported, SD_VARLINK_BOOL, 0),
@@ -83,12 +99,50 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_FIELD_COMMENT("The current state of the reboot-to-firmware-UI flag"),
SD_VARLINK_DEFINE_OUTPUT(state, SD_VARLINK_BOOL, 0));
static SD_VARLINK_DEFINE_ENUM_TYPE(
Operation,
SD_VARLINK_FIELD_COMMENT("Install the boot loader afresh, creating everything it needs"),
SD_VARLINK_DEFINE_ENUM_VALUE(new),
SD_VARLINK_FIELD_COMMENT("Just update existing boot loader binaries"),
SD_VARLINK_DEFINE_ENUM_VALUE(update));
static SD_VARLINK_DEFINE_ENUM_TYPE(
BootEntryTokenType,
SD_VARLINK_FIELD_COMMENT("Pick identifiers for type #1 boot entries based on /etc/machine-id"),
SD_VARLINK_DEFINE_ENUM_VALUE(machine_id),
SD_VARLINK_FIELD_COMMENT("Pick identifiers for type #1 boot entries based on the IMAGE_ID= field from /etc/os-release"),
SD_VARLINK_DEFINE_ENUM_VALUE(os_image_id),
SD_VARLINK_FIELD_COMMENT("Pick identifiers for type #1 boot entries based on the ID= field from /etc/os-release"),
SD_VARLINK_DEFINE_ENUM_VALUE(os_id),
SD_VARLINK_FIELD_COMMENT("Pick identifiers for type #1 boot entries based on a manually chosen string"),
SD_VARLINK_DEFINE_ENUM_VALUE(literal),
SD_VARLINK_FIELD_COMMENT("Choose automatically how to pick identifiers for type #1 boot entries"),
SD_VARLINK_DEFINE_ENUM_VALUE(auto));
static SD_VARLINK_DEFINE_METHOD(
Install,
SD_VARLINK_FIELD_COMMENT("Operation, either 'new' or 'update'"),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(operation, Operation, 0),
SD_VARLINK_FIELD_COMMENT("If true, continue on various failures"),
SD_VARLINK_DEFINE_INPUT(graceful, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
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_INT, 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("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_ERROR(
RebootToFirmwareNotSupported);
static SD_VARLINK_DEFINE_ERROR(
NoSuchBootEntry);
static SD_VARLINK_DEFINE_ERROR(
NoESPFound);
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_BootControl,
"io.systemd.BootControl",
@@ -101,13 +155,21 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_type_BootEntryAddon,
SD_VARLINK_SYMBOL_COMMENT("A structure encapsulating a boot entry"),
&vl_type_BootEntry,
SD_VARLINK_SYMBOL_COMMENT("The operation to execute"),
&vl_type_Operation,
SD_VARLINK_SYMBOL_COMMENT("Enumerates boot entries. Method call must be called with 'more' flag set. Each response returns one entry. If no entries are defined returns the NoSuchBootEntry error."),
&vl_method_ListBootEntries,
SD_VARLINK_SYMBOL_COMMENT("Sets the reboot-to-firmware-UI flag of the firmware, if this concept exists. Returns the RebootToFirmwareNotSupported error if not."),
&vl_method_SetRebootToFirmware,
SD_VARLINK_SYMBOL_COMMENT("Gets the current state of the reboot-to-firmware-UI flag of the firmware, if this concept exists. Returns the RebootToFirmwareNotSupported error if not."),
&vl_method_GetRebootToFirmware,
SD_VARLINK_SYMBOL_COMMENT("The boot entry token type to use."),
&vl_type_BootEntryTokenType,
SD_VARLINK_SYMBOL_COMMENT("Install the boot loader on the ESP."),
&vl_method_Install,
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);
&vl_error_NoSuchBootEntry,
SD_VARLINK_SYMBOL_COMMENT("No EFI System Partition (ESP) found."),
&vl_error_NoESPFound);
+1 -1
View File
@@ -8,7 +8,7 @@
# (at your option) any later version.
[Unit]
Description=Boot Entries Service Socket
Description=Boot Loader Control Service Socket
Documentation=man:bootctl(1)
DefaultDependencies=no
After=local-fs.target
+1 -1
View File
@@ -8,7 +8,7 @@
# (at your option) any later version.
[Unit]
Description=Boot Entries Service
Description=Boot Loader Control Service
Documentation=man:bootctl(1)
DefaultDependencies=no
Conflicts=shutdown.target