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)))"