shutdown: enforce a minimum uptime to make boot loops less annoying

Fixes: #9453
This commit is contained in:
Lennart Poettering
2026-04-04 22:52:58 +02:00
parent 8cfae74dcd
commit 0874eea302
6 changed files with 82 additions and 0 deletions
+1
View File
@@ -69,6 +69,7 @@
<term><varname>systemd.import_credentials=</varname></term>
<term><varname>systemd.reload_limit_interval_sec=</varname></term>
<term><varname>systemd.reload_limit_burst=</varname></term>
<term><varname>systemd.minimum_uptime_sec=</varname></term>
<listitem>
<para>Parameters understood by the system and service
manager to control system behavior. For details, see
+16
View File
@@ -633,6 +633,22 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>MinimumUptimeSec=</varname></term>
<listitem><para>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 <function>reboot()</function> 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.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
+10
View File
@@ -1061,6 +1061,16 @@
<xi:include href="version-info.xml" xpointer="v186"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.minimum_uptime_sec=</varname></term>
<listitem><para>Takes a time in seconds. Specifies the minimum uptime of the system before the system
shuts down. For more information see the <varname>MinimumUptimeSec=</varname> setting described in
<citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
</variablelist>
<para>For other kernel command line parameters understood by
+18
View File
@@ -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) {
+1
View File
@@ -37,6 +37,7 @@
#RebootWatchdogSec=10min
#KExecWatchdogSec=off
#WatchdogDevice=
#MinimumUptimeSec=15s
#CapabilityBoundingSet=
#NoNewPrivileges=no
#ProtectSystem=auto
+36
View File
@@ -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.");