logind: add io.systemd.Shutdown varlink Shutdown interface

The shutdown interface is currently only exposed via dbus. This commit
adds a comparable varlink implementation. It is inspired by the existing
dbus methods and implements PowerOff, Reboot, Halt, Kexec, SoftReboot.

It is (intentional) simpler than the dbus for now, i.e. strictly root
only. To match dbus we will need to the functionality
of verify_shutdown_creds() which is dbus-ish right now and would
need some refactor.

For the same reason it does not do the Can* methods - we will need
the verify_shutdown_creds() equivalent first.
This commit is contained in:
Michael Vogt
2026-04-07 20:38:40 +02:00
parent c2283986f9
commit d1d72563e0
6 changed files with 183 additions and 3 deletions
+9
View File
@@ -84,6 +84,15 @@
<citerefentry><refentrytitle>org.freedesktop.LogControl1</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for information about the D-Bus APIs <filename>systemd-logind</filename> provides.</para>
<para>In addition to the D-Bus interface, <filename>systemd-logind</filename> also provides a Varlink
interface <constant>io.systemd.Shutdown</constant> for shutting down or rebooting the system. It
supports <function>PowerOff</function>, <function>Reboot</function>, <function>Halt</function>,
<function>KExec</function>, and <function>SoftReboot</function> methods. Each method accepts an
optional <varname>skipInhibitors</varname> boolean parameter to bypass active block inhibitors
(matching the <constant>SD_LOGIND_SKIP_INHIBITORS</constant> flag of the D-Bus interface). The
interface can be queried with
<command>varlinkctl introspect /run/systemd/io.systemd.Login io.systemd.Shutdown</command>.</para>
<para>For more information see
<ulink url="https://systemd.io/INHIBITOR_LOCKS">Inhibitor Locks</ulink>.</para>
+114 -2
View File
@@ -4,15 +4,19 @@
#include "sd-event.h"
#include "alloc-util.h"
#include "bus-error.h"
#include "bus-polkit.h"
#include "cgroup-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "hashmap.h"
#include "json-util.h"
#include "logind-session.h"
#include "login-util.h"
#include "logind.h"
#include "logind-dbus.h"
#include "logind-seat.h"
#include "logind-session.h"
#include "logind-shutdown.h"
#include "logind-user.h"
#include "logind-varlink.h"
#include "strv.h"
@@ -20,6 +24,7 @@
#include "user-record.h"
#include "user-util.h"
#include "varlink-io.systemd.Login.h"
#include "varlink-io.systemd.Shutdown.h"
#include "varlink-io.systemd.service.h"
#include "varlink-util.h"
@@ -336,6 +341,107 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete
return sd_varlink_reply(link, NULL);
}
static int setup_wall_message_timer(Manager *m, sd_varlink *link) {
uid_t uid = UID_INVALID;
int r;
(void) sd_varlink_get_peer_uid(link, &uid);
m->scheduled_shutdown_uid = uid;
_cleanup_free_ char *tty = NULL;
pid_t pid = 0;
r = sd_varlink_get_peer_pid(link, &pid);
if (r >= 0)
(void) get_ctty(pid, /* ret_devnr= */ NULL, &tty);
r = free_and_strdup_warn(&m->scheduled_shutdown_tty, tty);
if (r < 0)
return log_oom();
return manager_setup_wall_message_timer(m);
}
static int manager_do_shutdown_action(sd_varlink *link, sd_json_variant *parameters, HandleAction action) {
Manager *m = ASSERT_PTR(sd_varlink_get_userdata(link));
int skip_inhibitors = -1;
uid_t uid;
int r;
static const sd_json_dispatch_field dispatch_table[] = {
{ "skipInhibitors", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, 0, 0 },
{}
};
r = sd_varlink_dispatch(link, parameters, dispatch_table, &skip_inhibitors);
if (r != 0)
return r;
uint64_t flags = skip_inhibitors > 0 ? SD_LOGIND_SKIP_INHIBITORS : 0;
const HandleActionData *a = handle_action_lookup(action);
assert(a);
/* TODO: provide full polkit support (matching the D-Bus verify_shutdown_creds() with
* multiple-sessions and inhibitor-override checks). This requires some refactor. */
r = varlink_check_privileged_peer(link);
if (r < 0)
return r;
r = sd_varlink_get_peer_uid(link, &uid);
if (r < 0)
return r;
/* Check for active inhibitors, mirroring the D-Bus verify_shutdown_creds() logic,
* we need this to ensure we handle the strong INHIBIT_BLOCK
* TODO: drop once we do the verify_shutdown_creds() */
if (manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, /* ret_offending= */ NULL) &&
!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS))
return sd_varlink_error(link, "io.systemd.Shutdown.BlockedByInhibitor", /* parameters= */ NULL);
if (m->delayed_action)
return sd_varlink_error(link, "io.systemd.Shutdown.AlreadyInProgress", /* parameters= */ NULL);
/* Reset in case we're short-circuiting a scheduled shutdown */
m->unlink_nologin = false;
manager_reset_scheduled_shutdown(m);
m->scheduled_shutdown_timeout = 0;
m->scheduled_shutdown_action = action;
(void) setup_wall_message_timer(m, link);
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = bus_manager_shutdown_or_sleep_now_or_later(m, a, &error);
if (r < 0) {
log_warning_errno(r, "Failed to execute %s: %s",
handle_action_to_string(action),
bus_error_message(&error, r));
return sd_varlink_error_errno(link, r);
}
return sd_varlink_reply(link, NULL);
}
static int vl_method_power_off(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
return manager_do_shutdown_action(link, parameters, HANDLE_POWEROFF);
}
static int vl_method_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
return manager_do_shutdown_action(link, parameters, HANDLE_REBOOT);
}
static int vl_method_halt(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
return manager_do_shutdown_action(link, parameters, HANDLE_HALT);
}
static int vl_method_kexec(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
return manager_do_shutdown_action(link, parameters, HANDLE_KEXEC);
}
static int vl_method_soft_reboot(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
return manager_do_shutdown_action(link, parameters, HANDLE_SOFT_REBOOT);
}
int manager_varlink_init(Manager *m, int fd) {
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
_unused_ _cleanup_close_ int fd_close = fd;
@@ -358,14 +464,20 @@ int manager_varlink_init(Manager *m, int fd) {
r = sd_varlink_server_add_interface_many(
s,
&vl_interface_io_systemd_Login,
&vl_interface_io_systemd_Shutdown,
&vl_interface_io_systemd_service);
if (r < 0)
return log_error_errno(r, "Failed to add Login interface to varlink server: %m");
return log_error_errno(r, "Failed to add varlink interfaces: %m");
r = sd_varlink_server_bind_method_many(
s,
"io.systemd.Login.CreateSession", vl_method_create_session,
"io.systemd.Login.ReleaseSession", vl_method_release_session,
"io.systemd.Shutdown.PowerOff", vl_method_power_off,
"io.systemd.Shutdown.Reboot", vl_method_reboot,
"io.systemd.Shutdown.Halt", vl_method_halt,
"io.systemd.Shutdown.KExec", vl_method_kexec,
"io.systemd.Shutdown.SoftReboot", vl_method_soft_reboot,
"io.systemd.service.Ping", varlink_method_ping,
"io.systemd.service.SetLogLevel", varlink_method_set_log_level,
"io.systemd.service.GetEnvironment", varlink_method_get_environment);
+1
View File
@@ -230,6 +230,7 @@ shared_sources = files(
'varlink-io.systemd.Resolve.c',
'varlink-io.systemd.Resolve.Hook.c',
'varlink-io.systemd.Resolve.Monitor.c',
'varlink-io.systemd.Shutdown.c',
'varlink-io.systemd.Udev.c',
'varlink-io.systemd.Unit.c',
'varlink-io.systemd.UserDatabase.c',
+52
View File
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "bus-polkit.h"
#include "varlink-io.systemd.Shutdown.h"
static SD_VARLINK_DEFINE_METHOD(
PowerOff,
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"),
SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD(
Reboot,
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"),
SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD(
Halt,
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"),
SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD(
KExec,
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"),
SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD(
SoftReboot,
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("Skip active inhibitors and force the operation"),
SD_VARLINK_DEFINE_INPUT(skipInhibitors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_ERROR(AlreadyInProgress);
static SD_VARLINK_DEFINE_ERROR(BlockedByInhibitor);
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_Shutdown,
"io.systemd.Shutdown",
SD_VARLINK_INTERFACE_COMMENT("APIs for shutting down or rebooting the system."),
SD_VARLINK_SYMBOL_COMMENT("Power off the system"),
&vl_method_PowerOff,
SD_VARLINK_SYMBOL_COMMENT("Reboot the system"),
&vl_method_Reboot,
SD_VARLINK_SYMBOL_COMMENT("Halt the system"),
&vl_method_Halt,
SD_VARLINK_SYMBOL_COMMENT("Reboot the system via kexec"),
&vl_method_KExec,
SD_VARLINK_SYMBOL_COMMENT("Reboot userspace only"),
&vl_method_SoftReboot,
SD_VARLINK_SYMBOL_COMMENT("Another shutdown or sleep operation is already in progress"),
&vl_error_AlreadyInProgress,
SD_VARLINK_SYMBOL_COMMENT("Operation denied due to active block inhibitor"),
&vl_error_BlockedByInhibitor);
+6
View File
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-varlink-idl.h"
extern const sd_varlink_interface vl_interface_io_systemd_Shutdown;
+1 -1
View File
@@ -13,7 +13,7 @@ Documentation=man:systemd-logind.service(8)
[Socket]
ListenStream=/run/systemd/io.systemd.Login
Symlinks=/run/varlink/registry/io.systemd.Login
Symlinks=/run/varlink/registry/io.systemd.Login /run/varlink/registry/io.systemd.Shutdown /run/systemd/io.systemd.Shutdown
FileDescriptorName=varlink
SocketMode=0666
Service=systemd-logind.service