]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
bootctl: add Install() varlink API
authorLennart Poettering <lennart@amutable.com>
Wed, 3 Sep 2025 10:11:19 +0000 (12:11 +0200)
committerLennart Poettering <lennart@amutable.com>
Fri, 6 Feb 2026 22:15:49 +0000 (23:15 +0100)
Fixes: #11221
src/bootctl/bootctl-install.c
src/bootctl/bootctl-install.h
src/bootctl/bootctl.c
src/shared/boot-entry.c
src/shared/boot-entry.h
src/shared/varlink-io.systemd.BootControl.c
units/systemd-bootctl.socket
units/systemd-bootctl@.service

index 4abaa1aa83eefd5ac9719772d52f588920a045f3..d6ce3471b11942fac972708782b64202e234cb19 100644 (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"
 #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);
+}
index cd4b725112aacffde023a642013619e83b1d7ad7..f2d7fab5c965e54bf3ad22a68cfda6a938ee8a0f 100644 (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);
index dc321a07ca859d125d23fc5a0aa06e8b8a06b892..889c5f91b67ab2879b2a58f2f1abfc79e0793ef9 100644 (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");
 
index 2885d3f92e8a9cf8eb3922163a46bb9f7bfc17c1..042522951cc911f01852250ff5b2362a270e6509 100644 (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);
index 84ea69a096aaf3ebe7b4737d982f9fc244a0037b..7db1ed0d5203d29ed00aa3eb123d4429a6902867 100644 (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);
index 11dcb4d09552c20c02c2fec7efdf2830f500fa42..453002aaa9f8c466555c19fae5c434bc4a6da7df 100644 (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);
index d4071833b6a4e07a5a0e70a443a13c07debf3169..f9553b840be4861a73a0a06a09130aed29df3085 100644 (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
index 3f49e104573ba1ce68e4baba3fae858b91931b9e..7a83d25b38b11440a813b7cbf4eabf8188953ffa 100644 (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