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