diff --git a/TODO b/TODO index 37345f4a75..378d318122 100644 --- a/TODO +++ b/TODO @@ -298,9 +298,6 @@ Features: * Add knob to cryptsetup, to trigger automatic reboot on failure to unlock disk. Enable this by default for rootfs, also in gpt-auto-generator -* Add RebootUptimeMinSec= knob to PID 1, that makes systemd-shutdown sleep - until the specified uptime has passed, to lengthen tight boot loops. - * replace bootctl's PE version check to actually use APIs from pe-binary.[ch] to find binary version. diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 088ce24154..9e24e749b3 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -69,6 +69,7 @@ systemd.import_credentials= systemd.reload_limit_interval_sec= systemd.reload_limit_burst= + systemd.minimum_uptime_sec= Parameters understood by the system and service manager to control system behavior. For details, see diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index 172657de65..e9e7d3d78d 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -633,6 +633,22 @@ + + + MinimumUptimeSec= + + Specifies the minimum uptime for the system which has to be reached before a shutdown + is executed. Defaults to 15s. This mechanism is introduced to avoid high frequency reboot loops, when + technical failures trigger an automatic shutdown during the boot process. Each reboot cycle is + delayed to the specified minimum time, giving the user a chance to review screen contents or + otherwise interact with the device before the shutdown proceeds. The delay takes place during the + very last phase of system shutdown, immediately before the reboot() system call + is executed. If the system is already running for longer than the specified time, this setting has no + effect. This logic is also skipped in container environments. Set to zero in order to disable this + logic. + + + diff --git a/man/systemd.xml b/man/systemd.xml index 06d9102e47..30ae385029 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -1061,6 +1061,16 @@ + + + systemd.minimum_uptime_sec= + + Takes a time in seconds. Specifies the minimum uptime of the system before the system + shuts down. For more information see the MinimumUptimeSec= setting described in + systemd-system.conf5. + + + For other kernel command line parameters understood by diff --git a/src/core/main.c b/src/core/main.c index bd065c351d..d0c0023f89 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -163,6 +163,7 @@ static void *arg_random_seed; static size_t arg_random_seed_size; static usec_t arg_reload_limit_interval_sec; static unsigned arg_reload_limit_burst; +static usec_t arg_minimum_uptime_usec; /* A copy of the original environment block */ static char **saved_env = NULL; @@ -554,6 +555,17 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } + } else if (proc_cmdline_key_streq(key, "systemd.minimum_uptime_sec")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_sec(value, &arg_minimum_uptime_usec); + if (r < 0) { + log_warning_errno(r, "Failed to parse systemd.minimum_uptime_sec= argument '%s', ignoring: %m", value); + return 0; + } + } else if (streq(key, "quiet") && !value) { if (arg_show_status == _SHOW_STATUS_INVALID) @@ -813,6 +825,7 @@ static int parse_config_file(void) { { "Manager", "ReloadLimitIntervalSec", config_parse_sec, 0, &arg_reload_limit_interval_sec }, { "Manager", "ReloadLimitBurst", config_parse_unsigned, 0, &arg_reload_limit_burst }, { "Manager", "DefaultMemoryZSwapWriteback", config_parse_bool, 0, &arg_defaults.memory_zswap_writeback }, + { "Manager", "MinimumUptimeSec", config_parse_sec, 0, &arg_minimum_uptime_usec }, #if ENABLE_SMACK { "Manager", "DefaultSmackProcessLabel", config_parse_string, 0, &arg_defaults.smack_process_label }, #else @@ -1740,6 +1753,9 @@ static int become_shutdown(int objective, int retval) { if (arg_watchdog_device) (void) strv_extendf(&env_block, "WATCHDOG_DEVICE=%s", arg_watchdog_device); + if (arg_minimum_uptime_usec != USEC_INFINITY) + (void) strv_extendf(&env_block, "MINIMUM_UPTIME_USEC=" USEC_FMT, arg_minimum_uptime_usec); + (void) write_boot_or_shutdown_osc("shutdown"); execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); @@ -2830,6 +2846,8 @@ static void reset_arguments(void) { arg_reload_limit_interval_sec = 0; arg_reload_limit_burst = 0; + + arg_minimum_uptime_usec = USEC_INFINITY; } static void determine_default_oom_score_adjust(void) { diff --git a/src/core/system.conf.in b/src/core/system.conf.in index 6000d1702e..ef2c59bd79 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -37,6 +37,7 @@ #RebootWatchdogSec=10min #KExecWatchdogSec=off #WatchdogDevice= +#MinimumUptimeSec=15s #CapabilityBoundingSet= #NoNewPrivileges=no #ProtectSystem=auto diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index 73c6dd6d87..1fb0f422e5 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -51,6 +51,7 @@ #define SYNC_PROGRESS_ATTEMPTS 3 #define SYNC_TIMEOUT_USEC (10*USEC_PER_SEC) +#define DEFAULT_MINIMUM_UPTIME_USEC (15U * USEC_PER_SEC) static const char *arg_verb = NULL; static uint8_t arg_exit_code = 0; @@ -286,14 +287,30 @@ static void init_watchdog(void) { const char *s; int r; - s = getenv("WATCHDOG_DEVICE"); + /* NB: we do not insist on $WATCHDOG_PID being set because old systemd versions didn't set it at all, + * and we want to retain some basic compatibility between an old service manager and a new shutdown + * binary. If it *is* set we'll insist on it being set to 1 however. */ + s = secure_getenv("WATCHDOG_PID"); + if (s) { + pid_t pid; + + r = parse_pid(s, &pid); + if (r < 0) + log_warning_errno(r, "Failed to parse $WATCHDOG_PID, ignoring: %s", s); + else if (pid != getpid_cached()) { + log_warning("$WATCHDOG_PID set, but not to " PID_FMT ", skipping watchdog logic.", getpid_cached()); + return; + } + } + + s = secure_getenv("WATCHDOG_DEVICE"); if (s) { r = watchdog_set_device(s); if (r < 0) - log_warning_errno(r, "Failed to set watchdog device to %s, ignoring: %m", s); + log_warning_errno(r, "Failed to set watchdog device to '%s', ignoring: %m", s); } - s = getenv("WATCHDOG_USEC"); + s = secure_getenv("WATCHDOG_USEC"); if (s) { usec_t usec; @@ -327,6 +344,35 @@ static void notify_supervisor(void) { arg_exit_code, arg_verb); } +static void sleep_until_minimum_uptime(void) { + uint64_t minimum_uptime_usec = DEFAULT_MINIMUM_UPTIME_USEC; + int r; + + const char *e = secure_getenv("MINIMUM_UPTIME_USEC"); + if (e) { + r = safe_atou64(e, &minimum_uptime_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse $MINIMUM_UPTIME_USEC, ignoring: %s", e); + } + + if (minimum_uptime_usec <= 0) /* turned off? */ + return; + + for (;;) { + usec_t n = now(CLOCK_BOOTTIME); + if (n >= minimum_uptime_usec) + break; + + usec_t m = minimum_uptime_usec - n; + log_notice("Delaying shutdown for %s, in order to reach minimum uptime of %s.", + FORMAT_TIMESPAN(m, USEC_PER_SEC), + FORMAT_TIMESPAN(minimum_uptime_usec, USEC_PER_SEC)); + + /* Sleep for up to 3s, then show message again, as a progress indicator. */ + usleep_safe(MIN(m, 3 * USEC_PER_SEC)); + } +} + int main(int argc, char *argv[]) { static const char* const dirs[] = { SYSTEM_SHUTDOWN_PATH, @@ -595,6 +641,12 @@ int main(int argc, char *argv[]) { notify_supervisor(); + /* Enforce the minimum uptime, but don't bother with it in containers, since – unlike on bare metal + * and VMs – the screen output isn't flushed out immediately when we reboot (as OVMF or real PC + * firmwares do) */ + if (!in_container) + sleep_until_minimum_uptime(); + if (streq(arg_verb, "exit")) { if (in_container) { log_info("Exiting container.");