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
This commit is contained in:
Lennart Poettering 2024-02-12 17:30:31 +01:00
parent f892954ba2
commit 79ec39958d
14 changed files with 281 additions and 3 deletions

View 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)));
}

View 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);

View 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))));
}

View 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);

View 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;

View 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 */

View 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',

View file

@ -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);

View file

@ -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;

View 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);
}

View file

@ -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

View 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'],

View file

@ -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

View file

@ -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