1
0
mirror of https://github.com/systemd/systemd.git synced 2025-02-03 17:47:28 +03:00

Merge pull request #31149 from YHNdnzj/restart-force-oneshot

core/service: allow RestartForceExitStatus= for oneshot service
This commit is contained in:
Frantisek Sumsal 2024-02-12 10:20:09 +01:00 committed by GitHub
commit 3588c510d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 110 additions and 64 deletions

View File

@ -790,62 +790,41 @@
<varlistentry>
<term><varname>Restart=</varname></term>
<listitem><para>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 <varname>ExecStartPre=</varname>,
<varname>ExecStartPost=</varname>,
<varname>ExecStop=</varname>,
<varname>ExecStopPost=</varname>, or
<varname>ExecReload=</varname>. 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.</para>
<listitem><para>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 <varname>ExecStartPre=</varname>,
<varname>ExecStartPost=</varname>, <varname>ExecStop=</varname>, <varname>ExecStopPost=</varname>,
or <varname>ExecReload=</varname>. 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.</para>
<para>Takes one of
<option>no</option>,
<option>on-success</option>,
<option>on-failure</option>,
<option>on-abnormal</option>,
<option>on-watchdog</option>,
<option>on-abort</option>, or
<option>always</option>.
If set to <option>no</option> (the default), the service will
not be restarted. If set to <option>on-success</option>, it
will be restarted only when the service process exits cleanly.
<para>Takes one of <option>no</option>, <option>on-success</option>, <option>on-failure</option>,
<option>on-abnormal</option>, <option>on-watchdog</option>, <option>on-abort</option>, or
<option>always</option>. If set to <option>no</option> (the default), the service will not be restarted.
If set to <option>on-success</option>, it will be restarted only when the service process exits cleanly.
In this context, a clean exit means any of the following:
<itemizedlist>
<listitem><simpara>exit code of 0;</simpara></listitem>
<listitem><simpara>for types other than
<varname>Type=oneshot</varname>, one of the signals
<constant>SIGHUP</constant>,
<constant>SIGINT</constant>,
<constant>SIGTERM</constant>, or
<constant>SIGPIPE</constant>;</simpara></listitem>
<listitem><simpara>for types other than <varname>Type=oneshot</varname>, one of the signals
<constant>SIGHUP</constant>, <constant>SIGINT</constant>,
<constant>SIGTERM</constant>, or <constant>SIGPIPE</constant>;
</simpara></listitem>
<listitem><simpara>exit statuses and signals specified in
<varname>SuccessExitStatus=</varname>.</simpara></listitem>
</itemizedlist>
If set to
<option>on-failure</option>, 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 <option>on-abnormal</option>,
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
<option>on-abort</option>, 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
<option>on-watchdog</option>, the service will be restarted
only if the watchdog timeout for the service expires. If set
to <option>always</option>, the service will be restarted
regardless of whether it exited cleanly or not, got terminated
abnormally by a signal, or hit a timeout.</para>
If set to <option>on-failure</option>, 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 <option>on-abnormal</option>, 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 <option>on-abort</option>,
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 <option>on-watchdog</option>, the service will be restarted
only if the watchdog timeout for the service expires. If set to <option>always</option>, 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 <varname>Type=oneshot</varname> services will never be restarted
on a clean exit status, i.e. <option>always</option> and <option>on-success</option> are rejected
for them.</para>
<table>
<title>Exit causes and the effect of the <varname>Restart=</varname> settings</title>
@ -1042,12 +1021,15 @@
<varlistentry>
<term><varname>RestartForceExitStatus=</varname></term>
<listitem><para>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 <varname>Restart=</varname>. The argument format is
similar to
<varname>RestartPreventExitStatus=</varname>.</para>
<listitem><para>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
<varname>Restart=</varname>. The argument format is similar to <varname>RestartPreventExitStatus=</varname>.
</para>
<para>Note that for <varname>Type=oneshot</varname> 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.</para>
<xi:include href="version-info.xml" xpointer="v215"/></listitem>
</varlistentry>

View File

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

View File

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

View File

@ -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" <<EOF
[Unit]
OnSuccess=$ONSUCCESS_UNIT_NAME
[Service]
Type=oneshot
RestartForceExitStatus=0 2
ExecStart=/usr/lib/systemd/tests/testdata/testsuite-23.units/testsuite-23-oneshot-restartforce.sh "$TMP_FILE"
[Install]
WantedBy=multi-user.target
EOF
cat >"/run/systemd/system/$ONSUCCESS_UNIT_NAME" <<EOF
[Service]
Type=oneshot
ExecStart=bash -c 'echo finished >$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) <<EOF
Result=success
NRestarts=1
SubState=dead
EOF
systemctl disable "$UNIT_NAME"
rm "$TMP_FILE" /run/systemd/system/{"$UNIT_NAME","$ONSUCCESS_UNIT_NAME"} "$FIFO_FILE"
systemctl log-level info