diff --git a/TODO b/TODO
index d8de3b4a23..64fe9559f7 100644
--- a/TODO
+++ b/TODO
@@ -82,6 +82,14 @@ Features:
the quota of a the user indicated in User= via unit file settings, like the
other resource management concepts. Would mix nicely with DynamicUser=1
+* Introduce "exit" as an EmergencyAction value, and allow to configure a
+ per-unit success/failure exit code to configure. This would be useful for
+ running commands inside of services inside of containers, which could then
+ propagate their failure state all the way up.
+
+* In DynamicUser= mode: before selecting a UID, use disk quota APIs on relevant
+ disks to see if the UID is already in use.
+
* add dissect_image_warn() as a wrapper around dissect_image() that prints
friendly log messages for the returned errors, so that we don't have to
duplicate that in nspawn, systemd-dissect and PID 1.
diff --git a/man/systemd.service.xml b/man/systemd.service.xml
index fe83581b6e..ada92369e1 100644
--- a/man/systemd.service.xml
+++ b/man/systemd.service.xml
@@ -893,14 +893,6 @@
effect.
-
- FailureAction=
- Configure the action to take when the service enters a failed state. Takes the same values as
- the unit setting StartLimitAction= and executes the same actions (see
- systemd.unit5). Defaults to
- .
-
-
FileDescriptorStoreMax=
Configure how many file descriptors may be stored in the service manager for the service using
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index 6047524623..9c40562e08 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -877,11 +877,21 @@
semantics. Defaults to .
+
+ FailureAction=
+ SuccessAction=
+ Configure the action to take when the unit stops and enters a failed state or inactive
+ state. Takes the same values as the setting StartLimitAction= setting and executes the same
+ actions (see
+ systemd.unit5). Both options
+ default to .
+
+
RebootArgument=
Configure the optional argument for the
reboot2 system call if
- StartLimitAction= or a service's FailureAction= is a reboot action. This
+ StartLimitAction= or FailureAction= is a reboot action. This
works just like the optional argument to systemctl reboot command.
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
index 6b83982cf8..7b6cb395d8 100644
--- a/src/core/dbus-service.c
+++ b/src/core/dbus-service.c
@@ -51,7 +51,6 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_PROPERTY("RuntimeMaxUSec", "t", bus_property_get_usec, offsetof(Service, runtime_max_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("WatchdogUSec", "t", bus_property_get_usec, offsetof(Service, watchdog_usec), SD_BUS_VTABLE_PROPERTY_CONST),
BUS_PROPERTY_DUAL_TIMESTAMP("WatchdogTimestamp", offsetof(Service, watchdog_timestamp), 0),
- SD_BUS_PROPERTY("FailureAction", "s", property_get_emergency_action, offsetof(Service, emergency_action), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PermissionsStartOnly", "b", bus_property_get_bool, offsetof(Service, permissions_start_only), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootDirectoryStartOnly", "b", bus_property_get_bool, offsetof(Service, root_directory_start_only), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RemainAfterExit", "b", bus_property_get_bool, offsetof(Service, remain_after_exit), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -82,6 +81,7 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
SD_BUS_PROPERTY("StartLimitAction", "s", property_get_emergency_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
+ SD_BUS_PROPERTY("FailureAction", "s", property_get_emergency_action, offsetof(Unit, failure_action), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
SD_BUS_VTABLE_END
};
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
index 7e5c889830..d4bdf1760a 100644
--- a/src/core/dbus-unit.c
+++ b/src/core/dbus-unit.c
@@ -798,6 +798,8 @@ const sd_bus_vtable bus_unit_vtable[] = {
SD_BUS_PROPERTY("StartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StartLimitAction", "s", property_get_emergency_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("FailureAction", "s", property_get_emergency_action, offsetof(Unit, failure_action), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("SuccessAction", "s", property_get_emergency_action, offsetof(Unit, success_action), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("InvocationID", "ay", bus_property_get_id128, offsetof(Unit, invocation_id), 0),
SD_BUS_PROPERTY("CollectMode", "s", property_get_collect_mode, offsetof(Unit, collect_mode), 0),
@@ -1470,6 +1472,30 @@ static int bus_unit_set_transient_property(
return 1;
+ } else if (STR_IN_SET(name, "FailureAction", "SuccessAction")) {
+ EmergencyAction action;
+ const char *s;
+
+ r = sd_bus_message_read(message, "s", &s);
+ if (r < 0)
+ return r;
+
+ action = emergency_action_from_string(s);
+ if (action < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid emergency action: %s", s);
+
+ if (mode != UNIT_CHECK) {
+
+ if (streq(name, "FailureAction"))
+ u->failure_action = action;
+ else
+ u->success_action = action;
+
+ unit_write_drop_in_format(u, mode, name, "%s=%s", name, emergency_action_to_string(action));
+ }
+
+ return 1;
+
} else if (streq(name, "AddRef")) {
int b;
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 73b13977ed..716145a8ff 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -223,6 +223,8 @@ m4_dnl The following is a legacy alias name for compatibility
Unit.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval)
Unit.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst)
Unit.StartLimitAction, config_parse_emergency_action, 0, offsetof(Unit, start_limit_action)
+Unit.FailureAction, config_parse_emergency_action, 0, offsetof(Unit, failure_action)
+Unit.SuccessAction, config_parse_emergency_action, 0, offsetof(Unit, success_action)
Unit.RebootArgument, config_parse_unit_string_printf, 0, offsetof(Unit, reboot_arg)
Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, conditions)
Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, conditions)
@@ -281,12 +283,12 @@ Service.TimeoutStartSec, config_parse_service_timeout, 0,
Service.TimeoutStopSec, config_parse_service_timeout, 0, 0
Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec)
Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec)
-m4_dnl The following three only exist for compatibility, they moved into Unit, see above
+m4_dnl The following five only exist for compatibility, they moved into Unit, see above
Service.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval)
Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst)
Service.StartLimitAction, config_parse_emergency_action, 0, offsetof(Unit, start_limit_action)
+Service.FailureAction, config_parse_emergency_action, 0, offsetof(Unit, failure_action)
Service.RebootArgument, config_parse_unit_path_printf, 0, offsetof(Unit, reboot_arg)
-Service.FailureAction, config_parse_emergency_action, 0, offsetof(Service, emergency_action)
Service.Type, config_parse_service_type, 0, offsetof(Service, type)
Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart)
Service.PermissionsStartOnly, config_parse_bool, 0, offsetof(Service, permissions_start_only)
diff --git a/src/core/service.c b/src/core/service.c
index 445d1becc1..1dfde82816 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1532,9 +1532,6 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD);
- if (s->result != SERVICE_SUCCESS)
- emergency_action(UNIT(s)->manager, s->emergency_action, UNIT(s)->reboot_arg, "service failed");
-
if (allow_restart && service_shall_restart(s)) {
r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec));
diff --git a/src/core/service.h b/src/core/service.h
index a529f48a63..a995c89605 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -174,8 +174,6 @@ struct Service {
char *status_text;
int status_errno;
- EmergencyAction emergency_action;
-
UnitRef accept_socket;
sd_event_source *timer_event_source;
diff --git a/src/core/unit.c b/src/core/unit.c
index d5e6b3891b..386238a525 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -1181,6 +1181,11 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
STRV_FOREACH(j, u->dropin_paths)
fprintf(f, "%s\tDropIn Path: %s\n", prefix, *j);
+ if (u->failure_action != EMERGENCY_ACTION_NONE)
+ fprintf(f, "%s\tFailure Action: %s\n", prefix, emergency_action_to_string(u->failure_action));
+ if (u->success_action != EMERGENCY_ACTION_NONE)
+ fprintf(f, "%s\tSuccess Action: %s\n", prefix, emergency_action_to_string(u->success_action));
+
if (u->job_timeout != USEC_INFINITY)
fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0));
@@ -2503,6 +2508,11 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
* units go directly from starting to inactive,
* without ever entering started.) */
unit_check_binds_to(u);
+
+ if (os != UNIT_FAILED && ns == UNIT_FAILED)
+ (void) emergency_action(u->manager, u->failure_action, u->reboot_arg, "unit failed");
+ else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && ns == UNIT_INACTIVE)
+ (void) emergency_action(u->manager, u->success_action, u->reboot_arg, "unit succeeded");
}
unit_add_to_dbus_queue(u);
diff --git a/src/core/unit.h b/src/core/unit.h
index 2b11a285d4..ae3dcfa840 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -248,6 +248,9 @@ struct Unit {
/* Put a ratelimit on unit starting */
RateLimit start_limit;
EmergencyAction start_limit_action;
+
+ EmergencyAction failure_action;
+ EmergencyAction success_action;
char *reboot_arg;
/* Make sure we never enter endless loops with the check unneeded logic, or the BindsTo= logic */
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index a607233038..78b9a68348 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -408,7 +408,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
"RootDirectory", "SyslogIdentifier", "ProtectSystem",
"ProtectHome", "SELinuxContext", "Restart", "RootImage",
"NotifyAccess", "RuntimeDirectoryPreserve", "Personality",
- "KeyringMode", "CollectMode"))
+ "KeyringMode", "CollectMode", "FailureAction", "SuccessAction"))
r = sd_bus_message_append(m, "v", "s", eq);
else if (streq(field, "StandardInputData")) {
diff --git a/test/TEST-18-FAILUREACTION/Makefile b/test/TEST-18-FAILUREACTION/Makefile
new file mode 100644
index 0000000000..b895de8bcb
--- /dev/null
+++ b/test/TEST-18-FAILUREACTION/Makefile
@@ -0,0 +1,4 @@
+include ../Makefile.guess
+
+all setup clean run:
+ @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@
diff --git a/test/TEST-18-FAILUREACTION/test.sh b/test/TEST-18-FAILUREACTION/test.sh
new file mode 100755
index 0000000000..e48ba9bac3
--- /dev/null
+++ b/test/TEST-18-FAILUREACTION/test.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+set -e
+TEST_DESCRIPTION="FailureAction= operation"
+
+. $TEST_BASE_DIR/test-functions
+QEMU_TIMEOUT=180
+
+test_setup() {
+ create_empty_image
+ mkdir -p $TESTDIR/root
+ mount ${LOOPDEV}p1 $TESTDIR/root
+
+ (
+ LOG_LEVEL=5
+ eval $(udevadm info --export --query=env --name=${LOOPDEV}p2)
+
+ setup_basic_environment
+
+ # setup the testsuite service
+ cat >$initdir/etc/systemd/system/testsuite.service < /firstphase
+ systemd-run --wait -p SuccessAction=reboot true
+else
+ echo OK > /testok
+ systemd-run --wait -p FailureAction=poweroff false
+fi
+
+sleep infinity
diff --git a/test/test-functions b/test/test-functions
index 0413e166f2..853ef5d312 100644
--- a/test/test-functions
+++ b/test/test-functions
@@ -21,7 +21,7 @@ if ! ROOTLIBDIR=$(pkg-config --variable=systemdutildir systemd); then
ROOTLIBDIR=/usr/lib/systemd
fi
-BASICTOOLS="sh bash setsid loadkeys setfont login sulogin gzip sleep echo mount umount cryptsetup date dmsetup modprobe sed cmp tee rm"
+BASICTOOLS="sh bash setsid loadkeys setfont login sulogin gzip sleep echo mount umount cryptsetup date dmsetup modprobe sed cmp tee rm true false"
DEBUGTOOLS="df free ls stty cat ps ln ip route dmesg dhclient mkdir cp ping dhclient strace less grep id tty touch du sort hostname find"
STATEDIR="${BUILD_DIR:-.}/test/$(basename $(dirname $(realpath $0)))"