]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #21838 from lnussel/logind-refactor
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 31 Jan 2022 10:45:33 +0000 (19:45 +0900)
committerGitHub <noreply@github.com>
Mon, 31 Jan 2022 10:45:33 +0000 (19:45 +0900)
Logind shutdown refactor

16 files changed:
src/login/logind-action.c
src/login/logind-action.h
src/login/logind-button.c
src/login/logind-dbus.c
src/login/logind-dbus.h
src/login/logind-utmp.c
src/login/logind.c
src/login/logind.h
src/systemctl/systemctl-compat-halt.c
src/systemctl/systemctl-logind.c
src/systemctl/systemctl-start-special.c
test/TEST-69-SHUTDOWN/Makefile [new symlink]
test/TEST-69-SHUTDOWN/test.sh [new file with mode: 0755]
test/test-functions
test/test-shutdown.py [new file with mode: 0755]
test/units/testsuite-69.service [new file with mode: 0644]

index e1729109482b031d9f0a896ac4b369d8b325b59d..45f77bc090949cd7ee6b7810d7374753a0a0bc08 100644 (file)
@@ -2,6 +2,8 @@
 
 #include <unistd.h>
 
+#include "sd-messages.h"
+
 #include "alloc-util.h"
 #include "bus-error.h"
 #include "bus-util.h"
 #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"
 
+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) {
-        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,
-        };
+        assert(handle >= 0);
+        assert(handle < (ssize_t) ELEMENTSOF(action_table));
 
+        return action_table[handle].target;
+}
+
+const ActionTableItem* manager_item_for_handle(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];
+}
+
+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),
index ec2fece2b7b056ebbae95abff228106a199a6b4b..e6d304774331a59bdd7ce14d056d61b55d9e2c94 100644 (file)
@@ -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);
index 7fb811463938a6b1b5e5e7a2556e462c3149ab13..0f4e1f1b4173721f992e5be1edad1c4b71ae43f8 100644 (file)
@@ -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;
index f38f0629a82488caf38444b6e184bd7e52910bb0..5c4341df2bf4c24bace0a179b1bc2a036e645b10 100644 (file)
@@ -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) {
+static int bus_manager_log_shutdown(
+                Manager *m,
+                const ActionTableItem *a) {
+
+        const char *message, *log_str;
+
         assert(m);
+        assert(a);
+
+        message = a->message;
+        log_str = a->log_str;
+
+        if (message)
+                message = strjoina("MESSAGE=", message);
+        else
+                message = "MESSAGE=System is shutting down";
 
         if (isempty(m->wall_message))
-                p = strjoina(p, ".");
+                message = strjoina(message, ".");
         else
-                p = strjoina(p, " (", m->wall_message, ").");
+                message = strjoina(message, " (", m->wall_message, ").");
 
-        return log_struct(LOG_NOTICE, d, p, q);
-}
+        if (log_str)
+                log_str = strjoina("SHUTDOWN=", log_str);
 
-static int bus_manager_log_shutdown(
-                Manager *m,
-                const char *unit_name) {
-
-        assert(m);
-        assert(unit_name);
-
-        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");
-
-        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 (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 (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 (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)
@@ -1854,15 +1840,33 @@ 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,
-                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) {
 
@@ -1871,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) */
@@ -1882,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
@@ -1898,31 +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;
 
-        r = bus_manager_shutdown_or_sleep_now_or_later(m, unit_name, w, error);
+        /* 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, a, error);
         if (r < 0)
                 return r;
 
@@ -1934,12 +1944,7 @@ static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error
 
         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);
 }
@@ -1949,12 +1954,7 @@ static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *
 
         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);
 }
@@ -1964,12 +1964,7 @@ static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *er
 
         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);
 }
@@ -1979,12 +1974,7 @@ static int method_suspend(sd_bus_message *message, void *userdata, sd_bus_error
 
         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);
 }
@@ -1994,12 +1984,7 @@ static int method_hibernate(sd_bus_message *message, void *userdata, sd_bus_erro
 
         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);
 }
@@ -2009,12 +1994,7 @@ static int method_hybrid_sleep(sd_bus_message *message, void *userdata, sd_bus_e
 
         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);
 }
@@ -2024,12 +2004,7 @@ static int method_suspend_then_hibernate(sd_bus_message *message, void *userdata
 
         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);
 }
@@ -2077,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;
@@ -2116,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) {
@@ -2133,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;
         }
 
@@ -2167,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;
         }
 
@@ -2189,10 +2152,8 @@ 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;
+        HandleAction handle;
+        const ActionTableItem *a;
         uint64_t elapse;
         char *type;
         int r;
@@ -2210,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;
 
@@ -2245,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) {
@@ -2270,23 +2218,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)
@@ -2297,20 +2233,42 @@ 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 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;
+
+        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,
+                        a->polkit_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) {
@@ -2323,21 +2281,17 @@ 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);
 }
 
 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;
@@ -2345,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);
-
-        if (sleep_operation >= 0) {
-                r = can_sleep(sleep_operation);
+        assert(a);
+
+        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)
@@ -2372,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;
 
@@ -2394,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;
 
@@ -2407,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;
 
@@ -2425,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;
 
@@ -2445,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);
 }
 
@@ -2458,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);
 }
 
@@ -2471,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);
 }
 
@@ -2484,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);
 }
 
@@ -2497,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);
 }
 
@@ -2510,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);
 }
 
@@ -2523,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);
 }
 
@@ -3200,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)) &&
@@ -3267,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");
 
@@ -3774,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;
         }
 
index 6b5d3abcd6d1fa43fec633086556902b5af4a52a..13aff11bba8e7b5f3341bc6019c2bc6cf9221bff 100644 (file)
@@ -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);
index 553383647336a47235d047b495593f06faecd0a3..03817f9c1a6c74fc4a77b029968054094e300187 100644 (file)
@@ -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,16 +130,14 @@ 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 < 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;
index 52b1d9503460278a689afa1ed984b972cb67e928..c561b75951fd5da7cffc38e8d6ea2abc3830dc2d 100644 (file)
@@ -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),
         };
 
@@ -167,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);
index 730c14a46ae708f8c0b386ef071e46fc98637044..5647e5069c154757c516a9a2d3898adf8a45428b 100644 (file)
@@ -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;
index 760758322f5671861f9da803a87e6fb16542c242..a7d3ffadf421d7d3807fdbd677c9e1e2098ba197 100644 (file)
@@ -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;
-
-        /* 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;
-                }
+        /* always try logind first */
+        if (arg_when > 0)
+                r = logind_schedule_shutdown();
+        else {
+                r = logind_check_inhibitors(arg_action);
+                if (r < 0)
+                        return r;
 
-                /* 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, -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 */
 
         /* 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)
index 9eae59ca31050675dcd39bb515be8bced35fface..9bf24ed5549aca109522f5ac355e200fa3481aa2 100644 (file)
@@ -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();
index 6ece700a9bf717bd36ea75c6b0c8c750f5726f3a..08eefc473e9f39cbe6ac632bc8399fdd19b26a6e 100644 (file)
@@ -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
diff --git a/test/TEST-69-SHUTDOWN/Makefile b/test/TEST-69-SHUTDOWN/Makefile
new file mode 120000 (symlink)
index 0000000..e9f93b1
--- /dev/null
@@ -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 (executable)
index 0000000..42a600e
--- /dev/null
@@ -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 "$@"
index dcb54ec2433ad790cc198da6f3837352018a6181..ac8bf8883b9ebe3c45a3e1bb6a80fc6ce90966ed 100644 (file)
@@ -1896,6 +1896,8 @@ has_user_dbus_socket() {
     fi
 }
 
+setup_nspawn_root_hook() { :;}
+
 setup_nspawn_root() {
     if [ -z "${initdir}" ]; then
         dfatal "\$initdir not defined"
@@ -1908,6 +1910,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 (executable)
index 0000000..d34e224
--- /dev/null
@@ -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<date>.*), 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 (file)
index 0000000..3b2b81e
--- /dev/null
@@ -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