From 90b1ec03b2ce939f589239133a32f4429f2ad6a6 Mon Sep 17 00:00:00 2001 From: Ludwig Nussel Date: Tue, 14 Dec 2021 15:26:43 +0100 Subject: [PATCH 1/8] logind: fix wall message for immediate shutdowns An elapse time of zero means NOW which should trigger a wall message. --- src/login/logind-dbus.c | 46 ++++++++++++++++++++++++++--------------- src/login/logind-utmp.c | 4 ++-- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index c05c0d02cc..41b48bcab1 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -1854,6 +1854,29 @@ static int verify_shutdown_creds( 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; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_AUGMENT|SD_BUS_CREDS_TTY|SD_BUS_CREDS_UID, &creds); + if (r >= 0) { + const char *tty = NULL; + + (void) sd_bus_creds_get_uid(creds, &m->scheduled_shutdown_uid); + (void) sd_bus_creds_get_tty(creds, &tty); + + r = free_and_strdup(&m->scheduled_shutdown_tty, tty); + if (r < 0) + return log_oom(); + } + + r = manager_setup_wall_message_timer(m); + if (r < 0) + return r; + + return 0; +} + static int method_do_shutdown_or_sleep( Manager *m, sd_bus_message *message, @@ -1922,6 +1945,8 @@ static int method_do_shutdown_or_sleep( if (r != 0) return r; + (void) setup_wall_message_timer(m, message); + r = bus_manager_shutdown_or_sleep_now_or_later(m, unit_name, w, error); if (r < 0) return r; @@ -2189,7 +2214,6 @@ error: static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; const char *action_multiple_sessions = NULL; const char *action_ignore_inhibit = NULL; const char *action = NULL; @@ -2270,23 +2294,11 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ m->scheduled_shutdown_timeout = elapse; - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_AUGMENT|SD_BUS_CREDS_TTY|SD_BUS_CREDS_UID, &creds); - if (r >= 0) { - const char *tty = NULL; - - (void) sd_bus_creds_get_uid(creds, &m->scheduled_shutdown_uid); - (void) sd_bus_creds_get_tty(creds, &tty); - - r = free_and_strdup(&m->scheduled_shutdown_tty, tty); - if (r < 0) { - m->scheduled_shutdown_timeout_source = sd_event_source_unref(m->scheduled_shutdown_timeout_source); - return log_oom(); - } - } - - r = manager_setup_wall_message_timer(m); - if (r < 0) + r = setup_wall_message_timer(m, message); + if (r < 0) { + m->scheduled_shutdown_timeout_source = sd_event_source_unref(m->scheduled_shutdown_timeout_source); return r; + } r = update_schedule_file(m); if (r < 0) diff --git a/src/login/logind-utmp.c b/src/login/logind-utmp.c index 5533836473..d2b2f8b193 100644 --- a/src/login/logind-utmp.c +++ b/src/login/logind-utmp.c @@ -135,11 +135,11 @@ int manager_setup_wall_message_timer(Manager *m) { return 0; } - if (elapse < n) + if (elapse > 0 && elapse < n) return 0; /* Warn immediately if less than 15 minutes are left */ - if (elapse - n < 15 * USEC_PER_MINUTE) { + if (elapse == 0 || elapse - n < 15 * USEC_PER_MINUTE) { r = warn_wall(m, n); if (r == 0) return 0; From adefc8789b63225662e50ceaa282f9553b5c64eb Mon Sep 17 00:00:00 2001 From: Ludwig Nussel Date: Tue, 14 Dec 2021 17:27:05 +0100 Subject: [PATCH 2/8] systemctl: simplify halt_main() The code at this point is not able to tell whether it was called as halt/poweroff/reboot or shutdown with time "now". The code also takes a shortcut to skip logind if called as root. That however means asking shutdown for immediate action won't trigger a wall message. As per https://github.com/systemd/systemd/issues/8424#issuecomment-374677315 all commands should trigger a wall message. That simplifies the code as we can try logind first always. --- src/systemctl/systemctl-compat-halt.c | 47 +++++++++++---------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/systemctl/systemctl-compat-halt.c b/src/systemctl/systemctl-compat-halt.c index 760758322f..7d95d34d54 100644 --- a/src/systemctl/systemctl-compat-halt.c +++ b/src/systemctl/systemctl-compat-halt.c @@ -144,35 +144,23 @@ int halt_parse_argv(int argc, char *argv[]) { int halt_main(void) { int r; - r = logind_check_inhibitors(arg_action); - if (r < 0) - return r; + /* always try logind first */ + if (arg_when > 0) + r = logind_schedule_shutdown(); + else { + r = logind_check_inhibitors(arg_action); + if (r < 0) + return r; - /* Delayed shutdown requested, and was successful */ - if (arg_when > 0 && logind_schedule_shutdown() == 0) - return 0; - - /* No delay, or logind failed or is not at all available */ - if (geteuid() != 0) { - if (arg_dry_run || arg_force > 0) { - (void) must_be_root(); - return -EPERM; - } - - /* Try logind if we are a normal user and no special mode applies. Maybe polkit allows us to - * shutdown the machine. */ - if (IN_SET(arg_action, ACTION_POWEROFF, ACTION_REBOOT, ACTION_KEXEC, ACTION_HALT)) { - r = logind_reboot(arg_action); - if (r >= 0) - return r; - if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) - /* Requested operation is not supported on the local system or already in - * progress */ - return r; - - /* on all other errors, try low-level operation */ - } + r = logind_reboot(arg_action); } + if (r >= 0) + return r; + if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) + /* Requested operation is not supported on the local system or already in + * progress */ + return r; + /* on all other errors, try low-level operation */ /* In order to minimize the difference between operation with and without logind, we explicitly * enable non-blocking mode for this, as logind's shutdown operations are always non-blocking. */ @@ -181,7 +169,10 @@ int halt_main(void) { if (!arg_dry_run && !arg_force) return start_with_fallback(); - assert(geteuid() == 0); + if (geteuid() != 0) { + (void) must_be_root(); + return -EPERM; + } if (!arg_no_wtmp) { if (sd_booted() > 0) From cdf370626f08ed509a5dde9d5618eed29d625032 Mon Sep 17 00:00:00 2001 From: Ludwig Nussel Date: Tue, 14 Dec 2021 15:55:55 +0100 Subject: [PATCH 3/8] logind: enable wall messages by default Something calling directly into the dbus interface to request a shutdown may not bother turning wall messages on explicitly. This has the convenient side effect that no separate polkit auth is required to turn on wall messages. Was annoying as having a wall message is the default behavior of the commandline tools. Now it's the other way around ie eg systemctl reboot --no-wall requires auth to explicitly turn off the wall message. --- src/login/logind.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/login/logind.c b/src/login/logind.c index 52b1d95034..b24f26485a 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -54,6 +54,7 @@ static int manager_new(Manager **ret) { *m = (Manager) { .console_active_fd = -1, .reserve_vt_fd = -1, + .enable_wall_messages = true, .idle_action_not_before_usec = now(CLOCK_MONOTONIC), }; From 030f37c4083b1fba3eb445813e9e1d711563459a Mon Sep 17 00:00:00 2001 From: Ludwig Nussel Date: Thu, 9 Dec 2021 17:26:07 +0100 Subject: [PATCH 4/8] logind: fix wall messages for direct shutdown calls The wall mechanism uses the scheduled_shutdown_type to determine what message to send so it needs to be filled in also for the cases that call for shutdown without schedule. It's really a hackish way. The overall code needs refacturing. --- src/login/logind-dbus.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 41b48bcab1..89285aee5b 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -1957,6 +1957,8 @@ static int method_do_shutdown_or_sleep( static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; + (void)free_and_strdup(&m->scheduled_shutdown_type, "poweroff"); + return method_do_shutdown_or_sleep( m, message, SPECIAL_POWEROFF_TARGET, @@ -1972,6 +1974,8 @@ static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; + (void)free_and_strdup(&m->scheduled_shutdown_type, "reboot"); + return method_do_shutdown_or_sleep( m, message, SPECIAL_REBOOT_TARGET, @@ -1987,6 +1991,8 @@ static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error * static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; + (void)free_and_strdup(&m->scheduled_shutdown_type, "halt"); + return method_do_shutdown_or_sleep( m, message, SPECIAL_HALT_TARGET, @@ -2002,6 +2008,8 @@ static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *er static int method_suspend(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; + (void)free_and_strdup(&m->scheduled_shutdown_type, "suspend"); + return method_do_shutdown_or_sleep( m, message, SPECIAL_SUSPEND_TARGET, @@ -2017,6 +2025,8 @@ static int method_suspend(sd_bus_message *message, void *userdata, sd_bus_error static int method_hibernate(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; + (void)free_and_strdup(&m->scheduled_shutdown_type, "hibernate"); + return method_do_shutdown_or_sleep( m, message, SPECIAL_HIBERNATE_TARGET, @@ -2032,6 +2042,8 @@ static int method_hibernate(sd_bus_message *message, void *userdata, sd_bus_erro static int method_hybrid_sleep(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; + (void)free_and_strdup(&m->scheduled_shutdown_type, "hybrid-sleep"); + return method_do_shutdown_or_sleep( m, message, SPECIAL_HYBRID_SLEEP_TARGET, @@ -2047,6 +2059,8 @@ static int method_hybrid_sleep(sd_bus_message *message, void *userdata, sd_bus_e static int method_suspend_then_hibernate(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; + (void)free_and_strdup(&m->scheduled_shutdown_type, "sleep"); + return method_do_shutdown_or_sleep( m, message, SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET, From ec14fba91c94f38f3d4703fc9b4d7f1316a3922f Mon Sep 17 00:00:00 2001 From: Ludwig Nussel Date: Wed, 15 Dec 2021 16:28:57 +0100 Subject: [PATCH 5/8] logind: require polkit auth for cancelling shutdowns --- src/login/logind-dbus.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 89285aee5b..223f0da205 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -2323,20 +2323,49 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; + const char *action; bool cancelled; + int r; assert(m); assert(message); cancelled = m->scheduled_shutdown_type != NULL; + + if (!cancelled) + goto done; + + // mirrors code in method_schedule_shutdown() + if (streq(m->scheduled_shutdown_type, "poweroff")) { + action = "org.freedesktop.login1.power-off"; + } else if (STR_IN_SET(m->scheduled_shutdown_type, "reboot", "kexec")) { + action = "org.freedesktop.login1.reboot"; + } else if (streq(m->scheduled_shutdown_type, "halt")) { + action = "org.freedesktop.login1.halt"; + } else + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Unsupported shutdown type"); + + r = bus_verify_polkit_async( + message, + CAP_SYS_BOOT, + action, + NULL, + false, + UID_INVALID, + &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 */ + reset_scheduled_shutdown(m); - if (cancelled && m->enable_wall_messages) { + if (m->enable_wall_messages) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; _cleanup_free_ char *username = NULL; const char *tty = NULL; uid_t uid = 0; - int r; r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_AUGMENT|SD_BUS_CREDS_TTY|SD_BUS_CREDS_UID, &creds); if (r >= 0) { @@ -2349,6 +2378,7 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd username, tty, logind_wall_tty_filter, m); } +done: return sd_bus_reply_method_return(message, "b", cancelled); } From 5ed73478e1b1560274038ef30ec6f89022b4d8f6 Mon Sep 17 00:00:00 2001 From: Ludwig Nussel Date: Wed, 15 Dec 2021 18:21:09 +0100 Subject: [PATCH 6/8] logind: refactor Avoid hardcoded strings and string compares related to shutdown actions. Instead put everything into a common structure. Reuse existing HandleAction as index since it's already exposed as property for the button handlers. --- src/login/logind-action.c | 137 +++++++++--- src/login/logind-action.h | 20 ++ src/login/logind-button.c | 3 +- src/login/logind-dbus.c | 458 ++++++++++++++------------------------ src/login/logind-dbus.h | 3 +- src/login/logind-utmp.c | 6 +- src/login/logind.c | 1 - src/login/logind.h | 10 +- 8 files changed, 303 insertions(+), 335 deletions(-) diff --git a/src/login/logind-action.c b/src/login/logind-action.c index e172910948..45f77bc090 100644 --- a/src/login/logind-action.c +++ b/src/login/logind-action.c @@ -2,6 +2,8 @@ #include +#include "sd-messages.h" + #include "alloc-util.h" #include "bus-error.h" #include "bus-util.h" @@ -11,29 +13,119 @@ #include "logind-dbus.h" #include "logind-session-dbus.h" #include "process-util.h" -#include "sleep-config.h" #include "special.h" #include "string-table.h" #include "terminal-util.h" #include "user-util.h" -const char* manager_target_for_action(HandleAction handle) { - static const char * const target_table[_HANDLE_ACTION_MAX] = { - [HANDLE_POWEROFF] = SPECIAL_POWEROFF_TARGET, - [HANDLE_REBOOT] = SPECIAL_REBOOT_TARGET, - [HANDLE_HALT] = SPECIAL_HALT_TARGET, - [HANDLE_KEXEC] = SPECIAL_KEXEC_TARGET, - [HANDLE_SUSPEND] = SPECIAL_SUSPEND_TARGET, - [HANDLE_HIBERNATE] = SPECIAL_HIBERNATE_TARGET, - [HANDLE_HYBRID_SLEEP] = SPECIAL_HYBRID_SLEEP_TARGET, - [HANDLE_SUSPEND_THEN_HIBERNATE] = SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET, - [HANDLE_FACTORY_RESET] = SPECIAL_FACTORY_RESET_TARGET, - }; +static const ActionTableItem action_table[_HANDLE_ACTION_MAX] = { + [HANDLE_POWEROFF] = { + SPECIAL_POWEROFF_TARGET, + INHIBIT_SHUTDOWN, + "org.freedesktop.login1.power-off", + "org.freedesktop.login1.power-off-multiple-sessions", + "org.freedesktop.login1.power-off-ignore-inhibit", + _SLEEP_OPERATION_INVALID, + SD_MESSAGE_SHUTDOWN_STR, + "System is powering down", + "power-off", + }, + [HANDLE_REBOOT] = { + SPECIAL_REBOOT_TARGET, + INHIBIT_SHUTDOWN, + "org.freedesktop.login1.reboot", + "org.freedesktop.login1.reboot-multiple-sessions", + "org.freedesktop.login1.reboot-ignore-inhibit", + _SLEEP_OPERATION_INVALID, + SD_MESSAGE_SHUTDOWN_STR, + "System is rebooting", + "reboot", + }, + [HANDLE_HALT] = { + SPECIAL_HALT_TARGET, + INHIBIT_SHUTDOWN, + "org.freedesktop.login1.halt", + "org.freedesktop.login1.halt-multiple-sessions", + "org.freedesktop.login1.halt-ignore-inhibit", + _SLEEP_OPERATION_INVALID, + SD_MESSAGE_SHUTDOWN_STR, + "System is halting", + "halt", + }, + [HANDLE_KEXEC] = { + SPECIAL_KEXEC_TARGET, + INHIBIT_SHUTDOWN, + "org.freedesktop.login1.reboot", + "org.freedesktop.login1.reboot-multiple-sessions", + "org.freedesktop.login1.reboot-ignore-inhibit", + _SLEEP_OPERATION_INVALID, + SD_MESSAGE_SHUTDOWN_STR, + "System is rebooting with kexec", + "kexec", + }, + [HANDLE_SUSPEND] = { + SPECIAL_SUSPEND_TARGET, + INHIBIT_SLEEP, + "org.freedesktop.login1.suspend", + "org.freedesktop.login1.suspend-multiple-sessions", + "org.freedesktop.login1.suspend-ignore-inhibit", + SLEEP_SUSPEND, + }, + [HANDLE_HIBERNATE] = { + SPECIAL_HIBERNATE_TARGET, + INHIBIT_SLEEP, + "org.freedesktop.login1.hibernate", + "org.freedesktop.login1.hibernate-multiple-sessions", + "org.freedesktop.login1.hibernate-ignore-inhibit", + SLEEP_HIBERNATE, + }, + [HANDLE_HYBRID_SLEEP] = { + SPECIAL_HYBRID_SLEEP_TARGET, + INHIBIT_SLEEP, + "org.freedesktop.login1.hibernate", + "org.freedesktop.login1.hibernate-multiple-sessions", + "org.freedesktop.login1.hibernate-ignore-inhibit", + SLEEP_HYBRID_SLEEP, + }, + [HANDLE_SUSPEND_THEN_HIBERNATE] = { + SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET, + INHIBIT_SLEEP, + "org.freedesktop.login1.hibernate", + "org.freedesktop.login1.hibernate-multiple-sessions", + "org.freedesktop.login1.hibernate-ignore-inhibit", + SLEEP_SUSPEND_THEN_HIBERNATE, + }, + [HANDLE_FACTORY_RESET] = { + SPECIAL_FACTORY_RESET_TARGET, + _INHIBIT_WHAT_INVALID, + NULL, + NULL, + NULL, + _SLEEP_OPERATION_INVALID, + SD_MESSAGE_FACTORY_RESET_STR, + "System is performing factory reset", + NULL + }, +}; +const char* manager_target_for_action(HandleAction handle) { assert(handle >= 0); - if (handle < (ssize_t) ELEMENTSOF(target_table)) - return target_table[handle]; - return NULL; + assert(handle < (ssize_t) ELEMENTSOF(action_table)); + + return action_table[handle].target; +} + +const ActionTableItem* manager_item_for_handle(HandleAction handle) { + assert(handle >= 0); + assert(handle < (ssize_t) ELEMENTSOF(action_table)); + + return &action_table[handle]; +} + +HandleAction manager_handle_for_item(const ActionTableItem* a) { + if (a && a < action_table + ELEMENTSOF(action_table)) + return a - action_table; + return _HANDLE_ACTION_INVALID; } int manager_handle_action( @@ -59,7 +151,6 @@ int manager_handle_action( InhibitWhat inhibit_operation; Inhibitor *offending = NULL; bool supported; - const char *target; int r; assert(m); @@ -129,17 +220,13 @@ int manager_handle_action( return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Requested %s operation not supported, ignoring.", handle_action_to_string(handle)); - if (m->action_what > 0) + if (m->delayed_action) return log_debug_errno(SYNTHETIC_ERRNO(EALREADY), "Action already in progress (%s), ignoring requested %s operation.", - inhibit_what_to_string(m->action_what), + inhibit_what_to_string(m->delayed_action->inhibit_what), handle_action_to_string(handle)); - assert_se(target = manager_target_for_action(handle)); - - inhibit_operation = IN_SET(handle, HANDLE_SUSPEND, HANDLE_HIBERNATE, - HANDLE_HYBRID_SLEEP, - HANDLE_SUSPEND_THEN_HIBERNATE) ? INHIBIT_SLEEP : INHIBIT_SHUTDOWN; + inhibit_operation = manager_item_for_handle(handle)->inhibit_what; /* If the actual operation is inhibited, warn and fail */ if (!ignore_inhibited && @@ -162,7 +249,7 @@ int manager_handle_action( log_info("%s", message_table[handle]); - r = bus_manager_shutdown_or_sleep_now_or_later(m, target, inhibit_operation, &error); + r = bus_manager_shutdown_or_sleep_now_or_later(m, manager_item_for_handle(handle), &error); if (r < 0) return log_error_errno(r, "Failed to execute %s operation: %s", handle_action_to_string(handle), diff --git a/src/login/logind-action.h b/src/login/logind-action.h index ec2fece2b7..e6d3047743 100644 --- a/src/login/logind-action.h +++ b/src/login/logind-action.h @@ -19,8 +19,26 @@ typedef enum HandleAction { _HANDLE_ACTION_INVALID = -EINVAL, } HandleAction; +typedef struct ActionTableItem ActionTableItem; + +#define handle_action_valid(x) (x && (x < _HANDLE_ACTION_MAX)) + #include "logind-inhibit.h" #include "logind.h" +#include "sleep-config.h" + +struct ActionTableItem { + const char *target; + InhibitWhat inhibit_what; + const char *polkit_action; + const char *polkit_action_multiple_sessions; + const char *polkit_action_ignore_inhibit; + SleepOperation sleep_operation; + const char* message_id; + const char* message; + const char* log_str; + +}; int manager_handle_action( Manager *m, @@ -33,5 +51,7 @@ const char* handle_action_to_string(HandleAction h) _const_; HandleAction handle_action_from_string(const char *s) _pure_; const char* manager_target_for_action(HandleAction handle); +const ActionTableItem* manager_item_for_handle(HandleAction handle); +HandleAction manager_handle_for_item(const ActionTableItem* a); CONFIG_PARSER_PROTOTYPE(config_parse_handle_action); diff --git a/src/login/logind-button.c b/src/login/logind-button.c index 7fb8114639..0f4e1f1b41 100644 --- a/src/login/logind-button.c +++ b/src/login/logind-button.c @@ -84,8 +84,7 @@ static void button_lid_switch_handle_action(Manager *manager, bool is_edge) { * differently */ if (manager_is_docked_or_external_displays(manager)) handle_action = manager->handle_lid_switch_docked; - else if (manager->handle_lid_switch_ep != _HANDLE_ACTION_INVALID && - manager_is_on_external_power()) + else if (!handle_action_valid(manager->handle_lid_switch_ep) && manager_is_on_external_power()) handle_action = manager->handle_lid_switch_ep; else handle_action = manager->handle_lid_switch; diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 223f0da205..cffe445919 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -29,6 +29,7 @@ #include "fileio.h" #include "format-util.h" #include "fs-util.h" +#include "logind-action.h" #include "logind-dbus.h" #include "logind-polkit.h" #include "logind-seat-dbus.h" @@ -53,6 +54,8 @@ #include "utmp-wtmp.h" #include "virt.h" +static void reset_scheduled_shutdown(Manager *m); + static int get_sender_session( Manager *m, sd_bus_message *message, @@ -309,16 +312,18 @@ static int property_get_preparing( sd_bus_error *error) { Manager *m = userdata; - bool b; + bool b = false; assert(bus); assert(reply); assert(m); - if (streq(property, "PreparingForShutdown")) - b = m->action_what & INHIBIT_SHUTDOWN; - else - b = m->action_what & INHIBIT_SLEEP; + if (m->delayed_action) { + if (streq(property, "PreparingForShutdown")) + b = m->delayed_action->inhibit_what & INHIBIT_SHUTDOWN; + else + b = m->delayed_action->inhibit_what & INHIBIT_SLEEP; + } return sd_bus_message_append(reply, "b", b); } @@ -343,7 +348,9 @@ static int property_get_scheduled_shutdown( if (r < 0) return r; - r = sd_bus_message_append(reply, "st", m->scheduled_shutdown_type, m->scheduled_shutdown_timeout); + r = sd_bus_message_append(reply, "st", + handle_action_to_string(manager_handle_for_item(m->scheduled_shutdown_type)), + m->scheduled_shutdown_timeout); if (r < 0) return r; @@ -1488,59 +1495,35 @@ static int have_multiple_sessions( return false; } -_printf_(2, 0) -static int log_with_wall_message(Manager *m, const char *d, const char *p, const char *q) { - assert(m); - - if (isempty(m->wall_message)) - p = strjoina(p, "."); - else - p = strjoina(p, " (", m->wall_message, ")."); - - return log_struct(LOG_NOTICE, d, p, q); -} - static int bus_manager_log_shutdown( Manager *m, - const char *unit_name) { + const ActionTableItem *a) { + + const char *message, *log_str; assert(m); - assert(unit_name); + assert(a); - if (streq(unit_name, SPECIAL_POWEROFF_TARGET)) - return log_with_wall_message(m, - "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_STR, - "MESSAGE=System is powering down", - "SHUTDOWN=power-off"); + message = a->message; + log_str = a->log_str; - if (streq(unit_name, SPECIAL_REBOOT_TARGET)) - return log_with_wall_message(m, - "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_STR, - "MESSAGE=System is rebooting", - "SHUTDOWN=reboot"); + if (message) + message = strjoina("MESSAGE=", message); + else + message = "MESSAGE=System is shutting down"; - if (streq(unit_name, SPECIAL_HALT_TARGET)) - return log_with_wall_message(m, - "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_STR, - "MESSAGE=System is halting", - "SHUTDOWN=halt"); + if (isempty(m->wall_message)) + message = strjoina(message, "."); + else + message = strjoina(message, " (", m->wall_message, ")."); - if (streq(unit_name, SPECIAL_KEXEC_TARGET)) - return log_with_wall_message(m, - "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_STR, - "MESSAGE=System is rebooting with kexec", - "SHUTDOWN=kexec"); + if (log_str) + log_str = strjoina("SHUTDOWN=", log_str); - if (streq(unit_name, SPECIAL_FACTORY_RESET_TARGET)) - return log_with_wall_message(m, - "MESSAGE_ID=" SD_MESSAGE_FACTORY_RESET_STR, - "MESSAGE=System is performing factory reset", - NULL); - - return log_with_wall_message(m, - "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_STR, - "MESSAGE=System is shutting down", - NULL); + return log_struct(LOG_NOTICE, + "MESSAGE_ID=%s", a->message_id ? a->message_id : SD_MESSAGE_SHUTDOWN_STR, + message, + log_str); } static int lid_switch_ignore_handler(sd_event_source *e, uint64_t usec, void *userdata) { @@ -1604,8 +1587,7 @@ static int send_prepare_for(Manager *m, InhibitWhat w, bool _active) { static int execute_shutdown_or_sleep( Manager *m, - InhibitWhat w, - const char *unit_name, + const ActionTableItem *a, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -1613,12 +1595,10 @@ static int execute_shutdown_or_sleep( int r; assert(m); - assert(w > 0); - assert(w < _INHIBIT_WHAT_MAX); - assert(unit_name); + assert(a); - if (w == INHIBIT_SHUTDOWN) - bus_manager_log_shutdown(m, unit_name); + if (a->inhibit_what == INHIBIT_SHUTDOWN) + bus_manager_log_shutdown(m, a); r = bus_call_method( m->bus, @@ -1626,7 +1606,7 @@ static int execute_shutdown_or_sleep( "StartUnit", error, &reply, - "ss", unit_name, "replace-irreversibly"); + "ss", a->target, "replace-irreversibly"); if (r < 0) goto error; @@ -1638,8 +1618,7 @@ static int execute_shutdown_or_sleep( if (r < 0) goto error; - m->action_unit = unit_name; - m->action_what = w; + m->delayed_action = a; /* Make sure the lid switch is ignored for a while */ manager_set_lid_switch_ignore(m, usec_add(now(CLOCK_MONOTONIC), m->holdoff_timeout_usec)); @@ -1648,7 +1627,7 @@ static int execute_shutdown_or_sleep( error: /* Tell people that they now may take a lock again */ - (void) send_prepare_for(m, w, false); + (void) send_prepare_for(m, a->inhibit_what, false); return r; } @@ -1660,10 +1639,10 @@ int manager_dispatch_delayed(Manager *manager, bool timeout) { assert(manager); - if (manager->action_what == 0 || manager->action_job) + if (!manager->delayed_action || manager->action_job) return 0; - if (manager_is_inhibited(manager, manager->action_what, INHIBIT_DELAY, NULL, false, false, 0, &offending)) { + if (manager_is_inhibited(manager, manager->delayed_action->inhibit_what, INHIBIT_DELAY, NULL, false, false, 0, &offending)) { _cleanup_free_ char *comm = NULL, *u = NULL; if (!timeout) @@ -1678,13 +1657,12 @@ int manager_dispatch_delayed(Manager *manager, bool timeout) { } /* Actually do the operation */ - r = execute_shutdown_or_sleep(manager, manager->action_what, manager->action_unit, &error); + r = execute_shutdown_or_sleep(manager, manager->delayed_action, &error); if (r < 0) { log_warning("Error during inhibitor-delayed operation (already returned success to client): %s", bus_error_message(&error, r)); - manager->action_unit = NULL; - manager->action_what = 0; + manager->delayed_action = NULL; } return 1; /* We did some work. */ @@ -1705,15 +1683,12 @@ static int manager_inhibit_timeout_handler( static int delay_shutdown_or_sleep( Manager *m, - InhibitWhat w, - const char *unit_name) { + const ActionTableItem *a) { int r; assert(m); - assert(w >= 0); - assert(w < _INHIBIT_WHAT_MAX); - assert(unit_name); + assert(a); if (m->inhibit_timeout_source) { r = sd_event_source_set_time_relative(m->inhibit_timeout_source, m->inhibit_delay_max); @@ -1733,16 +1708,14 @@ static int delay_shutdown_or_sleep( return r; } - m->action_unit = unit_name; - m->action_what = w; + m->delayed_action = a; return 0; } int bus_manager_shutdown_or_sleep_now_or_later( Manager *m, - const char *unit_name, - InhibitWhat w, + const ActionTableItem *a, sd_bus_error *error) { _cleanup_free_ char *load_state = NULL; @@ -1750,35 +1723,33 @@ int bus_manager_shutdown_or_sleep_now_or_later( int r; assert(m); - assert(unit_name); - assert(w > 0); - assert(w < _INHIBIT_WHAT_MAX); + assert(a); assert(!m->action_job); - r = unit_load_state(m->bus, unit_name, &load_state); + r = unit_load_state(m->bus, a->target, &load_state); if (r < 0) return r; if (!streq(load_state, "loaded")) return log_notice_errno(SYNTHETIC_ERRNO(EACCES), "Unit %s is %s, refusing operation.", - unit_name, load_state); + a->target, load_state); /* Tell everybody to prepare for shutdown/sleep */ - (void) send_prepare_for(m, w, true); + (void) send_prepare_for(m, a->inhibit_what, true); delayed = m->inhibit_delay_max > 0 && - manager_is_inhibited(m, w, INHIBIT_DELAY, NULL, false, false, 0, NULL); + manager_is_inhibited(m, a->inhibit_what, INHIBIT_DELAY, NULL, false, false, 0, NULL); if (delayed) /* Shutdown is delayed, keep in mind what we * want to do, and start a timeout */ - r = delay_shutdown_or_sleep(m, w, unit_name); + r = delay_shutdown_or_sleep(m, a); else /* Shutdown is not delayed, execute it * immediately */ - r = execute_shutdown_or_sleep(m, w, unit_name, error); + r = execute_shutdown_or_sleep(m, a, error); return r; } @@ -1786,10 +1757,7 @@ int bus_manager_shutdown_or_sleep_now_or_later( static int verify_shutdown_creds( Manager *m, sd_bus_message *message, - InhibitWhat w, - const char *action, - const char *action_multiple_sessions, - const char *action_ignore_inhibit, + const ActionTableItem *a, uint64_t flags, sd_bus_error *error) { @@ -1799,12 +1767,8 @@ static int verify_shutdown_creds( int r; assert(m); + assert(a); assert(message); - assert(w >= 0); - assert(w <= _INHIBIT_WHAT_MAX); - assert(action); - assert(action_multiple_sessions); - assert(action_ignore_inhibit); r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); if (r < 0) @@ -1819,11 +1783,19 @@ static int verify_shutdown_creds( return r; multiple_sessions = r > 0; - blocked = manager_is_inhibited(m, w, INHIBIT_BLOCK, NULL, false, true, uid, NULL); + blocked = manager_is_inhibited(m, a->inhibit_what, INHIBIT_BLOCK, NULL, false, true, uid, NULL); interactive = flags & SD_LOGIND_INTERACTIVE; if (multiple_sessions) { - r = bus_verify_polkit_async(message, CAP_SYS_BOOT, action_multiple_sessions, NULL, interactive, UID_INVALID, &m->polkit_registry, error); + r = bus_verify_polkit_async( + message, + CAP_SYS_BOOT, + a->polkit_action_multiple_sessions, + NULL, + interactive, + UID_INVALID, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -1836,7 +1808,14 @@ static int verify_shutdown_creds( return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Access denied to root due to active block inhibitor"); - r = bus_verify_polkit_async(message, CAP_SYS_BOOT, action_ignore_inhibit, NULL, interactive, UID_INVALID, &m->polkit_registry, error); + r = bus_verify_polkit_async(message, + CAP_SYS_BOOT, + a->polkit_action_ignore_inhibit, + NULL, + interactive, + UID_INVALID, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -1844,7 +1823,14 @@ static int verify_shutdown_creds( } if (!multiple_sessions && !blocked) { - r = bus_verify_polkit_async(message, CAP_SYS_BOOT, action, NULL, interactive, UID_INVALID, &m->polkit_registry, error); + r = bus_verify_polkit_async(message, + CAP_SYS_BOOT, + a->polkit_action, + NULL, + interactive, + UID_INVALID, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -1880,12 +1866,7 @@ static int setup_wall_message_timer(Manager *m, sd_bus_message* message) { static int method_do_shutdown_or_sleep( Manager *m, sd_bus_message *message, - const char *unit_name, - InhibitWhat w, - const char *action, - const char *action_multiple_sessions, - const char *action_ignore_inhibit, - SleepOperation sleep_operation, + const ActionTableItem *a, bool with_flags, sd_bus_error *error) { @@ -1894,9 +1875,7 @@ static int method_do_shutdown_or_sleep( assert(m); assert(message); - assert(unit_name); - assert(w >= 0); - assert(w <= _INHIBIT_WHAT_MAX); + assert(a); if (with_flags) { /* New style method: with flags parameter (and interactive bool in the bus message header) */ @@ -1905,7 +1884,7 @@ static int method_do_shutdown_or_sleep( return r; if ((flags & ~SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_PUBLIC) != 0) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); - if (!streq(unit_name, SPECIAL_REBOOT_TARGET) && (flags & SD_LOGIND_REBOOT_VIA_KEXEC)) + if (manager_handle_for_item(a) != HANDLE_REBOOT && (flags & SD_LOGIND_REBOOT_VIA_KEXEC)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Reboot via kexec is only applicable with reboot operations"); } else { /* Old style method: no flags parameter, but interactive bool passed as boolean in @@ -1921,33 +1900,39 @@ static int method_do_shutdown_or_sleep( } if ((flags & SD_LOGIND_REBOOT_VIA_KEXEC) && kexec_loaded()) - unit_name = SPECIAL_KEXEC_TARGET; + a = manager_item_for_handle(HANDLE_KEXEC); /* Don't allow multiple jobs being executed at the same time */ - if (m->action_what > 0) + if (m->delayed_action) return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS, "There's already a shutdown or sleep operation in progress"); - if (sleep_operation >= 0) { - r = can_sleep(sleep_operation); + if (a->sleep_operation >= 0) { + r = can_sleep(a->sleep_operation); if (r == -ENOSPC) return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Not enough swap space for hibernation"); if (r == 0) return sd_bus_error_setf(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, - "Sleep verb \"%s\" not supported", sleep_operation_to_string(sleep_operation)); + "Sleep verb \"%s\" not supported", sleep_operation_to_string(a->sleep_operation)); if (r < 0) return r; } - r = verify_shutdown_creds(m, message, w, action, action_multiple_sessions, - action_ignore_inhibit, flags, error); + r = verify_shutdown_creds(m, message, a, flags, error); if (r != 0) return r; + /* reset case we're shorting a scheduled shutdown */ + m->unlink_nologin = false; + reset_scheduled_shutdown(m); + + m->scheduled_shutdown_timeout = 0; + m->scheduled_shutdown_type = a; + (void) setup_wall_message_timer(m, message); - r = bus_manager_shutdown_or_sleep_now_or_later(m, unit_name, w, error); + r = bus_manager_shutdown_or_sleep_now_or_later(m, a, error); if (r < 0) return r; @@ -1957,16 +1942,9 @@ static int method_do_shutdown_or_sleep( static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - (void)free_and_strdup(&m->scheduled_shutdown_type, "poweroff"); - return method_do_shutdown_or_sleep( m, message, - SPECIAL_POWEROFF_TARGET, - INHIBIT_SHUTDOWN, - "org.freedesktop.login1.power-off", - "org.freedesktop.login1.power-off-multiple-sessions", - "org.freedesktop.login1.power-off-ignore-inhibit", - _SLEEP_OPERATION_INVALID, + manager_item_for_handle(HANDLE_POWEROFF), sd_bus_message_is_method_call(message, NULL, "PowerOffWithFlags"), error); } @@ -1974,16 +1952,9 @@ static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - (void)free_and_strdup(&m->scheduled_shutdown_type, "reboot"); - return method_do_shutdown_or_sleep( m, message, - SPECIAL_REBOOT_TARGET, - INHIBIT_SHUTDOWN, - "org.freedesktop.login1.reboot", - "org.freedesktop.login1.reboot-multiple-sessions", - "org.freedesktop.login1.reboot-ignore-inhibit", - _SLEEP_OPERATION_INVALID, + manager_item_for_handle(HANDLE_REBOOT), sd_bus_message_is_method_call(message, NULL, "RebootWithFlags"), error); } @@ -1991,16 +1962,9 @@ static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error * static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - (void)free_and_strdup(&m->scheduled_shutdown_type, "halt"); - return method_do_shutdown_or_sleep( m, message, - SPECIAL_HALT_TARGET, - INHIBIT_SHUTDOWN, - "org.freedesktop.login1.halt", - "org.freedesktop.login1.halt-multiple-sessions", - "org.freedesktop.login1.halt-ignore-inhibit", - _SLEEP_OPERATION_INVALID, + manager_item_for_handle(HANDLE_HALT), sd_bus_message_is_method_call(message, NULL, "HaltWithFlags"), error); } @@ -2008,16 +1972,9 @@ static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *er static int method_suspend(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - (void)free_and_strdup(&m->scheduled_shutdown_type, "suspend"); - return method_do_shutdown_or_sleep( m, message, - SPECIAL_SUSPEND_TARGET, - INHIBIT_SLEEP, - "org.freedesktop.login1.suspend", - "org.freedesktop.login1.suspend-multiple-sessions", - "org.freedesktop.login1.suspend-ignore-inhibit", - SLEEP_SUSPEND, + manager_item_for_handle(HANDLE_SUSPEND), sd_bus_message_is_method_call(message, NULL, "SuspendWithFlags"), error); } @@ -2025,16 +1982,9 @@ static int method_suspend(sd_bus_message *message, void *userdata, sd_bus_error static int method_hibernate(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - (void)free_and_strdup(&m->scheduled_shutdown_type, "hibernate"); - return method_do_shutdown_or_sleep( m, message, - SPECIAL_HIBERNATE_TARGET, - INHIBIT_SLEEP, - "org.freedesktop.login1.hibernate", - "org.freedesktop.login1.hibernate-multiple-sessions", - "org.freedesktop.login1.hibernate-ignore-inhibit", - SLEEP_HIBERNATE, + manager_item_for_handle(HANDLE_HIBERNATE), sd_bus_message_is_method_call(message, NULL, "HibernateWithFlags"), error); } @@ -2042,16 +1992,9 @@ static int method_hibernate(sd_bus_message *message, void *userdata, sd_bus_erro static int method_hybrid_sleep(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - (void)free_and_strdup(&m->scheduled_shutdown_type, "hybrid-sleep"); - return method_do_shutdown_or_sleep( m, message, - SPECIAL_HYBRID_SLEEP_TARGET, - INHIBIT_SLEEP, - "org.freedesktop.login1.hibernate", - "org.freedesktop.login1.hibernate-multiple-sessions", - "org.freedesktop.login1.hibernate-ignore-inhibit", - SLEEP_HYBRID_SLEEP, + manager_item_for_handle(HANDLE_HYBRID_SLEEP), sd_bus_message_is_method_call(message, NULL, "HybridSleepWithFlags"), error); } @@ -2059,16 +2002,9 @@ static int method_hybrid_sleep(sd_bus_message *message, void *userdata, sd_bus_e static int method_suspend_then_hibernate(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - (void)free_and_strdup(&m->scheduled_shutdown_type, "sleep"); - return method_do_shutdown_or_sleep( m, message, - SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET, - INHIBIT_SLEEP, - "org.freedesktop.login1.hibernate", - "org.freedesktop.login1.hibernate-multiple-sessions", - "org.freedesktop.login1.hibernate-ignore-inhibit", - SLEEP_SUSPEND_THEN_HIBERNATE, + manager_item_for_handle(HANDLE_SUSPEND_THEN_HIBERNATE), sd_bus_message_is_method_call(message, NULL, "SuspendThenHibernateWithFlags"), error); } @@ -2116,7 +2052,7 @@ static int update_schedule_file(Manager *m) { "MODE=%s\n", m->scheduled_shutdown_timeout, m->enable_wall_messages, - m->scheduled_shutdown_type); + handle_action_to_string(manager_handle_for_item(m->scheduled_shutdown_type))); if (!isempty(m->wall_message)) { _cleanup_free_ char *t = NULL; @@ -2155,8 +2091,7 @@ static void reset_scheduled_shutdown(Manager *m) { m->wall_message_timeout_source = sd_event_source_unref(m->wall_message_timeout_source); m->nologin_timeout_source = sd_event_source_unref(m->nologin_timeout_source); - m->scheduled_shutdown_type = mfree(m->scheduled_shutdown_type); - m->scheduled_shutdown_timeout = 0; + m->scheduled_shutdown_type = NULL; m->shutdown_dry_run = false; if (m->unlink_nologin) { @@ -2172,31 +2107,20 @@ static int manager_scheduled_shutdown_handler( uint64_t usec, void *userdata) { + const ActionTableItem *a = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; Manager *m = userdata; - const char *target; int r; assert(m); - if (isempty(m->scheduled_shutdown_type)) - return 0; - - if (streq(m->scheduled_shutdown_type, "poweroff")) - target = SPECIAL_POWEROFF_TARGET; - else if (streq(m->scheduled_shutdown_type, "reboot")) - target = SPECIAL_REBOOT_TARGET; - else if (streq(m->scheduled_shutdown_type, "kexec")) - target = SPECIAL_KEXEC_TARGET; - else if (streq(m->scheduled_shutdown_type, "halt")) - target = SPECIAL_HALT_TARGET; - else - assert_not_reached(); + a = m->scheduled_shutdown_type; + assert(a); /* Don't allow multiple jobs being executed at the same time */ - if (m->action_what > 0) { + if (m->delayed_action) { r = -EALREADY; - log_error("Scheduled shutdown to %s failed: shutdown or sleep operation already in progress", target); + log_error("Scheduled shutdown to %s failed: shutdown or sleep operation already in progress", a->target); goto error; } @@ -2206,16 +2130,16 @@ static int manager_scheduled_shutdown_handler( * above) for some seconds after our admin has seen the final * wall message. */ - bus_manager_log_shutdown(m, target); + bus_manager_log_shutdown(m, a); log_info("Running in dry run, suppressing action."); reset_scheduled_shutdown(m); return 0; } - r = bus_manager_shutdown_or_sleep_now_or_later(m, target, INHIBIT_SHUTDOWN, &error); + r = bus_manager_shutdown_or_sleep_now_or_later(m, m->scheduled_shutdown_type, &error); if (r < 0) { - log_error_errno(r, "Scheduled shutdown to %s failed: %m", target); + log_error_errno(r, "Scheduled shutdown to %s failed: %m", a->target); goto error; } @@ -2228,9 +2152,8 @@ error: static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - const char *action_multiple_sessions = NULL; - const char *action_ignore_inhibit = NULL; - const char *action = NULL; + HandleAction handle; + const ActionTableItem *a; uint64_t elapse; char *type; int r; @@ -2248,23 +2171,15 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ dry_run = true; } - if (streq(type, "poweroff")) { - action = "org.freedesktop.login1.power-off"; - action_multiple_sessions = "org.freedesktop.login1.power-off-multiple-sessions"; - action_ignore_inhibit = "org.freedesktop.login1.power-off-ignore-inhibit"; - } else if (STR_IN_SET(type, "reboot", "kexec")) { - action = "org.freedesktop.login1.reboot"; - action_multiple_sessions = "org.freedesktop.login1.reboot-multiple-sessions"; - action_ignore_inhibit = "org.freedesktop.login1.reboot-ignore-inhibit"; - } else if (streq(type, "halt")) { - action = "org.freedesktop.login1.halt"; - action_multiple_sessions = "org.freedesktop.login1.halt-multiple-sessions"; - action_ignore_inhibit = "org.freedesktop.login1.halt-ignore-inhibit"; - } else + handle = handle_action_from_string(type); + if (!IN_SET(handle, HANDLE_POWEROFF, HANDLE_REBOOT, HANDLE_HALT, HANDLE_KEXEC)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Unsupported shutdown type"); - r = verify_shutdown_creds(m, message, INHIBIT_SHUTDOWN, action, action_multiple_sessions, - action_ignore_inhibit, 0, error); + a = manager_item_for_handle(handle); + assert(a); + assert(a->polkit_action); + + r = verify_shutdown_creds(m, message, a, 0, error); if (r != 0) return r; @@ -2283,12 +2198,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ return log_error_errno(r, "sd_event_add_time() failed: %m"); } - r = free_and_strdup(&m->scheduled_shutdown_type, type); - if (r < 0) { - m->scheduled_shutdown_timeout_source = sd_event_source_unref(m->scheduled_shutdown_timeout_source); - return log_oom(); - } - + m->scheduled_shutdown_type = a; m->shutdown_dry_run = dry_run; if (m->nologin_timeout_source) { @@ -2323,32 +2233,25 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - const char *action; + const ActionTableItem *a; bool cancelled; int r; assert(m); assert(message); - cancelled = m->scheduled_shutdown_type != NULL; - + cancelled = !IN_SET(manager_handle_for_item(m->scheduled_shutdown_type), HANDLE_IGNORE, _HANDLE_ACTION_INVALID); if (!cancelled) goto done; - // mirrors code in method_schedule_shutdown() - if (streq(m->scheduled_shutdown_type, "poweroff")) { - action = "org.freedesktop.login1.power-off"; - } else if (STR_IN_SET(m->scheduled_shutdown_type, "reboot", "kexec")) { - action = "org.freedesktop.login1.reboot"; - } else if (streq(m->scheduled_shutdown_type, "halt")) { - action = "org.freedesktop.login1.halt"; - } else - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Unsupported shutdown type"); + a = m->scheduled_shutdown_type; + if (!a->polkit_action) + return sd_bus_error_set(error, SD_BUS_ERROR_AUTH_FAILED, "Unsupported shutdown type"); r = bus_verify_polkit_async( message, CAP_SYS_BOOT, - action, + a->polkit_action, NULL, false, UID_INVALID, @@ -2385,15 +2288,10 @@ done: static int method_can_shutdown_or_sleep( Manager *m, sd_bus_message *message, - InhibitWhat w, - const char *action, - const char *action_multiple_sessions, - const char *action_ignore_inhibit, - SleepOperation sleep_operation, + const ActionTableItem *a, sd_bus_error *error) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - HandleAction handle; bool multiple_sessions, challenge, blocked; const char *result = NULL; uid_t uid; @@ -2401,14 +2299,10 @@ static int method_can_shutdown_or_sleep( assert(m); assert(message); - assert(w >= 0); - assert(w <= _INHIBIT_WHAT_MAX); - assert(action); - assert(action_multiple_sessions); - assert(action_ignore_inhibit); + assert(a); - if (sleep_operation >= 0) { - r = can_sleep(sleep_operation); + if (a->sleep_operation >= 0) { + r = can_sleep(a->sleep_operation); if (IN_SET(r, 0, -ENOSPC)) return sd_bus_reply_method_return(message, "s", "na"); if (r < 0) @@ -2428,9 +2322,9 @@ static int method_can_shutdown_or_sleep( return r; multiple_sessions = r > 0; - blocked = manager_is_inhibited(m, w, INHIBIT_BLOCK, NULL, false, true, uid, NULL); + blocked = manager_is_inhibited(m, a->inhibit_what, INHIBIT_BLOCK, NULL, false, true, uid, NULL); - handle = handle_action_from_string(sleep_operation_to_string(sleep_operation)); + HandleAction handle = handle_action_from_string(sleep_operation_to_string(a->sleep_operation)); if (handle >= 0) { const char *target; @@ -2450,7 +2344,7 @@ static int method_can_shutdown_or_sleep( } if (multiple_sessions) { - r = bus_test_polkit(message, CAP_SYS_BOOT, action_multiple_sessions, NULL, UID_INVALID, &challenge, error); + r = bus_test_polkit(message, CAP_SYS_BOOT, a->polkit_action_multiple_sessions, NULL, UID_INVALID, &challenge, error); if (r < 0) return r; @@ -2463,7 +2357,7 @@ static int method_can_shutdown_or_sleep( } if (blocked) { - r = bus_test_polkit(message, CAP_SYS_BOOT, action_ignore_inhibit, NULL, UID_INVALID, &challenge, error); + r = bus_test_polkit(message, CAP_SYS_BOOT, a->polkit_action_ignore_inhibit, NULL, UID_INVALID, &challenge, error); if (r < 0) return r; @@ -2481,7 +2375,7 @@ static int method_can_shutdown_or_sleep( /* If neither inhibit nor multiple sessions * apply then just check the normal policy */ - r = bus_test_polkit(message, CAP_SYS_BOOT, action, NULL, UID_INVALID, &challenge, error); + r = bus_test_polkit(message, CAP_SYS_BOOT, a->polkit_action, NULL, UID_INVALID, &challenge, error); if (r < 0) return r; @@ -2501,12 +2395,7 @@ static int method_can_poweroff(sd_bus_message *message, void *userdata, sd_bus_e Manager *m = userdata; return method_can_shutdown_or_sleep( - m, message, - INHIBIT_SHUTDOWN, - "org.freedesktop.login1.power-off", - "org.freedesktop.login1.power-off-multiple-sessions", - "org.freedesktop.login1.power-off-ignore-inhibit", - _SLEEP_OPERATION_INVALID, + m, message, manager_item_for_handle(HANDLE_POWEROFF), error); } @@ -2514,12 +2403,7 @@ static int method_can_reboot(sd_bus_message *message, void *userdata, sd_bus_err Manager *m = userdata; return method_can_shutdown_or_sleep( - m, message, - INHIBIT_SHUTDOWN, - "org.freedesktop.login1.reboot", - "org.freedesktop.login1.reboot-multiple-sessions", - "org.freedesktop.login1.reboot-ignore-inhibit", - _SLEEP_OPERATION_INVALID, + m, message, manager_item_for_handle(HANDLE_REBOOT), error); } @@ -2527,12 +2411,7 @@ static int method_can_halt(sd_bus_message *message, void *userdata, sd_bus_error Manager *m = userdata; return method_can_shutdown_or_sleep( - m, message, - INHIBIT_SHUTDOWN, - "org.freedesktop.login1.halt", - "org.freedesktop.login1.halt-multiple-sessions", - "org.freedesktop.login1.halt-ignore-inhibit", - _SLEEP_OPERATION_INVALID, + m, message, manager_item_for_handle(HANDLE_HALT), error); } @@ -2540,12 +2419,7 @@ static int method_can_suspend(sd_bus_message *message, void *userdata, sd_bus_er Manager *m = userdata; return method_can_shutdown_or_sleep( - m, message, - INHIBIT_SLEEP, - "org.freedesktop.login1.suspend", - "org.freedesktop.login1.suspend-multiple-sessions", - "org.freedesktop.login1.suspend-ignore-inhibit", - SLEEP_SUSPEND, + m, message, manager_item_for_handle(HANDLE_SUSPEND), error); } @@ -2553,12 +2427,7 @@ static int method_can_hibernate(sd_bus_message *message, void *userdata, sd_bus_ Manager *m = userdata; return method_can_shutdown_or_sleep( - m, message, - INHIBIT_SLEEP, - "org.freedesktop.login1.hibernate", - "org.freedesktop.login1.hibernate-multiple-sessions", - "org.freedesktop.login1.hibernate-ignore-inhibit", - SLEEP_HIBERNATE, + m, message, manager_item_for_handle(HANDLE_HIBERNATE), error); } @@ -2566,12 +2435,7 @@ static int method_can_hybrid_sleep(sd_bus_message *message, void *userdata, sd_b Manager *m = userdata; return method_can_shutdown_or_sleep( - m, message, - INHIBIT_SLEEP, - "org.freedesktop.login1.hibernate", - "org.freedesktop.login1.hibernate-multiple-sessions", - "org.freedesktop.login1.hibernate-ignore-inhibit", - SLEEP_HYBRID_SLEEP, + m, message, manager_item_for_handle(HANDLE_HYBRID_SLEEP), error); } @@ -2579,12 +2443,7 @@ static int method_can_suspend_then_hibernate(sd_bus_message *message, void *user Manager *m = userdata; return method_can_shutdown_or_sleep( - m, message, - INHIBIT_SLEEP, - "org.freedesktop.login1.hibernate", - "org.freedesktop.login1.hibernate-multiple-sessions", - "org.freedesktop.login1.hibernate-ignore-inhibit", - SLEEP_SUSPEND_THEN_HIBERNATE, + m, message, manager_item_for_handle(HANDLE_SUSPEND_THEN_HIBERNATE), error); } @@ -3256,6 +3115,15 @@ static int method_set_wall_message( if (r < 0) return r; + /* sysvinit has a 252 (256-(strlen(" \r\n")+1)) character + * limit for the wall message. There is no real technical + * need for that but doesn't make sense to store arbitrary + * armounts either. + * https://git.savannah.nongnu.org/cgit/sysvinit.git/tree/src/shutdown.c#n72) + */ + if (strlen(wall_message) > 252) + return -EMSGSIZE; + /* Short-circuit the operation if the desired state is already in place, to * avoid an unnecessary polkit permission check. */ if (streq_ptr(m->wall_message, empty_to_null(wall_message)) && @@ -3323,7 +3191,7 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error * executing the operation. We shouldn't create the impression * that the lock was successful if the machine is about to go * down/suspend any moment. */ - if (m->action_what & w) + if (m->delayed_action && m->delayed_action->inhibit_what & w) return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS, "The operation inhibition has been requested for is already running"); @@ -3830,14 +3698,14 @@ int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *err } if (m->action_job && streq(m->action_job, path)) { - log_info("Operation '%s' finished.", inhibit_what_to_string(m->action_what)); + assert(m->delayed_action); + log_info("Operation '%s' finished.", inhibit_what_to_string(m->delayed_action->inhibit_what)); /* Tell people that they now may take a lock again */ - (void) send_prepare_for(m, m->action_what, false); + (void) send_prepare_for(m, m->delayed_action->inhibit_what, false); m->action_job = mfree(m->action_job); - m->action_unit = NULL; - m->action_what = 0; + m->delayed_action = NULL; return 0; } diff --git a/src/login/logind-dbus.h b/src/login/logind-dbus.h index 6b5d3abcd6..13aff11bba 100644 --- a/src/login/logind-dbus.h +++ b/src/login/logind-dbus.h @@ -4,6 +4,7 @@ #include "sd-bus.h" #include "bus-object.h" +#include "logind-action.h" #include "logind-session.h" #include "logind-user.h" #include "logind.h" @@ -14,7 +15,7 @@ int manager_get_seat_from_creds(Manager *m, sd_bus_message *message, const char int manager_dispatch_delayed(Manager *manager, bool timeout); -int bus_manager_shutdown_or_sleep_now_or_later(Manager *m, const char *unit_name, InhibitWhat w, sd_bus_error *error); +int bus_manager_shutdown_or_sleep_now_or_later(Manager *m, const ActionTableItem *a, sd_bus_error *error); int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/login/logind-utmp.c b/src/login/logind-utmp.c index d2b2f8b193..03817f9c1a 100644 --- a/src/login/logind-utmp.c +++ b/src/login/logind-utmp.c @@ -72,7 +72,7 @@ static int warn_wall(Manager *m, usec_t n) { r = asprintf(&l, "%s%sThe system is going down for %s %s%s!", strempty(m->wall_message), isempty(m->wall_message) ? "" : "\n", - m->scheduled_shutdown_type, + handle_action_to_string(manager_handle_for_item(m->scheduled_shutdown_type)), left ? "at " : "NOW", left ? FORMAT_TIMESTAMP(m->scheduled_shutdown_timeout) : ""); if (r < 0) { @@ -130,10 +130,8 @@ int manager_setup_wall_message_timer(Manager *m) { /* wall message handling */ - if (isempty(m->scheduled_shutdown_type)) { - warn_wall(m, n); + if (!m->scheduled_shutdown_type) return 0; - } if (elapse > 0 && elapse < n) return 0; diff --git a/src/login/logind.c b/src/login/logind.c index b24f26485a..c561b75951 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -168,7 +168,6 @@ static Manager* manager_unref(Manager *m) { strv_free(m->kill_only_users); strv_free(m->kill_exclude_users); - free(m->scheduled_shutdown_type); free(m->scheduled_shutdown_tty); free(m->wall_message); free(m->action_job); diff --git a/src/login/logind.h b/src/login/logind.h index 730c14a46a..5647e5069c 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -68,21 +68,17 @@ struct Manager { usec_t inhibit_delay_max; usec_t user_stop_delay; - /* If an action is currently being executed or is delayed, - * this is != 0 and encodes what is being done */ - InhibitWhat action_what; - /* If a shutdown/suspend was delayed due to an inhibitor this - contains the unit name we are supposed to start after the + contains the action we are supposed to start after the delay is over */ - const char *action_unit; + const ActionTableItem *delayed_action; /* If a shutdown/suspend is currently executed, then this is * the job of it */ char *action_job; sd_event_source *inhibit_timeout_source; - char *scheduled_shutdown_type; + const ActionTableItem *scheduled_shutdown_type; usec_t scheduled_shutdown_timeout; sd_event_source *scheduled_shutdown_timeout_source; uid_t scheduled_shutdown_uid; From 38d55bf2641f345445cb4e6a5e5e808555591db2 Mon Sep 17 00:00:00 2001 From: Ludwig Nussel Date: Mon, 20 Dec 2021 18:05:50 +0100 Subject: [PATCH 7/8] systemctl: shutdown don't fallback on auth fail For shutdowns don't fall back to starting the target directly if talking to logind failed with auth failure. That would just lead to another polkit auth attempt. --- src/systemctl/systemctl-compat-halt.c | 4 ++-- src/systemctl/systemctl-logind.c | 2 +- src/systemctl/systemctl-start-special.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/systemctl/systemctl-compat-halt.c b/src/systemctl/systemctl-compat-halt.c index 7d95d34d54..a7d3ffadf4 100644 --- a/src/systemctl/systemctl-compat-halt.c +++ b/src/systemctl/systemctl-compat-halt.c @@ -156,8 +156,8 @@ int halt_main(void) { } if (r >= 0) return r; - if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) - /* Requested operation is not supported on the local system or already in + if (IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS)) + /* Requested operation requires auth, is not supported on the local system or already in * progress */ return r; /* on all other errors, try low-level operation */ diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c index 9eae59ca31..9bf24ed554 100644 --- a/src/systemctl/systemctl-logind.c +++ b/src/systemctl/systemctl-logind.c @@ -330,7 +330,7 @@ int logind_schedule_shutdown(void) { r = bus_call_method(bus, bus_login_mgr, "ScheduleShutdown", &error, NULL, "st", action, arg_when); if (r < 0) - return log_warning_errno(r, "Failed to call ScheduleShutdown in logind, proceeding with immediate shutdown: %s", bus_error_message(&error, r)); + return log_warning_errno(r, "Failed to schedule shutdown: %s", bus_error_message(&error, r)); if (!arg_quiet) logind_show_shutdown(); diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index 6ece700a9b..08eefc473e 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -213,8 +213,8 @@ int start_special(int argc, char *argv[], void *userdata) { r = logind_reboot(a); if (r >= 0) return r; - if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) - /* Requested operation is not supported or already in progress */ + if (IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS)) + /* Requested operation requires auth, is not supported or already in progress */ return r; /* On all other errors, try low-level operation. In order to minimize the difference From 48f3bc5cc4dd52155a06753da5ef2cd9d48f7b07 Mon Sep 17 00:00:00 2001 From: Ludwig Nussel Date: Wed, 22 Dec 2021 11:50:08 +0100 Subject: [PATCH 8/8] test: add shutdown test Wraps nspawn to be able to use pexpect. The test logs in on the console and runs screen. In one screen window it types in shutdown commands and checks whether a wall message was sent to the other. --- test/TEST-69-SHUTDOWN/Makefile | 1 + test/TEST-69-SHUTDOWN/test.sh | 33 +++++++++ test/test-functions | 4 ++ test/test-shutdown.py | 114 ++++++++++++++++++++++++++++++++ test/units/testsuite-69.service | 7 ++ 5 files changed, 159 insertions(+) create mode 120000 test/TEST-69-SHUTDOWN/Makefile create mode 100755 test/TEST-69-SHUTDOWN/test.sh create mode 100755 test/test-shutdown.py create mode 100644 test/units/testsuite-69.service diff --git a/test/TEST-69-SHUTDOWN/Makefile b/test/TEST-69-SHUTDOWN/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-69-SHUTDOWN/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-69-SHUTDOWN/test.sh b/test/TEST-69-SHUTDOWN/test.sh new file mode 100755 index 0000000000..42a600ec18 --- /dev/null +++ b/test/TEST-69-SHUTDOWN/test.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -e + +TEST_DESCRIPTION="shutdown testing" +IMAGE_NAME="shutdown" +TEST_NO_QEMU=1 + +# shellcheck source=test/test-functions +. "${TEST_BASE_DIR:?}/test-functions" + +_ORIG_NSPAWN="$SYSTEMD_NSPAWN" +SYSTEMD_NSPAWN="$STATEDIR/run-nspawn" + +setup_nspawn_root_hook() { + cat > "$STATEDIR"/run-nspawn <<-EOF + #!/bin/bash + exec "$TEST_BASE_DIR"/test-shutdown.py -- "$_ORIG_NSPAWN" "\$@" + exit 1 + EOF + chmod 755 "$STATEDIR"/run-nspawn +} + +test_append_files() { + # prevent shutdown in test suite, the expect script does that manually. + rm "$1"/usr/lib/systemd/tests/testdata/units/end.service + inst /usr/bin/screen + echo "PS1='screen\$WINDOW # '" > "$1"/etc/bash.bashrc + echo 'startup_message off' > "$1"/etc/screenrc + echo 'bell_msg ""' >> "$1"/etc/screenrc +} + +do_test "$@" diff --git a/test/test-functions b/test/test-functions index ba18a0ea2d..2258b8b1fe 100644 --- a/test/test-functions +++ b/test/test-functions @@ -1884,6 +1884,8 @@ has_user_dbus_socket() { fi } +setup_nspawn_root_hook() { :;} + setup_nspawn_root() { if [ -z "${initdir}" ]; then dfatal "\$initdir not defined" @@ -1896,6 +1898,8 @@ setup_nspawn_root() { ddebug "cp -ar $initdir $TESTDIR/unprivileged-nspawn-root" cp -ar "$initdir" "$TESTDIR/unprivileged-nspawn-root" fi + + setup_nspawn_root_hook } setup_basic_dirs() { diff --git a/test/test-shutdown.py b/test/test-shutdown.py new file mode 100755 index 0000000000..d34e224942 --- /dev/null +++ b/test/test-shutdown.py @@ -0,0 +1,114 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: LGPL-2.1-or-later +# + +import argparse +import logging +import pexpect +import sys + + +def run(args): + + ret = 1 + logger = logging.getLogger("test-shutdown") + + logger.info("spawning test") + console = pexpect.spawn(args.command, args.arg, env={ + "TERM": "linux", + }, encoding='utf-8', timeout=30) + + if args.verbose: + console.logfile = sys.stdout + + logger.debug("child pid %d" % console.pid) + + try: + logger.info("waiting for login prompt") + console.expect('H login: ', 10) + + logger.info("log in and start screen") + console.sendline('root') + console.expect('bash.*# ', 10) + console.sendline('screen') + console.expect('screen0 ', 10) + console.sendcontrol('a') + console.send('c') + console.expect('screen1 ', 10) + +# console.interact() + + console.sendline('tty') + console.expect(r'/dev/(pts/\d+)') + pty = console.match.group(1) + logger.info("window 1 at line %s", pty) + + logger.info("schedule reboot") + console.sendline('shutdown -r') + console.expect("Reboot scheduled for (?P.*), use 'shutdown -c' to cancel", 2) + date = console.match.group('date') + logger.info("reboot scheduled for %s", date) + + console.sendcontrol('a') + console.send('0') + logger.info("verify broadcast message") + console.expect('Broadcast message from root@H on %s' % pty, 2) + console.expect('The system is going down for reboot at %s' % date, 2) + + logger.info("check show output") + console.sendline('shutdown --show') + console.expect("Reboot scheduled for %s, use 'shutdown -c' to cancel" % date, 2) + + logger.info("cancel shutdown") + console.sendline('shutdown -c') + console.sendcontrol('a') + console.send('1') + console.expect('The system shutdown has been cancelled', 2) + + logger.info("call for reboot") + console.sendline('sleep 10; shutdown -r now') + console.sendcontrol('a') + console.send('0') + console.expect("The system is going down for reboot NOW!", 12) + + logger.info("waiting for reboot") + + console.expect('H login: ', 10) + console.sendline('root') + console.expect('bash.*# ', 10) + + console.sendline('> /testok') + + logger.info("power off") + console.sendline('poweroff') + + logger.info("expect termination now") + console.expect(pexpect.EOF) + + ret = 0 + except Exception as e: + logger.error(e) + logger.info("killing child pid %d" % console.pid) + console.terminate() + + return ret + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='test logind shutdown feature') + parser.add_argument("-v", "--verbose", action="store_true", help="verbose") + parser.add_argument("command", help="command to run") + parser.add_argument("arg", nargs='*', help="args for command") + + args = parser.parse_args() + + if args.verbose: + level = logging.DEBUG + else: + level = logging.INFO + + logging.basicConfig(level=level) + + sys.exit(run(args)) + +# vim: sw=4 et diff --git a/test/units/testsuite-69.service b/test/units/testsuite-69.service new file mode 100644 index 0000000000..3b2b81edc8 --- /dev/null +++ b/test/units/testsuite-69.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-69-SHUTDOWN + +[Service] +Type=oneshot +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh