From: Lennart Poettering Date: Tue, 10 Mar 2026 07:06:02 +0000 (+0100) Subject: shutdown: enforce a minimum uptime to make boot loops less annoying X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0874eea302d0ba2d436dcce0b992cdc957190ff4;p=thirdparty%2Fsystemd.git shutdown: enforce a minimum uptime to make boot loops less annoying Fixes: #9453 --- diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 088ce241540..9e24e749b3f 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 172657de65c..e9e7d3d78db 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 06d9102e475..30ae385029b 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 bd065c351d2..d0c0023f899 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 6000d1702e0..ef2c59bd79a 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 9b0328bfca0..1fb0f422e53 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; @@ -343,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, @@ -611,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.");