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

File diff suppressed because it is too large Load Diff

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