diff --git a/man/systemd-logind.service.xml b/man/systemd-logind.service.xml
index 34f6330bf7c..30d9dab14e8 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-dbus.c b/src/login/logind-dbus.c
index 14ec1f90ab4..98c651896d2 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -45,6 +45,7 @@
#include "logind-seat.h"
#include "logind-seat-dbus.h"
#include "logind-session-dbus.h"
+#include "logind-shutdown.h"
#include "logind-user.h"
#include "logind-user-dbus.h"
#include "logind-utmp.h"
@@ -76,10 +77,6 @@
*/
#define WALL_MESSAGE_MAX 4096U
-#define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled"
-
-static void reset_scheduled_shutdown(Manager *m);
-
static int get_sender_session(
Manager *m,
sd_bus_message *message,
@@ -1866,24 +1863,6 @@ static int method_flush_devices(sd_bus_message *message, void *userdata, sd_bus_
return sd_bus_reply_method_return(message, NULL);
}
-static int have_multiple_sessions(
- Manager *m,
- uid_t uid) {
-
- Session *session;
-
- assert(m);
-
- /* Check for other users' sessions. Greeter sessions do not
- * count, and non-login sessions do not count either. */
- HASHMAP_FOREACH(session, m->sessions)
- if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) &&
- session->user->user_record->uid != uid)
- return true;
-
- return false;
-}
-
static int bus_manager_log_shutdown(
Manager *m,
const HandleActionData *a) {
@@ -2189,121 +2168,6 @@ int bus_manager_shutdown_or_sleep_now_or_later(
return r;
}
-static int verify_shutdown_creds(
- Manager *m,
- sd_bus_message *message,
- const HandleActionData *a,
- uint64_t flags,
- sd_bus_error *error) {
-
- _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
- bool multiple_sessions, blocked, interactive;
- _unused_ bool error_or_denial = false;
- Inhibitor *offending = NULL;
- uid_t uid;
- int r;
-
- assert(m);
- assert(a);
- assert(message);
-
- r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
- if (r < 0)
- return r;
-
- r = sd_bus_creds_get_euid(creds, &uid);
- if (r < 0)
- return r;
-
- r = have_multiple_sessions(m, uid);
- if (r < 0)
- return r;
-
- multiple_sessions = r > 0;
- blocked = manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, &offending);
- interactive = flags & SD_LOGIND_INTERACTIVE;
-
- if (multiple_sessions) {
- r = bus_verify_polkit_async_full(
- message,
- a->polkit_action_multiple_sessions,
- /* details= */ NULL,
- /* good_user= */ UID_INVALID,
- interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
- &m->polkit_registry,
- error);
- if (r < 0) {
- /* If we get -EBUSY, it means a polkit decision was made, but not for
- * this action in particular. Assuming we are blocked on inhibitors,
- * ignore that error and allow the decision to be revealed below. */
- if (blocked && r == -EBUSY)
- error_or_denial = true;
- else
- return r;
- }
- if (r == 0)
- return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
- }
-
- if (blocked) {
- PolkitFlags polkit_flags = 0;
-
- /* With a strong inhibitor, if the skip flag is not set, reject outright.
- * With a weak inhibitor, if root is asking and the root flag is set, reject outright.
- * All else, check polkit first. */
- if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) &&
- (offending->mode != INHIBIT_BLOCK_WEAK ||
- (uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS))))
- return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK,
- "Operation denied due to active block inhibitor");
-
- /* We want to always ask here, even for root, to only allow bypassing if explicitly allowed
- * by polkit, unless a weak blocker is used, in which case it will be authorized. */
- if (offending->mode != INHIBIT_BLOCK_WEAK)
- polkit_flags |= POLKIT_ALWAYS_QUERY;
-
- if (interactive)
- polkit_flags |= POLKIT_ALLOW_INTERACTIVE;
-
- r = bus_verify_polkit_async_full(
- message,
- a->polkit_action_ignore_inhibit,
- /* details= */ NULL,
- /* good_user= */ UID_INVALID,
- polkit_flags,
- &m->polkit_registry,
- error);
- if (r < 0)
- return r;
- if (r == 0)
- return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
- }
-
- if (!multiple_sessions && !blocked) {
- r = bus_verify_polkit_async_full(
- message,
- a->polkit_action,
- /* details= */ NULL,
- /* good_user= */ UID_INVALID,
- interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
- &m->polkit_registry,
- error);
- if (r < 0)
- return r;
- if (r == 0)
- return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
- }
-
- /* If error_or_denial was set above, it means that a polkit denial or
- * error was deferred for a future call to bus_verify_polkit_async_full()
- * to catch. In any case, it also means that the payload guarded by
- * these polkit calls should never be executed, and hence we should
- * never reach this point. */
- assert(!error_or_denial);
-
- return 0;
-}
-
static int setup_wall_message_timer(Manager *m, sd_bus_message* message) {
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
int r;
@@ -2442,10 +2306,17 @@ static int method_do_shutdown_or_sleep(
} else if (!a)
assert_se(a = handle_action_lookup(action));
- r = verify_shutdown_creds(m, message, a, flags, error);
+ r = manager_verify_shutdown_creds(m, message, /* link= */ NULL, a, flags, error);
if (r != 0)
return r;
+ {
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+
+ (void) bus_query_sender_pidref(message, &pidref);
+ log_shutdown_caller(&pidref, handle_action_to_string(a->handle));
+ }
+
if (m->delayed_action)
return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS,
"Action %s already in progress, refusing requested %s operation.",
@@ -2454,7 +2325,7 @@ static int method_do_shutdown_or_sleep(
/* reset case we're shorting a scheduled shutdown */
m->unlink_nologin = false;
- reset_scheduled_shutdown(m);
+ manager_reset_scheduled_shutdown(m);
m->scheduled_shutdown_timeout = 0;
m->scheduled_shutdown_action = action;
@@ -2568,29 +2439,6 @@ static usec_t nologin_timeout_usec(usec_t elapse) {
return LESS_BY(elapse, 5 * USEC_PER_MINUTE);
}
-static void reset_scheduled_shutdown(Manager *m) {
- assert(m);
-
- m->scheduled_shutdown_timeout_source = sd_event_source_disable_unref(m->scheduled_shutdown_timeout_source);
- m->wall_message_timeout_source = sd_event_source_disable_unref(m->wall_message_timeout_source);
- m->nologin_timeout_source = sd_event_source_disable_unref(m->nologin_timeout_source);
-
- m->scheduled_shutdown_action = _HANDLE_ACTION_INVALID;
- m->scheduled_shutdown_timeout = USEC_INFINITY;
- m->scheduled_shutdown_uid = UID_INVALID;
- m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty);
- m->shutdown_dry_run = false;
-
- if (m->unlink_nologin) {
- (void) unlink_or_warn("/run/nologin");
- m->unlink_nologin = false;
- }
-
- (void) unlink(SHUTDOWN_SCHEDULE_FILE);
-
- manager_send_changed(m, "ScheduledShutdown");
-}
-
static int update_schedule_file(Manager *m) {
_cleanup_(unlink_and_freep) char *temp_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
@@ -2669,7 +2517,7 @@ static int manager_scheduled_shutdown_handler(
bus_manager_log_shutdown(m, a);
log_info("Running in dry run, suppressing action.");
- reset_scheduled_shutdown(m);
+ manager_reset_scheduled_shutdown(m);
return 0;
}
@@ -2683,7 +2531,7 @@ static int manager_scheduled_shutdown_handler(
return 0;
error:
- reset_scheduled_shutdown(m);
+ manager_reset_scheduled_shutdown(m);
return r;
}
@@ -2738,7 +2586,7 @@ void manager_load_scheduled_shutdown(Manager *m) {
"TTY", &tty);
/* reset will delete the file */
- reset_scheduled_shutdown(m);
+ manager_reset_scheduled_shutdown(m);
if (r == -ENOENT)
return;
@@ -2784,7 +2632,7 @@ void manager_load_scheduled_shutdown(Manager *m) {
r = manager_setup_shutdown_timers(m);
if (r < 0)
- return reset_scheduled_shutdown(m);
+ return manager_reset_scheduled_shutdown(m);
(void) manager_setup_wall_message_timer(m);
(void) update_schedule_file(m);
@@ -2819,7 +2667,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
assert_se(a = handle_action_lookup(handle));
assert(a->polkit_action);
- r = verify_shutdown_creds(m, message, a, 0, error);
+ r = manager_verify_shutdown_creds(m, message, /* link= */ NULL, a, 0, error);
if (r != 0)
return r;
@@ -2853,7 +2701,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
r = update_schedule_file(m);
if (r < 0) {
- reset_scheduled_shutdown(m);
+ manager_reset_scheduled_shutdown(m);
return r;
}
@@ -2913,7 +2761,7 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd
}
cancel_delayed_action(m);
- reset_scheduled_shutdown(m);
+ manager_reset_scheduled_shutdown(m);
return sd_bus_reply_method_return(message, "b", true);
}
@@ -2969,7 +2817,7 @@ static int method_can_shutdown_or_sleep(
if (r < 0)
return r;
- r = have_multiple_sessions(m, uid);
+ r = manager_have_multiple_sessions(m, uid);
if (r < 0)
return r;
diff --git a/src/login/logind-shutdown.c b/src/login/logind-shutdown.c
new file mode 100644
index 00000000000..064ebf8e2ff
--- /dev/null
+++ b/src/login/logind-shutdown.c
@@ -0,0 +1,250 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#include "sd-bus.h"
+#include "sd-event.h"
+#include "sd-varlink.h"
+
+#include "bus-common-errors.h"
+#include "bus-polkit.h"
+#include "cgroup-util.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "hashmap.h"
+#include "log.h"
+#include "login-util.h"
+#include "logind.h"
+#include "logind-dbus.h"
+#include "logind-inhibit.h"
+#include "logind-session.h"
+#include "logind-shutdown.h"
+#include "logind-user.h"
+#include "pidref.h"
+#include "process-util.h"
+#include "user-record.h"
+
+int manager_have_multiple_sessions(
+ Manager *m,
+ uid_t uid) {
+
+ Session *session;
+
+ assert(m);
+
+ /* Check for other users' sessions. Greeter sessions do not
+ * count, and non-login sessions do not count either. */
+ HASHMAP_FOREACH(session, m->sessions)
+ if (SESSION_CLASS_IS_INHIBITOR_LIKE(session->class) &&
+ session->user->user_record->uid != uid)
+ return true;
+
+ return false;
+}
+
+void log_shutdown_caller(const PidRef *caller, const char *method) {
+ _cleanup_free_ char *comm = NULL, *unit = NULL;
+
+ assert(method);
+
+ if (!pidref_is_set(caller)) {
+ return log_notice("%s requested from unknown client PID...", method);
+ }
+
+ (void) pidref_get_comm(caller, &comm);
+ (void) cg_pidref_get_unit(caller, &unit);
+
+ log_notice("%s requested from client PID " PID_FMT "%s%s%s%s%s%s...",
+ method, caller->pid,
+ comm ? " ('" : "", strempty(comm), comm ? "')" : "",
+ unit ? " (unit " : "", strempty(unit), unit ? ")" : "");
+}
+
+int manager_verify_shutdown_creds(
+ Manager *m,
+ sd_bus_message *message,
+ sd_varlink *link,
+ const HandleActionData *a,
+ uint64_t flags,
+ sd_bus_error *error) {
+
+ bool multiple_sessions, blocked, interactive;
+ _unused_ bool error_or_denial = false;
+ Inhibitor *offending = NULL;
+ uid_t uid;
+ int r;
+
+ assert(m);
+ assert(a);
+ assert(!!message != !!link); /* exactly one transport */
+ assert(!link || !error); /* varlink doesn't use sd_bus_error */
+
+ if (message) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+
+ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_creds_get_euid(creds, &uid);
+ if (r < 0)
+ return r;
+ } else {
+ r = sd_varlink_get_peer_uid(link, &uid);
+ if (r < 0)
+ return r;
+ }
+
+ r = manager_have_multiple_sessions(m, uid);
+ if (r < 0)
+ return r;
+
+ multiple_sessions = r > 0;
+ blocked = manager_is_inhibited(m, a->inhibit_what, NULL, /* flags= */ 0, uid, &offending);
+ interactive = flags & SD_LOGIND_INTERACTIVE;
+
+ if (multiple_sessions) {
+ if (message)
+ r = bus_verify_polkit_async_full(
+ message,
+ a->polkit_action_multiple_sessions,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
+ &m->polkit_registry,
+ error);
+ else
+ r = varlink_verify_polkit_async_full(
+ link,
+ m->bus,
+ a->polkit_action_multiple_sessions,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
+ &m->polkit_registry);
+
+ if (r < 0) {
+ /* If we get -EBUSY, it means a polkit decision was made, but not for
+ * this action in particular. Assuming we are blocked on inhibitors,
+ * ignore that error and allow the decision to be revealed below. */
+ if (blocked && r == -EBUSY)
+ error_or_denial = true;
+ else
+ return r;
+ }
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+ }
+
+ if (blocked) {
+ PolkitFlags polkit_flags = 0;
+
+ /* With a strong inhibitor, if the skip flag is not set, reject outright.
+ * With a weak inhibitor, if root is asking and the root flag is set, reject outright.
+ * All else, check polkit first. */
+ if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) &&
+ (offending->mode != INHIBIT_BLOCK_WEAK ||
+ (uid == 0 && FLAGS_SET(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS)))) {
+ if (link)
+ return sd_varlink_errorbo(
+ link,
+ "io.systemd.Shutdown.BlockedByInhibitor",
+ SD_JSON_BUILD_PAIR_STRING("who", offending->who),
+ SD_JSON_BUILD_PAIR_STRING("why", offending->why));
+ if (error)
+ return sd_bus_error_set(error, BUS_ERROR_BLOCKED_BY_INHIBITOR_LOCK,
+ "Operation denied due to active block inhibitor");
+ return -EACCES;
+ }
+
+ /* We want to always ask here, even for root, to only allow bypassing if explicitly allowed
+ * by polkit, unless a weak blocker is used, in which case it will be authorized. */
+ if (offending->mode != INHIBIT_BLOCK_WEAK)
+ polkit_flags |= POLKIT_ALWAYS_QUERY;
+
+ if (interactive)
+ polkit_flags |= POLKIT_ALLOW_INTERACTIVE;
+
+ if (message)
+ r = bus_verify_polkit_async_full(
+ message,
+ a->polkit_action_ignore_inhibit,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ polkit_flags,
+ &m->polkit_registry,
+ error);
+ else
+ r = varlink_verify_polkit_async_full(
+ link,
+ m->bus,
+ a->polkit_action_ignore_inhibit,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ polkit_flags,
+ &m->polkit_registry);
+
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+ }
+
+ if (!multiple_sessions && !blocked) {
+ if (message)
+ r = bus_verify_polkit_async_full(
+ message,
+ a->polkit_action,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
+ &m->polkit_registry,
+ error);
+ else
+ r = varlink_verify_polkit_async_full(
+ link,
+ m->bus,
+ a->polkit_action,
+ /* details= */ NULL,
+ /* good_user= */ UID_INVALID,
+ interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
+ &m->polkit_registry);
+
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+ }
+
+ /* If error_or_denial was set above, it means that a polkit denial or
+ * error was deferred for a future call to bus_verify_polkit_async_full()
+ * to catch. In any case, it also means that the payload guarded by
+ * these polkit calls should never be executed, and hence we should
+ * never reach this point. */
+ assert(!error_or_denial);
+
+ return 0;
+}
+
+void manager_reset_scheduled_shutdown(Manager *m) {
+ assert(m);
+
+ m->scheduled_shutdown_timeout_source = sd_event_source_disable_unref(m->scheduled_shutdown_timeout_source);
+ m->wall_message_timeout_source = sd_event_source_disable_unref(m->wall_message_timeout_source);
+ m->nologin_timeout_source = sd_event_source_disable_unref(m->nologin_timeout_source);
+
+ m->scheduled_shutdown_action = _HANDLE_ACTION_INVALID;
+ m->scheduled_shutdown_timeout = USEC_INFINITY;
+ m->scheduled_shutdown_uid = UID_INVALID;
+ m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty);
+ m->shutdown_dry_run = false;
+
+ if (m->unlink_nologin) {
+ (void) unlink_or_warn("/run/nologin");
+ m->unlink_nologin = false;
+ }
+
+ (void) unlink(SHUTDOWN_SCHEDULE_FILE);
+
+ manager_send_changed(m, "ScheduledShutdown");
+}
diff --git a/src/login/logind-shutdown.h b/src/login/logind-shutdown.h
new file mode 100644
index 00000000000..e6bcc8c4f5d
--- /dev/null
+++ b/src/login/logind-shutdown.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "logind-forward.h"
+
+#define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled"
+
+int manager_have_multiple_sessions(Manager *m, uid_t uid);
+
+void log_shutdown_caller(const PidRef *caller, const char *method);
+
+/* manager_verify_shutdown_creds() takes *either* a "message" or "link" depending on if it is used
+ * to validate a D-Bus or Varlink shutdown request. When varlink is used the sd_bus_error *error
+ * must be NULL */
+int manager_verify_shutdown_creds(Manager *m, sd_bus_message *message, sd_varlink *link, const HandleActionData *a, uint64_t flags, sd_bus_error *error);
+
+void manager_reset_scheduled_shutdown(Manager *m);
diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c
index ae2e32d3faf..40dee113c42 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,101 @@ 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;
+ int r;
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "skipInhibitors", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, 0, 0 },
+ VARLINK_DISPATCH_POLKIT_FIELD,
+ {}
+ };
+
+ 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);
+
+ r = manager_verify_shutdown_creds(m, /* message= */ NULL, link, a, flags, /* error= */ NULL);
+ if (r != 0)
+ return r;
+
+ {
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+
+ (void) varlink_get_peer_pidref(link, &pidref);
+ log_shutdown_caller(&pidref, handle_action_to_string(action));
+ }
+
+ 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 +458,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/login/meson.build b/src/login/meson.build
index d6654ff5ced..390960d5f6c 100644
--- a/src/login/meson.build
+++ b/src/login/meson.build
@@ -21,6 +21,7 @@ systemd_logind_extract_sources = files(
'logind-session-dbus.c',
'logind-session-device.c',
'logind-session.c',
+ 'logind-shutdown.c',
'logind-user-dbus.c',
'logind-user.c',
'logind-utmp.c',
diff --git a/src/shared/meson.build b/src/shared/meson.build
index 2c6207d4944..22dccf0e2a7 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 00000000000..55728b7463e
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Shutdown.c
@@ -0,0 +1,57 @@
+/* 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_FIELD_COMMENT("Who is holding the inhibitor"),
+ SD_VARLINK_DEFINE_FIELD(who, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("Why the inhibitor is held"),
+ SD_VARLINK_DEFINE_FIELD(why, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
+
+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 00000000000..e97853b0bc7
--- /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 1d3652e0497..377eac7006f 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