]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #31286 from poettering/bootctl-varlink
authorLuca Boccassi <bluca@debian.org>
Wed, 14 Feb 2024 18:59:33 +0000 (18:59 +0000)
committerGitHub <noreply@github.com>
Wed, 14 Feb 2024 18:59:33 +0000 (18:59 +0000)
bootctl: add simple varlink IPC interface

17 files changed:
src/boot/bootctl-reboot-to-firmware.c
src/boot/bootctl-reboot-to-firmware.h
src/boot/bootctl-status.c
src/boot/bootctl-status.h
src/boot/bootctl.c
src/boot/bootctl.h
src/shared/bootspec.c
src/shared/bootspec.h
src/shared/efi-loader.c
src/shared/meson.build
src/shared/varlink-io.systemd.BootControl.c [new file with mode: 0644]
src/shared/varlink-io.systemd.BootControl.h [new file with mode: 0644]
src/test/test-varlink-idl.c
test/units/testsuite-74.bootctl.sh
units/meson.build
units/systemd-bootctl.socket [new file with mode: 0644]
units/systemd-bootctl@.service.in [new file with mode: 0644]

index 91f259768c00e0e888d01164a509fe2ba0252798..cdb04f804505ced28f0d01be5805222377229d54 100644 (file)
@@ -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)));
+}
index 0ca4b2c3a30ded150db86636e81f0fd24898c8ce..fb8a2485b33c65ae5eac01b4e3726b20f9d75ad0 100644 (file)
@@ -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);
index f8b57c1f9d474fbf5ae27f8e21b5d336501891f8..58b6276dd914dc0e438949a52c1578969871062c 100644 (file)
@@ -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))));
+}
index f7998a3303eb5cec2980664764d0606e5256f58f..6fd436513ba04ef76e872cdd27c633356a6603bc 100644 (file)
@@ -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);
index f608e8cc8e0a917dc31e90b388c1e25cf9e7d995..bd10c08b82eaa1aad093996c4a42aeeab14d99ba 100644 (file)
@@ -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;
index e395b3324ad803d1b1a4c010965ab19a9962ec03..25cb5166ce75244d5618c71eda2d70488a4db0f4 100644 (file)
@@ -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 */
index b0bf94c7daefcd4e8cc15956d6c514ea9b899cd9..df45c670007c24f27a05ec415ea97c13f57850ea 100644 (file)
@@ -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(
index 8289325b9e313dd1cab7152206a0c525c577539e..9ae88d78aae532f9d4687bd5a9e9b702acad4a3a 100644 (file)
@@ -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);
index 758aaa13c169ac09ae037ae7114d73aeca5c0ef1..7d6bda924a9a0479b46849dc7121cb12a1c7525a 100644 (file)
@@ -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;
 
index 81de6708f01d6f3668fa1fd320d4c8ca96bf8166..fe0c9c1f2fa203fadd2b60a00fb44393c1bf993f 100644 (file)
@@ -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 (file)
index 0000000..500e072
--- /dev/null
@@ -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 (file)
index 0000000..fa72b70
--- /dev/null
@@ -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;
index e5708b73b546a13352b11129db1aba19d381bb92..d80fd705293cb7744ed5989e579e3185a5dc4cb3 100644 (file)
@@ -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);
 }
 
index 4be7bfd0b8f380df923c7e45638c33321bf74088..133006e40a45895b0d4af12bf15154536bcc578e 100755 (executable)
@@ -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
index 0c971ef0bc4b5c8d004c7bee7b7a63b2c667c5be..936ebf783745ea97d4ab6fe274131b1ef47d47a1 100644 (file)
@@ -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 (file)
index 0000000..2b26d7e
--- /dev/null
@@ -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 (file)
index 0000000..d1c3ded
--- /dev/null
@@ -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