mirror of
https://github.com/systemd/systemd.git
synced 2026-08-08 00:51:19 +00:00
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:
@@ -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>
|
||||
|
||||
|
||||
@@ -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
250
src/login/logind-shutdown.c
Normal 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");
|
||||
}
|
||||
17
src/login/logind-shutdown.h
Normal file
17
src/login/logind-shutdown.h
Normal 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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
57
src/shared/varlink-io.systemd.Shutdown.c
Normal file
57
src/shared/varlink-io.systemd.Shutdown.c
Normal 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);
|
||||
6
src/shared/varlink-io.systemd.Shutdown.h
Normal file
6
src/shared/varlink-io.systemd.Shutdown.h
Normal 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;
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user