logind: add io.systemd.Shutdown varlink interface (#41229)

The shutdown interface is currently only exposed via dbus. This PR
adds a comparable varlink implementation. It is inspired by the existing
dbus methods and implements PowerOff, Reboot, Halt, Kexec, SoftReboot
as varlink methods on a new io.systemd.Shutdown interface.

It is (intentional) simpler than the dbus for now, i.e. no Can* yet,
mostly
because I want to get feedback first (happy to do that in a followup). 

The only real difficulty here is what to do about
verify_shutdown_creds()
as this is needed by both dbus and varlink and its dbus only. I went for
an ugly but (hopefully) pragmatic choice (see the commit message for
details). But I can totally understand if a refactor instead is
preferred.
This commit is contained in:
Lennart Poettering
2026-04-10 12:29:32 +02:00
committed by GitHub
10 changed files with 468 additions and 173 deletions

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>

View File

@@ -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;

250
src/login/logind-shutdown.c Normal file
View File

@@ -0,0 +1,250 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <unistd.h>
#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");
}

View File

@@ -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);

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,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);

View File

@@ -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',

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',

View File

@@ -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);

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;

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