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/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); 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; 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 4be7bfd0b8..133006e40a 100755 --- a/test/units/testsuite-74.bootctl.sh +++ b/test/units/testsuite-74.bootctl.sh @@ -263,4 +263,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