]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
logind: add io.systemd.Shutdown varlink Shutdown interface
authorMichael Vogt <michael@amutable.com>
Fri, 20 Mar 2026 12:37:59 +0000 (13:37 +0100)
committerMichael Vogt <michael@amutable.com>
Tue, 7 Apr 2026 18:38:40 +0000 (20:38 +0200)
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
src/login/logind-varlink.c
src/shared/meson.build
src/shared/varlink-io.systemd.Shutdown.c [new file with mode: 0644]
src/shared/varlink-io.systemd.Shutdown.h [new file with mode: 0644]
units/systemd-logind-varlink.socket

index 34f6330bf7c07884d78f9c6046cdec26a6197a28..30d9dab14e830365f1dfefce010fc16a0c2ad0aa 100644 (file)
     <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>
 
index ae2e32d3fafa132b42e6caa5d25b6312af65c333..a14569cacd7c2fe01b707a6c5976eab2d07e8f18 100644 (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);
index 2c6207d494454b561fcf57971c78ce766b1da624..22dccf0e2a7dab0ccdfa6df044a59c7e6cd1680b 100644 (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',
diff --git a/src/shared/varlink-io.systemd.Shutdown.c b/src/shared/varlink-io.systemd.Shutdown.c
new file mode 100644 (file)
index 0000000..96f4fef
--- /dev/null
@@ -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 (file)
index 0000000..e97853b
--- /dev/null
@@ -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;
index 1d3652e0497421ec5b7bebad53df658112f23fe6..377eac7006fdf57418cbd38f248e9106ef741758 100644 (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