From 2cda44c23eb54cebf60f90aaeda82d95ec204152 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Feb 2024 17:23:59 +0100 Subject: [PATCH 1/3] efi-loader: make efi_loader_get_entries() handling missing NUL termination gracefully Our function so far assumed that the LoaderEntries's last string is or is not NUL terminated. But if it was, then we'd debug log about this, claiming there was an invalid id. sd-boot actually ends the list in a properly NUL-terminated string, hence we should just accept that. Handle that case gracefully, and add comments explaining why we have two ways why we exit the loop. This is cosmetic only, just suppresses a misleading debug log message. --- src/shared/efi-loader.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index 758aaa13c1..7d6bda924a 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -102,7 +102,8 @@ int efi_loader_get_entries(char ***ret) { if (r < 0) return r; - /* The variable contains a series of individually NUL terminated UTF-16 strings. */ + /* The variable contains a series of individually NUL terminated UTF-16 strings. We gracefully + * consider the final NUL byte optional (i.e. the last string may or may not end in a NUL byte).*/ for (size_t i = 0, start = 0;; i++) { _cleanup_free_ char *decoded = NULL; @@ -116,6 +117,11 @@ int efi_loader_get_entries(char ***ret) { if (!end && entries[i] != 0) continue; + /* Empty string at the end of variable? That's the trailer, we are done (i.e. we have a final + * NUL terminator). */ + if (end && start == i) + break; + /* We reached the end of a string, let's decode it into UTF-8 */ decoded = utf16_to_utf8(entries + start, (i - start) * sizeof(char16_t)); if (!decoded) @@ -128,7 +134,8 @@ int efi_loader_get_entries(char ***ret) { } else log_debug("Ignoring invalid loader entry '%s'.", decoded); - /* We reached the end of the variable */ + /* Exit the loop if we reached the end of the variable (i.e. we do not have a final NUL + * terminator) */ if (end) break; From f892954ba29a58aa1596d9a539cc998e10433f43 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Feb 2024 17:29:17 +0100 Subject: [PATCH 2/3] bootspec: split out helper that turns BootEntry into a JSON object We can use that later for returning the boot loader entry list as JSON via Varlink. --- src/shared/bootspec.c | 97 ++++++++++++++++++++++++++----------------- src/shared/bootspec.h | 2 + 2 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index b0bf94c7da..df45c67000 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -1668,6 +1668,63 @@ int show_boot_entry( return -status; } +int boot_entry_to_json(const BootConfig *c, size_t i, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + const BootEntry *e; + int r; + + assert(c); + assert(ret); + + if (i >= c->n_entries) { + *ret = NULL; + return 0; + } + + e = c->entries + i; + + r = json_variant_merge_objectb( + &v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))), + JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)), + JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)), + JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)), + JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)), + JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))), + JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)), + JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)), + JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)), + JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)), + JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)), + JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)), + JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay)))); + if (r < 0) + return log_oom(); + + /* Sanitizers (only memory sanitizer?) do not like function call with too many + * arguments and trigger false positive warnings. Let's not add too many json objects + * at once. */ + r = json_variant_merge_objectb( + &v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("isReported", JSON_BUILD_BOOLEAN(e->reported_by_loader)), + JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", JSON_BUILD_UNSIGNED(e->tries_left)), + JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", JSON_BUILD_UNSIGNED(e->tries_done)), + JSON_BUILD_PAIR_CONDITION(c->default_entry >= 0, "isDefault", JSON_BUILD_BOOLEAN(i == (size_t) c->default_entry)), + JSON_BUILD_PAIR_CONDITION(c->selected_entry >= 0, "isSelected", JSON_BUILD_BOOLEAN(i == (size_t) c->selected_entry)))); + + if (r < 0) + return log_oom(); + + r = json_cmdline(e, &c->global_addons, &v); + if (r < 0) + return log_oom(); + + *ret = TAKE_PTR(v); + return 1; +} + int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { int r; @@ -1677,44 +1734,9 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; for (size_t i = 0; i < config->n_entries; i++) { - const BootEntry *e = config->entries + i; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - r = json_variant_merge_objectb( - &v, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))), - JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)), - JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)), - JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)), - JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)), - JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))), - JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)), - JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)), - JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)), - JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)), - JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)), - JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)), - JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)), - JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)), - JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay)))); - if (r < 0) - return log_oom(); - - r = json_cmdline(e, &config->global_addons, &v); - if (r < 0) - return log_oom(); - - /* Sanitizers (only memory sanitizer?) do not like function call with too many - * arguments and trigger false positive warnings. Let's not add too many json objects - * at once. */ - r = json_variant_merge_objectb( - &v, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR("isReported", JSON_BUILD_BOOLEAN(e->reported_by_loader)), - JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", JSON_BUILD_UNSIGNED(e->tries_left)), - JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", JSON_BUILD_UNSIGNED(e->tries_done)), - JSON_BUILD_PAIR_CONDITION(config->default_entry >= 0, "isDefault", JSON_BUILD_BOOLEAN(i == (size_t) config->default_entry)), - JSON_BUILD_PAIR_CONDITION(config->selected_entry >= 0, "isSelected", JSON_BUILD_BOOLEAN(i == (size_t) config->selected_entry)))); - + r = boot_entry_to_json(config, i, &v); if (r < 0) return log_oom(); @@ -1723,8 +1745,7 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { return log_oom(); } - json_variant_dump(array, json_format | JSON_FORMAT_EMPTY_ARRAY, NULL, NULL); - + return json_variant_dump(array, json_format | JSON_FORMAT_EMPTY_ARRAY, NULL, NULL); } else { for (size_t n = 0; n < config->n_entries; n++) { r = show_boot_entry( diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index 8289325b9e..9ae88d78aa 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -143,3 +143,5 @@ int show_boot_entries( JsonFormatFlags json_format); int boot_filename_extract_tries(const char *fname, char **ret_stripped, unsigned *ret_tries_left, unsigned *ret_tries_done); + +int boot_entry_to_json(const BootConfig *c, size_t i, JsonVariant **ret); From 79ec39958d70e3eeb141f7ca1f57ea52533727b6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Feb 2024 17:30:31 +0100 Subject: [PATCH 3/3] bootctl: add a Varlink interface For now, just super basic functionality: return the list of boot menu entries, and read/write the reboot to firmware flag --- src/boot/bootctl-reboot-to-firmware.c | 39 +++++++++++- src/boot/bootctl-reboot-to-firmware.h | 5 ++ src/boot/bootctl-status.c | 68 ++++++++++++++++++++- src/boot/bootctl-status.h | 4 ++ src/boot/bootctl.c | 39 ++++++++++++ src/boot/bootctl.h | 1 + src/shared/meson.build | 1 + src/shared/varlink-io.systemd.BootControl.c | 59 ++++++++++++++++++ src/shared/varlink-io.systemd.BootControl.h | 6 ++ src/test/test-varlink-idl.c | 3 + test/units/testsuite-74.bootctl.sh | 9 +++ units/meson.build | 9 +++ units/systemd-bootctl.socket | 21 +++++++ units/systemd-bootctl@.service.in | 20 ++++++ 14 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 src/shared/varlink-io.systemd.BootControl.c create mode 100644 src/shared/varlink-io.systemd.BootControl.h create mode 100644 units/systemd-bootctl.socket create mode 100644 units/systemd-bootctl@.service.in diff --git a/src/boot/bootctl-reboot-to-firmware.c b/src/boot/bootctl-reboot-to-firmware.c index 91f259768c..cdb04f8045 100644 --- a/src/boot/bootctl-reboot-to-firmware.c +++ b/src/boot/bootctl-reboot-to-firmware.c @@ -2,6 +2,7 @@ #include "bootctl-reboot-to-firmware.h" #include "efi-api.h" +#include "errno-util.h" #include "parse-util.h" int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { @@ -17,7 +18,7 @@ int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { puts("supported"); return 1; /* recognizable error #1 */ } - if (r == -EOPNOTSUPP) { + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { puts("not supported"); return 2; /* recognizable error #2 */ } @@ -36,3 +37,39 @@ int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { return 0; } } + +int vl_method_set_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "state", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, 0, 0 }, + {} + }; + bool b; + int r; + + r = varlink_dispatch(link, parameters, dispatch_table, &b); + if (r != 0) + return r; + + r = efi_set_reboot_to_firmware(b); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL); + if (r < 0) + return r; + + return varlink_reply(link, NULL); +} + +int vl_method_get_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + int r; + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + r = efi_get_reboot_to_firmware(); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL); + if (r < 0) + return r; + + return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_BOOLEAN("state", r))); +} diff --git a/src/boot/bootctl-reboot-to-firmware.h b/src/boot/bootctl-reboot-to-firmware.h index 0ca4b2c3a3..fb8a2485b3 100644 --- a/src/boot/bootctl-reboot-to-firmware.h +++ b/src/boot/bootctl-reboot-to-firmware.h @@ -1,3 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "varlink.h" + int verb_reboot_to_firmware(int argc, char *argv[], void *userdata); + +int vl_method_set_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); +int vl_method_get_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); diff --git a/src/boot/bootctl-status.c b/src/boot/bootctl-status.c index f8b57c1f9d..58b6276dd9 100644 --- a/src/boot/bootctl-status.c +++ b/src/boot/bootctl-status.c @@ -318,7 +318,13 @@ int verb_status(int argc, char *argv[], void *userdata) { dev_t esp_devid = 0, xbootldr_devid = 0; int r, k; - r = acquire_esp(/* unprivileged_mode= */ -1, /* graceful= */ false, NULL, NULL, NULL, &esp_uuid, &esp_devid); + r = acquire_esp(/* unprivileged_mode= */ -1, + /* graceful= */ false, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + &esp_uuid, + &esp_devid); if (arg_print_esp_path) { if (r == -EACCES) /* If we couldn't acquire the ESP path, log about access errors (which is the only * error the find_esp_and_warn() won't log on its own) */ @@ -330,7 +336,10 @@ int verb_status(int argc, char *argv[], void *userdata) { return 0; } - r = acquire_xbootldr(/* unprivileged_mode= */ -1, &xbootldr_uuid, &xbootldr_devid); + r = acquire_xbootldr( + /* unprivileged_mode= */ -1, + &xbootldr_uuid, + &xbootldr_devid); if (arg_print_dollar_boot_path) { if (r == -EACCES) return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); @@ -825,3 +834,58 @@ int verb_list(int argc, char *argv[], void *userdata) { int verb_unlink(int argc, char *argv[], void *userdata) { return verb_list(argc, argv, userdata); } + +int vl_method_list_boot_entries(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + dev_t esp_devid = 0, xbootldr_devid = 0; + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + 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; + + r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); + if (r < 0) + return r; + + _cleanup_(json_variant_unrefp) JsonVariant *previous = NULL; + for (size_t i = 0; i < config.n_entries; i++) { + if (previous) { + r = varlink_notifyb(link, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_VARIANT("entry", previous))); + if (r < 0) + return r; + + previous = json_variant_unref(previous); + } + + r = boot_entry_to_json(&config, i, &previous); + if (r < 0) + return r; + } + + return varlink_replyb(link, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(previous, "entry", JSON_BUILD_VARIANT(previous)))); +} diff --git a/src/boot/bootctl-status.h b/src/boot/bootctl-status.h index f7998a3303..6fd436513b 100644 --- a/src/boot/bootctl-status.h +++ b/src/boot/bootctl-status.h @@ -1,5 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "varlink.h" + int verb_status(int argc, char *argv[], void *userdata); int verb_list(int argc, char *argv[], void *userdata); int verb_unlink(int argc, char *argv[], void *userdata); + +int vl_method_list_boot_entries(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index f608e8cc8e..bd10c08b82 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -22,6 +22,8 @@ #include "parse-argument.h" #include "pretty-print.h" #include "utf8.h" +#include "varlink.h" +#include "varlink-io.systemd.BootControl.h" #include "verbs.h" #include "virt.h" @@ -53,6 +55,7 @@ InstallSource arg_install_source = ARG_INSTALL_SOURCE_AUTO; char *arg_efi_boot_option_description = NULL; bool arg_dry_run = false; ImagePolicy *arg_image_policy = NULL; +bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); @@ -418,6 +421,14 @@ static int parse_argv(int argc, char *argv[]) { if (arg_dry_run && argv[optind] && !STR_IN_SET(argv[optind], "unlink", "cleanup")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--dry is only supported with --unlink or --cleanup"); + r = varlink_invocation(VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r > 0) { + arg_varlink = true; + arg_pager_flags |= PAGER_DISABLE; + } + return 1; } @@ -462,6 +473,34 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_varlink) { + _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL; + + /* Invocation as Varlink service */ + + r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_BootControl); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = varlink_server_bind_method_many( + 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); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return EXIT_SUCCESS; + } + if (arg_print_root_device > 0) { _cleanup_free_ char *path = NULL; dev_t devno; diff --git a/src/boot/bootctl.h b/src/boot/bootctl.h index e395b3324a..25cb5166ce 100644 --- a/src/boot/bootctl.h +++ b/src/boot/bootctl.h @@ -36,6 +36,7 @@ extern InstallSource arg_install_source; extern char *arg_efi_boot_option_description; extern bool arg_dry_run; extern ImagePolicy *arg_image_policy; +extern bool arg_varlink; static inline const char *arg_dollar_boot_path(void) { /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ diff --git a/src/shared/meson.build b/src/shared/meson.build index 81de6708f0..fe0c9c1f2f 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -174,6 +174,7 @@ shared_sources = files( 'varlink.c', 'varlink-idl.c', 'varlink-io.systemd.c', + 'varlink-io.systemd.BootControl.c', 'varlink-io.systemd.Credentials.c', 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Journal.c', diff --git a/src/shared/varlink-io.systemd.BootControl.c b/src/shared/varlink-io.systemd.BootControl.c new file mode 100644 index 0000000000..500e07243c --- /dev/null +++ b/src/shared/varlink-io.systemd.BootControl.c @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.BootControl.h" + +static VARLINK_DEFINE_ENUM_TYPE( + BootEntryType, + VARLINK_DEFINE_ENUM_VALUE(type1), + VARLINK_DEFINE_ENUM_VALUE(type2), + VARLINK_DEFINE_ENUM_VALUE(loader), + VARLINK_DEFINE_ENUM_VALUE(auto)); + +static VARLINK_DEFINE_STRUCT_TYPE( + BootEntry, + VARLINK_DEFINE_FIELD_BY_TYPE(type, BootEntryType, 0), + VARLINK_DEFINE_FIELD(id, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(path, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(root, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(title, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(showTitle, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(sortKey, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(version, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(machineId, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(architecture, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(options, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(linux, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(efi, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(initrd, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(devicetree, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(devicetreeOverlay, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(isReported, VARLINK_BOOL, 0), + VARLINK_DEFINE_FIELD(triesLeft, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(triesDone, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(isDefault, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(isSelected, VARLINK_BOOL, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + ListBootEntries, + VARLINK_DEFINE_OUTPUT_BY_TYPE(entry, BootEntry, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + SetRebootToFirmware, + VARLINK_DEFINE_INPUT(state, VARLINK_BOOL, 0)); + +static VARLINK_DEFINE_METHOD( + GetRebootToFirmware, + VARLINK_DEFINE_OUTPUT(state, VARLINK_BOOL, 0)); + +static VARLINK_DEFINE_ERROR( + RebootToFirmwareNotSupported); + +VARLINK_DEFINE_INTERFACE( + io_systemd_BootControl, + "io.systemd.BootControl", + &vl_type_BootEntryType, + &vl_type_BootEntry, + &vl_method_ListBootEntries, + &vl_method_SetRebootToFirmware, + &vl_method_GetRebootToFirmware, + &vl_error_RebootToFirmwareNotSupported); diff --git a/src/shared/varlink-io.systemd.BootControl.h b/src/shared/varlink-io.systemd.BootControl.h new file mode 100644 index 0000000000..fa72b703d1 --- /dev/null +++ b/src/shared/varlink-io.systemd.BootControl.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_BootControl; diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index e5708b73b5..d80fd70529 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -8,6 +8,7 @@ #include "varlink.h" #include "varlink-idl.h" #include "varlink-io.systemd.h" +#include "varlink-io.systemd.BootControl.h" #include "varlink-io.systemd.Credentials.h" #include "varlink-io.systemd.Journal.h" #include "varlink-io.systemd.ManagedOOM.h" @@ -152,6 +153,8 @@ TEST(parse_format) { print_separator(); test_parse_format_one(&vl_interface_io_systemd_Credentials); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_BootControl); + print_separator(); test_parse_format_one(&vl_interface_xyz_test); } diff --git a/test/units/testsuite-74.bootctl.sh b/test/units/testsuite-74.bootctl.sh index 61373b506e..0c52b19203 100755 --- a/test/units/testsuite-74.bootctl.sh +++ b/test/units/testsuite-74.bootctl.sh @@ -258,4 +258,13 @@ EOF SYSTEMD_RELAX_ESP_CHECKS=yes SYSTEMD_RELAX_XBOOTLDR_CHECKS=yes basic_tests --root "${IMAGE_DIR}/root" } +testcase_bootctl_varlink() { + varlinkctl call --collect /run/systemd/io.systemd.BootControl io.systemd.BootControl.ListBootEntries '{}' + + # We have no UEFI in the test environment, hence just check that this fails cleanly + ( SYSTEMD_LOG_TARGET=console varlinkctl call --json=short /run/systemd/io.systemd.BootControl io.systemd.BootControl.GetRebootToFirmware '{}' 2>&1 || true ) | grep -q io.systemd.BootControl.RebootToFirmwareNotSupported + ( SYSTEMD_LOG_TARGET=console varlinkctl call --json=short /run/systemd/io.systemd.BootControl io.systemd.BootControl.SetRebootToFirmware '{"state":true}' 2>&1 || true ) | grep -q io.systemd.BootControl.RebootToFirmwareNotSupported + ( SYSTEMD_LOG_TARGET=console varlinkctl call --json=short /run/systemd/io.systemd.BootControl io.systemd.BootControl.SetRebootToFirmware '{"state":false}' 2>&1 || true ) | grep -q io.systemd.BootControl.RebootToFirmwareNotSupported +} + run_testcases diff --git a/units/meson.build b/units/meson.build index 0c971ef0bc..936ebf7837 100644 --- a/units/meson.build +++ b/units/meson.build @@ -267,6 +267,15 @@ units = [ 'file' : 'systemd-boot-update.service', 'conditions' : ['ENABLE_BOOTLOADER'], }, + { + 'file' : 'systemd-bootctl@.service.in', + 'conditions' : ['ENABLE_BOOTLOADER'], + }, + { + 'file' : 'systemd-bootctl.socket', + 'conditions' : ['ENABLE_BOOTLOADER'], + 'symlinks' : ['sockets.target.wants/'], + }, { 'file' : 'systemd-confext.service', 'conditions' : ['ENABLE_SYSEXT'], diff --git a/units/systemd-bootctl.socket b/units/systemd-bootctl.socket new file mode 100644 index 0000000000..2b26d7edac --- /dev/null +++ b/units/systemd-bootctl.socket @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Boot Control (Varlink) +Documentation=man:bootctl(1) +DefaultDependencies=no +After=local-fs.target +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/io.systemd.BootControl +FileDescriptorName=varlink +SocketMode=0600 +Accept=yes diff --git a/units/systemd-bootctl@.service.in b/units/systemd-bootctl@.service.in new file mode 100644 index 0000000000..d1c3deddfd --- /dev/null +++ b/units/systemd-bootctl@.service.in @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Boot Control (Varlink) +Documentation=man:bootctl(1) +DefaultDependencies=no +Conflicts=shutdown.target +After=local-fs.target +Before=shutdown.target + +[Service] +Environment=LISTEN_FDNAMES=varlink +ExecStart={{BINDIR}}/bootctl