Merge pull request #21838 from lnussel/logind-refactor

Logind shutdown refactor
This commit is contained in:
Yu Watanabe 2022-01-31 19:45:33 +09:00 committed by GitHub
commit 256ce2e889
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 536 additions and 361 deletions

View file

@ -2,6 +2,8 @@
#include <unistd.h>
#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),

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

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

View 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) {
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)
@ -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);
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)
@ -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;
}

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

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

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

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

View 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;
/* 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, -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)

View 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();

View 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

View file

@ -0,0 +1 @@
../TEST-01-BASIC/Makefile

33
test/TEST-69-SHUTDOWN/test.sh Executable file
View file

@ -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 "$@"

View 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() {

114
test/test-shutdown.py Executable file
View file

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

View file

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