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