From: Lennart Poettering Date: Wed, 3 Sep 2025 10:11:19 +0000 (+0200) Subject: bootctl: add Install() varlink API X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=aea76373b29e418101d4a17aacc1fa664af65768;p=thirdparty%2Fsystemd.git bootctl: add Install() varlink API Fixes: #11221 --- diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 4abaa1aa83e..d6ce3471b11 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -3,6 +3,8 @@ #include #include +#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); +} diff --git a/src/bootctl/bootctl-install.h b/src/bootctl/bootctl-install.h index cd4b725112a..f2d7fab5c96 100644 --- a/src/bootctl/bootctl-install.h +++ b/src/bootctl/bootctl-install.h @@ -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); diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index dc321a07ca8..889c5f91b67 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -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"); diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c index 2885d3f92e8..042522951cc 100644 --- a/src/shared/boot-entry.c +++ b/src/shared/boot-entry.c @@ -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); diff --git a/src/shared/boot-entry.h b/src/shared/boot-entry.h index 84ea69a096a..7db1ed0d520 100644 --- a/src/shared/boot-entry.h +++ b/src/shared/boot-entry.h @@ -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); diff --git a/src/shared/varlink-io.systemd.BootControl.c b/src/shared/varlink-io.systemd.BootControl.c index 11dcb4d0955..453002aaa9f 100644 --- a/src/shared/varlink-io.systemd.BootControl.c +++ b/src/shared/varlink-io.systemd.BootControl.c @@ -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); diff --git a/units/systemd-bootctl.socket b/units/systemd-bootctl.socket index d4071833b6a..f9553b840be 100644 --- a/units/systemd-bootctl.socket +++ b/units/systemd-bootctl.socket @@ -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 diff --git a/units/systemd-bootctl@.service b/units/systemd-bootctl@.service index 3f49e104573..7a83d25b38b 100644 --- a/units/systemd-bootctl@.service +++ b/units/systemd-bootctl@.service @@ -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