diff --git a/man/systemd.service.xml b/man/systemd.service.xml index 74c22260bc..154a7a2e66 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -790,62 +790,41 @@ Restart= - Configures whether the service shall be - restarted when the service process exits, is killed, or a - timeout is reached. The service process may be the main - service process, but it may also be one of the processes - specified with ExecStartPre=, - ExecStartPost=, - ExecStop=, - ExecStopPost=, or - ExecReload=. When the death of the process - is a result of systemd operation (e.g. service stop or - restart), the service will not be restarted. Timeouts include - missing the watchdog "keep-alive ping" deadline and a service - start, reload, and stop operation timeouts. + Configures whether the service shall be restarted when the service process exits, + is killed, or a timeout is reached. The service process may be the main service process, but it may + also be one of the processes specified with ExecStartPre=, + ExecStartPost=, ExecStop=, ExecStopPost=, + or ExecReload=. When the death of the process is a result of systemd operation + (e.g. service stop or restart), the service will not be restarted. Timeouts include missing the watchdog + "keep-alive ping" deadline and a service start, reload, and stop operation timeouts. - Takes one of - , - , - , - , - , - , or - . - If set to (the default), the service will - not be restarted. If set to , it - will be restarted only when the service process exits cleanly. + Takes one of , , , + , , , or + . If set to (the default), the service will not be restarted. + If set to , it will be restarted only when the service process exits cleanly. In this context, a clean exit means any of the following: exit code of 0; - for types other than - Type=oneshot, one of the signals - SIGHUP, - SIGINT, - SIGTERM, or - SIGPIPE; + for types other than Type=oneshot, one of the signals + SIGHUP, SIGINT, + SIGTERM, or SIGPIPE; + exit statuses and signals specified in SuccessExitStatus=. - If set to - , the service will be restarted - when the process exits with a non-zero exit code, is - terminated by a signal (including on core dump, but excluding - the aforementioned four signals), when an operation (such as - service reload) times out, and when the configured watchdog - timeout is triggered. If set to , - the service will be restarted when the process is terminated - by a signal (including on core dump, excluding the - aforementioned four signals), when an operation times out, or - when the watchdog timeout is triggered. If set to - , the service will be restarted only - if the service process exits due to an uncaught signal not - specified as a clean exit status. If set to - , the service will be restarted - only if the watchdog timeout for the service expires. If set - to , the service will be restarted - regardless of whether it exited cleanly or not, got terminated - abnormally by a signal, or hit a timeout. + If set to , the service will be restarted when the process exits with + a non-zero exit code, is terminated by a signal (including on core dump, but excluding the aforementioned + four signals), when an operation (such as service reload) times out, and when the configured watchdog + timeout is triggered. If set to , the service will be restarted when + the process is terminated by a signal (including on core dump, excluding the aforementioned four signals), + when an operation times out, or when the watchdog timeout is triggered. If set to , + the service will be restarted only if the service process exits due to an uncaught signal not specified + as a clean exit status. If set to , the service will be restarted + only if the watchdog timeout for the service expires. If set to , the service + will be restarted regardless of whether it exited cleanly or not, got terminated abnormally by + a signal, or hit a timeout. Note that Type=oneshot services will never be restarted + on a clean exit status, i.e. and are rejected + for them. Exit causes and the effect of the <varname>Restart=</varname> settings @@ -1042,12 +1021,15 @@ RestartForceExitStatus= - Takes a list of exit status definitions that, - when returned by the main service process, will force automatic - service restarts, regardless of the restart setting configured - with Restart=. The argument format is - similar to - RestartPreventExitStatus=. + + Takes a list of exit status definitions that, when returned by the main service + process, will force automatic service restarts, regardless of the restart setting configured with + Restart=. The argument format is similar to RestartPreventExitStatus=. + + + Note that for Type=oneshot services, a success exit status will prevent + them from auto-restarting, no matter whether the corresponding exit statuses are listed in this + option or not. diff --git a/src/core/service.c b/src/core/service.c index 2b760eb7aa..d2b8c18af1 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -662,13 +662,9 @@ static int service_verify(Service *s) { if (s->type != SERVICE_ONESHOT && s->exec_command[SERVICE_EXEC_START]->command_next) return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has more than one ExecStart= setting, which is only allowed for Type=oneshot services. Refusing."); - if (s->type == SERVICE_ONESHOT && - !IN_SET(s->restart, SERVICE_RESTART_NO, SERVICE_RESTART_ON_FAILURE, SERVICE_RESTART_ON_ABNORMAL, SERVICE_RESTART_ON_WATCHDOG, SERVICE_RESTART_ON_ABORT)) + if (s->type == SERVICE_ONESHOT && IN_SET(s->restart, SERVICE_RESTART_ALWAYS, SERVICE_RESTART_ON_SUCCESS)) return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has Restart= set to either always or on-success, which isn't allowed for Type=oneshot services. Refusing."); - if (s->type == SERVICE_ONESHOT && !exit_status_set_is_empty(&s->restart_force_status)) - return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has RestartForceExitStatus= set, which isn't allowed for Type=oneshot services. Refusing."); - if (s->type == SERVICE_ONESHOT && s->exit_type == SERVICE_EXIT_CGROUP) return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has ExitType=cgroup set, which isn't allowed for Type=oneshot services. Refusing."); @@ -1901,6 +1897,7 @@ static int cgroup_good(Service *s) { static bool service_shall_restart(Service *s, const char **reason) { assert(s); + assert(reason); /* Don't restart after manual stops */ if (s->forbid_restart) { @@ -1916,6 +1913,13 @@ static bool service_shall_restart(Service *s, const char **reason) { /* Restart if the exit code/status are configured as restart triggers */ if (exit_status_set_test(&s->restart_force_status, s->main_exec_status.code, s->main_exec_status.status)) { + /* Don't allow Type=oneshot services to restart on success. Note that Restart=always/on-success + * is already rejected in service_verify. */ + if (s->type == SERVICE_ONESHOT && s->result == SERVICE_SUCCESS) { + *reason = "service type and exit status"; + return false; + } + *reason = "forced by exit status"; return true; } diff --git a/test/testsuite-23.units/testsuite-23-oneshot-restartforce.sh b/test/testsuite-23.units/testsuite-23-oneshot-restartforce.sh new file mode 100755 index 0000000000..4c9e10b2cb --- /dev/null +++ b/test/testsuite-23.units/testsuite-23-oneshot-restartforce.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if [[ -f "$1" ]]; then + exit 0 +fi + +touch "$1" +exit 2 diff --git a/test/units/testsuite-23.oneshot-restart.sh b/test/units/testsuite-23.oneshot-restart.sh index 433cd69818..bb4d664945 100755 --- a/test/units/testsuite-23.oneshot-restart.sh +++ b/test/units/testsuite-23.oneshot-restart.sh @@ -3,12 +3,15 @@ set -eux set -o pipefail +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + # Test oneshot unit restart on failure # wait this many secs for each test service to succeed in what is being tested MAX_SECS=60 -systemd-analyze log-level debug +systemctl log-level debug # test one: Restart=on-failure should restart the service (! systemd-run --unit=oneshot-restart-one -p Type=oneshot -p Restart=on-failure /bin/bash -c "exit 1") @@ -21,7 +24,7 @@ if [[ "$(systemctl show oneshot-restart-one.service -P NRestarts)" -le 0 ]]; the exit 1 fi -TMP_FILE="/tmp/test-41-oneshot-restart-test" +TMP_FILE="/tmp/test-23-oneshot-restart-test$RANDOM" : >$TMP_FILE @@ -32,7 +35,7 @@ TMP_FILE="/tmp/test-41-oneshot-restart-test" -p StartLimitBurst=3 \ -p Type=oneshot \ -p Restart=on-failure \ - -p ExecStart="/bin/bash -c \"printf a >>$TMP_FILE\"" /bin/bash -c "exit 1") + -p ExecStart="/bin/bash -c 'printf a >>$TMP_FILE'" /bin/bash -c "exit 1") # wait for at least 3 restarts for ((secs = 0; secs < MAX_SECS; secs++)); do @@ -48,5 +51,51 @@ sleep 5 if [[ $(cat $TMP_FILE) != "aaa" ]]; then exit 1 fi +rm "$TMP_FILE" -systemd-analyze log-level info +# Test RestartForceExitStatus=. Note that success exit statuses are meant to be skipped + +TMP_FILE="/tmp/test-23-oneshot-restart-test$RANDOM" +UNIT_NAME="testsuite-23-oneshot-restartforce.service" +ONSUCCESS_UNIT_NAME="testsuite-23-oneshot-restartforce-onsuccess.service" +FIFO_FILE="/tmp/test-23-oneshot-restart-test-fifo" + +cat >"/run/systemd/system/$UNIT_NAME" <"/run/systemd/system/$ONSUCCESS_UNIT_NAME" <$FIFO_FILE' +EOF + +mkfifo "$FIFO_FILE" + +# Pin the unit in memory +systemctl enable "$UNIT_NAME" +# Initial run should fail +(! systemctl start "$UNIT_NAME") +# Wait for OnSuccess= +read -r x <"$FIFO_FILE" +assert_eq "$x" "finished" + +cmp -b <(systemctl show "$UNIT_NAME" -p Result -p NRestarts -p SubState) <