From d1d72563e0d90f924c8e789a978ff95196e1b969 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 20 Mar 2026 13:37:59 +0100 Subject: [PATCH] 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. --- man/systemd-logind.service.xml | 9 ++ src/login/logind-varlink.c | 116 ++++++++++++++++++++++- src/shared/meson.build | 1 + src/shared/varlink-io.systemd.Shutdown.c | 52 ++++++++++ src/shared/varlink-io.systemd.Shutdown.h | 6 ++ units/systemd-logind-varlink.socket | 2 +- 6 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/shared/varlink-io.systemd.Shutdown.c create mode 100644 src/shared/varlink-io.systemd.Shutdown.h diff --git a/man/systemd-logind.service.xml b/man/systemd-logind.service.xml index 34f6330bf7..30d9dab14e 100644 --- a/man/systemd-logind.service.xml +++ b/man/systemd-logind.service.xml @@ -84,6 +84,15 @@ org.freedesktop.LogControl15 for information about the D-Bus APIs systemd-logind provides. + In addition to the D-Bus interface, systemd-logind also provides a Varlink + interface io.systemd.Shutdown for shutting down or rebooting the system. It + supports PowerOff, Reboot, Halt, + KExec, and SoftReboot methods. Each method accepts an + optional skipInhibitors boolean parameter to bypass active block inhibitors + (matching the SD_LOGIND_SKIP_INHIBITORS flag of the D-Bus interface). The + interface can be queried with + varlinkctl introspect /run/systemd/io.systemd.Login io.systemd.Shutdown. + For more information see Inhibitor Locks. diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index ae2e32d3fa..a14569cacd 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -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); diff --git a/src/shared/meson.build b/src/shared/meson.build index 2c6207d494..22dccf0e2a 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -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', diff --git a/src/shared/varlink-io.systemd.Shutdown.c b/src/shared/varlink-io.systemd.Shutdown.c new file mode 100644 index 0000000000..96f4fef04d --- /dev/null +++ b/src/shared/varlink-io.systemd.Shutdown.c @@ -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); diff --git a/src/shared/varlink-io.systemd.Shutdown.h b/src/shared/varlink-io.systemd.Shutdown.h new file mode 100644 index 0000000000..e97853b0bc --- /dev/null +++ b/src/shared/varlink-io.systemd.Shutdown.h @@ -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; diff --git a/units/systemd-logind-varlink.socket b/units/systemd-logind-varlink.socket index 1d3652e049..377eac7006 100644 --- a/units/systemd-logind-varlink.socket +++ b/units/systemd-logind-varlink.socket @@ -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