From 596e447076b27d103a30c26a68626e9820ac705b Mon Sep 17 00:00:00 2001 From: Henri Chain Date: Wed, 24 Feb 2021 16:13:21 +0100 Subject: [PATCH] Reintroduce ExitType This introduces `ExitType=main|cgroup` for services. Similar to how `Type` specifies the launch of a service, `ExitType` is concerned with how systemd determines that a service exited. - If set to `main` (the current behavior), the service manager will consider the unit stopped when the main process exits. - The `cgroup` exit type is meant for applications whose forking model is not known ahead of time and which might not have a specific main process. The service will stay running as long as at least one process in the cgroup is running. This is intended for transient or automatically generated services, such as graphical applications inside of a desktop environment. Motivation for this is #16805. The original PR (#18782) was reverted (#20073) after realizing that the exit status of "the last process in the cgroup" can't reliably be known (#19385) This version instead uses the main process exit status if there is one and just listens to the cgroup empty event otherwise. The advantages of a service with `ExitType=cgroup` over scopes are: - Integrated logging / stdout redirection - Avoids the race / synchronisation issue between launch and scope creation - More extensive use of drop-ins and thus distro-level configuration: by moving from scopes to services we can have drop ins that will affect properties that can only be set during service creation, like `OOMPolicy` and security-related properties - It makes systemd-xdg-autostart-generator usable by fixing [1], as obviously only services can be used in the generator, not scopes. [1] https://bugs.kde.org/show_bug.cgi?id=433299 --- docs/TRANSIENT-SETTINGS.md | 1 + man/org.freedesktop.systemd1.xml | 6 + man/systemd.service.xml | 25 ++++ shell-completion/bash/systemd-run | 2 +- shell-completion/zsh/_systemd-run | 2 +- src/core/dbus-service.c | 6 + src/core/load-fragment-gperf.gperf.in | 1 + src/core/load-fragment.c | 2 + src/core/load-fragment.h | 1 + src/core/service.c | 148 +++++++++++--------- src/core/service.h | 11 ++ src/shared/bus-unit-util.c | 1 + test/TEST-56-EXIT-TYPE/Makefile | 1 + test/TEST-56-EXIT-TYPE/test.sh | 9 ++ test/fuzz/fuzz-unit-file/directives.service | 1 + test/units/testsuite-56.service | 6 + test/units/testsuite-56.sh | 79 +++++++++++ 17 files changed, 234 insertions(+), 68 deletions(-) create mode 120000 test/TEST-56-EXIT-TYPE/Makefile create mode 100755 test/TEST-56-EXIT-TYPE/test.sh create mode 100644 test/units/testsuite-56.service create mode 100755 test/units/testsuite-56.sh diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md index 218a43c6b7..3a293268f4 100644 --- a/docs/TRANSIENT-SETTINGS.md +++ b/docs/TRANSIENT-SETTINGS.md @@ -307,6 +307,7 @@ Most service unit settings are available for transient units. ✓ ExecStartPre= ✓ ExecStop= ✓ ExecStopPost= +✓ ExitType= ✓ FileDescriptorStoreMax= ✓ GuessMainPID= ✓ NonBlocking= diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index c9f71a6dca..af429f354b 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -2304,6 +2304,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s Type = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s ExitType = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s Restart = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s PIDFile = '...'; @@ -2894,6 +2896,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -3420,6 +3424,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + diff --git a/man/systemd.service.xml b/man/systemd.service.xml index 5042066d0d..95cb0aca3d 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -255,6 +255,31 @@ + + ExitType= + + + Specifies when the manager should consider the service to be finished. One of or + : + + + If set to (the default), the service manager + will consider the unit stopped when the main process, which is determined according to the + Type=, exits. Consequently, it cannot be used with + Type=. + + If set to , the service will be considered running as long as at + least one process in the cgroup has not exited. + + + It is generally recommended to use ExitType= when a service has + a known forking model and a main process can reliably be determined. ExitType= + is meant for applications whose forking model is not known ahead of time and which + might not have a specific main process. It is well suited for transient or automatically generated services, + such as graphical applications inside of a desktop environment. + + + RemainAfterExit= diff --git a/shell-completion/bash/systemd-run b/shell-completion/bash/systemd-run index 76b9700f79..c5db8b77bd 100644 --- a/shell-completion/bash/systemd-run +++ b/shell-completion/bash/systemd-run @@ -78,7 +78,7 @@ _systemd_run() { -p|--property) local comps='CPUAccounting= MemoryAccounting= BlockIOAccounting= SendSIGHUP= SendSIGKILL= MemoryLimit= CPUShares= BlockIOWeight= User= Group= - DevicePolicy= KillMode= DeviceAllow= BlockIOReadBandwidth= + DevicePolicy= KillMode= ExitType= DeviceAllow= BlockIOReadBandwidth= BlockIOWriteBandwidth= BlockIODeviceWeight= Nice= Environment= KillSignal= RestartKillSignal= FinalKillSignal= LimitCPU= LimitFSIZE= LimitDATA= LimitSTACK= LimitCORE= LimitRSS= LimitNOFILE= LimitAS= LimitNPROC= diff --git a/shell-completion/zsh/_systemd-run b/shell-completion/zsh/_systemd-run index 934834b94b..7568ed4840 100644 --- a/shell-completion/zsh/_systemd-run +++ b/shell-completion/zsh/_systemd-run @@ -45,7 +45,7 @@ _arguments \ {-p+,--property=}'[Set unit property]:NAME=VALUE:(( \ CPUAccounting= MemoryAccounting= BlockIOAccounting= SendSIGHUP= \ SendSIGKILL= MemoryLimit= CPUShares= BlockIOWeight= User= Group= \ - DevicePolicy= KillMode= DeviceAllow= BlockIOReadBandwidth= \ + DevicePolicy= KillMode= ExitType= DeviceAllow= BlockIOReadBandwidth= \ BlockIOWriteBandwidth= BlockIODeviceWeight= Nice= Environment= \ KillSignal= RestartKillSignal= FinalKillSignal= LimitCPU= LimitFSIZE= LimitDATA= \ LimitSTACK= LimitCORE= LimitRSS= LimitNOFILE= LimitAS= LimitNPROC= \ diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index f42d97afac..e90fe4f596 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -27,6 +27,7 @@ #include "unit.h" static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exit_type, service_exit_type, ServiceExitType); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, ServiceResult); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_notify_access, notify_access, NotifyAccess); @@ -192,6 +193,7 @@ int bus_service_method_mount_image(sd_bus_message *message, void *userdata, sd_b const sd_bus_vtable bus_service_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ExitType", "s", property_get_exit_type, offsetof(Service, exit_type), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Restart", "s", property_get_restart, offsetof(Service, restart), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PIDFile", "s", NULL, offsetof(Service, pid_file), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, offsetof(Service, notify_access), SD_BUS_VTABLE_PROPERTY_CONST), @@ -378,6 +380,7 @@ static int bus_set_transient_std_fd( } static BUS_DEFINE_SET_TRANSIENT_PARSE(notify_access, NotifyAccess, notify_access_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE(service_type, ServiceType, service_type_from_string); +static BUS_DEFINE_SET_TRANSIENT_PARSE(service_exit_type, ServiceExitType, service_exit_type_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE(service_restart, ServiceRestart, service_restart_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE(oom_policy, OOMPolicy, oom_policy_from_string); static BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(bus_name, sd_bus_service_name_is_valid); @@ -415,6 +418,9 @@ static int bus_service_set_transient_property( if (streq(name, "Type")) return bus_set_transient_service_type(u, name, &s->type, message, flags, error); + if (streq(name, "ExitType")) + return bus_set_transient_service_exit_type(u, name, &s->exit_type, message, flags, error); + if (streq(name, "OOMPolicy")) return bus_set_transient_oom_policy(u, name, &s->oom_policy, message, flags, error); diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 5ecba47e31..8912fc197b 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -392,6 +392,7 @@ Service.StartLimitAction, config_parse_emergency_action, Service.FailureAction, config_parse_emergency_action, 0, offsetof(Unit, failure_action) Service.RebootArgument, config_parse_unit_string_printf, 0, offsetof(Unit, reboot_arg) Service.Type, config_parse_service_type, 0, offsetof(Service, type) +Service.ExitType, config_parse_service_exit_type, 0, offsetof(Service, exit_type) Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart) Service.PermissionsStartOnly, config_parse_bool, 0, offsetof(Service, permissions_start_only) Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 62cadaf228..8369a7c187 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -136,6 +136,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_home, protect_home, ProtectHome, " DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_system, protect_system, ProtectSystem, "Failed to parse protect system value"); DEFINE_CONFIG_PARSE_ENUM(config_parse_runtime_preserve_mode, exec_preserve_mode, ExecPreserveMode, "Failed to parse runtime directory preserve mode"); DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_service_exit_type, service_exit_type, ServiceExitType, "Failed to parse service exit type"); DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier"); DEFINE_CONFIG_PARSE_ENUM(config_parse_service_timeout_failure_mode, service_timeout_failure_mode, ServiceTimeoutFailureMode, "Failed to parse timeout failure mode"); DEFINE_CONFIG_PARSE_ENUM(config_parse_socket_bind, socket_address_bind_ipv6_only_or_bool, SocketAddressBindIPv6Only, "Failed to parse bind IPv6 only value"); @@ -6194,6 +6195,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_unit_deps, "UNIT [...]" }, { config_parse_exec, "PATH [ARGUMENT [...]]" }, { config_parse_service_type, "SERVICETYPE" }, + { config_parse_service_exit_type, "SERVICEEXITTYPE" }, { config_parse_service_restart, "SERVICERESTART" }, { config_parse_service_timeout_failure_mode, "TIMEOUTMODE" }, { config_parse_kill_mode, "KILLMODE" }, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 9ff550410f..a37eb329e3 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -37,6 +37,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_service_timeout); CONFIG_PARSER_PROTOTYPE(config_parse_service_timeout_abort); CONFIG_PARSER_PROTOTYPE(config_parse_service_timeout_failure_mode); CONFIG_PARSER_PROTOTYPE(config_parse_service_type); +CONFIG_PARSER_PROTOTYPE(config_parse_service_exit_type); CONFIG_PARSER_PROTOTYPE(config_parse_service_restart); CONFIG_PARSER_PROTOTYPE(config_parse_socket_bindtodevice); CONFIG_PARSER_PROTOTYPE(config_parse_exec_output); diff --git a/src/core/service.c b/src/core/service.c index 4b99311e5a..83cbc9f489 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -602,6 +602,9 @@ static int service_verify(Service *s) { 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 RestartForceStatus= 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."); + if (s->type == SERVICE_DBUS && !s->bus_name) return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service is of type D-Bus but no D-Bus service name has been specified. Refusing."); @@ -3289,6 +3292,9 @@ static void service_notify_cgroup_empty_event(Unit *u) { break; } + if (s->exit_type == SERVICE_EXIT_CGROUP && main_pid_good(s) <= 0) + service_enter_start_post(s); + _fallthrough_; case SERVICE_START_POST: if (s->pid_file_pathspec && @@ -3477,79 +3483,82 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { service_run_next_main(s); } else { - - /* The service exited, so the service is officially gone. */ s->main_command = NULL; - switch (s->state) { + /* Services with ExitType=cgroup do not act on main PID exiting, + * unless the cgroup is already empty */ + if (s->exit_type == SERVICE_EXIT_MAIN || cgroup_good(s) <= 0) { + /* The service exited, so the service is officially gone. */ + switch (s->state) { - case SERVICE_START_POST: - case SERVICE_RELOAD: - /* If neither main nor control processes are running then - * the current state can never exit cleanly, hence immediately - * terminate the service. */ - if (control_pid_good(s) <= 0) - service_enter_stop(s, f); + case SERVICE_START_POST: + case SERVICE_RELOAD: + /* If neither main nor control processes are running then + * the current state can never exit cleanly, hence immediately + * terminate the service. */ + if (control_pid_good(s) <= 0) + service_enter_stop(s, f); - /* Otherwise need to wait until the operation is done. */ - break; - - case SERVICE_STOP: - /* Need to wait until the operation is done. */ - break; - - case SERVICE_START: - if (s->type == SERVICE_ONESHOT) { - /* This was our main goal, so let's go on */ - if (f == SERVICE_SUCCESS) - service_enter_start_post(s); - else - service_enter_signal(s, SERVICE_STOP_SIGTERM, f); + /* Otherwise need to wait until the operation is done. */ break; - } else if (s->type == SERVICE_NOTIFY) { - /* Only enter running through a notification, so that the - * SERVICE_START state signifies that no ready notification - * has been received */ - if (f != SERVICE_SUCCESS) - service_enter_signal(s, SERVICE_STOP_SIGTERM, f); - else if (!s->remain_after_exit || s->notify_access == NOTIFY_MAIN) - /* The service has never been and will never be active */ - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_PROTOCOL); + + case SERVICE_STOP: + /* Need to wait until the operation is done. */ break; + + case SERVICE_START: + if (s->type == SERVICE_ONESHOT) { + /* This was our main goal, so let's go on */ + if (f == SERVICE_SUCCESS) + service_enter_start_post(s); + else + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); + break; + } else if (s->type == SERVICE_NOTIFY) { + /* Only enter running through a notification, so that the + * SERVICE_START state signifies that no ready notification + * has been received */ + if (f != SERVICE_SUCCESS) + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); + else if (!s->remain_after_exit || s->notify_access == NOTIFY_MAIN) + /* The service has never been and will never be active */ + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_PROTOCOL); + break; + } + + _fallthrough_; + case SERVICE_RUNNING: + service_enter_running(s, f); + break; + + case SERVICE_STOP_WATCHDOG: + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + + if (control_pid_good(s) <= 0) + service_enter_stop_post(s, f); + + /* If there is still a control process, wait for that first */ + break; + + case SERVICE_STOP_POST: + + if (control_pid_good(s) <= 0) + service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + + break; + + case SERVICE_FINAL_WATCHDOG: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + + if (control_pid_good(s) <= 0) + service_enter_dead(s, f, true); + break; + + default: + assert_not_reached(); } - - _fallthrough_; - case SERVICE_RUNNING: - service_enter_running(s, f); - break; - - case SERVICE_STOP_WATCHDOG: - case SERVICE_STOP_SIGTERM: - case SERVICE_STOP_SIGKILL: - - if (control_pid_good(s) <= 0) - service_enter_stop_post(s, f); - - /* If there is still a control process, wait for that first */ - break; - - case SERVICE_STOP_POST: - - if (control_pid_good(s) <= 0) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); - - break; - - case SERVICE_FINAL_WATCHDOG: - case SERVICE_FINAL_SIGTERM: - case SERVICE_FINAL_SIGKILL: - - if (control_pid_good(s) <= 0) - service_enter_dead(s, f, true); - break; - - default: - assert_not_reached(); } } @@ -4513,6 +4522,13 @@ static const char* const service_type_table[_SERVICE_TYPE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); +static const char* const service_exit_type_table[_SERVICE_EXIT_TYPE_MAX] = { + [SERVICE_EXIT_MAIN] = "main", + [SERVICE_EXIT_CGROUP] = "cgroup", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_exit_type, ServiceExitType); + static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = { [SERVICE_EXEC_CONDITION] = "ExecCondition", [SERVICE_EXEC_START_PRE] = "ExecStartPre", diff --git a/src/core/service.h b/src/core/service.h index eaad95df6d..70ce70fba5 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -35,6 +35,13 @@ typedef enum ServiceType { _SERVICE_TYPE_INVALID = -EINVAL, } ServiceType; +typedef enum ServiceExitType { + SERVICE_EXIT_MAIN, /* we consider the main PID when deciding if the service exited */ + SERVICE_EXIT_CGROUP, /* we wait for the last process in the cgroup to exit */ + _SERVICE_EXIT_TYPE_MAX, + _SERVICE_EXIT_TYPE_INVALID = -EINVAL, +} ServiceExitType; + typedef enum ServiceExecCommand { SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE, @@ -97,6 +104,7 @@ struct Service { Unit meta; ServiceType type; + ServiceExitType exit_type; ServiceRestart restart; ExitStatusSet restart_prevent_status; ExitStatusSet restart_force_status; @@ -227,6 +235,9 @@ ServiceRestart service_restart_from_string(const char *s) _pure_; const char* service_type_to_string(ServiceType i) _const_; ServiceType service_type_from_string(const char *s) _pure_; +const char* service_exit_type_to_string(ServiceExitType i) _const_; +ServiceExitType service_exit_type_from_string(const char *s) _pure_; + const char* service_exec_command_to_string(ServiceExecCommand i) _const_; ServiceExecCommand service_exec_command_from_string(const char *s) _pure_; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index dbd33420be..56aa255f97 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2130,6 +2130,7 @@ static int bus_append_service_property(sd_bus_message *m, const char *field, con if (STR_IN_SET(field, "PIDFile", "Type", + "ExitType", "Restart", "BusName", "NotifyAccess", diff --git a/test/TEST-56-EXIT-TYPE/Makefile b/test/TEST-56-EXIT-TYPE/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-56-EXIT-TYPE/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-56-EXIT-TYPE/test.sh b/test/TEST-56-EXIT-TYPE/test.sh new file mode 100755 index 0000000000..0f84dca1ba --- /dev/null +++ b/test/TEST-56-EXIT-TYPE/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +TEST_DESCRIPTION="test ExitType=cgroup" + +# shellcheck source=test/test-functions +. "${TEST_BASE_DIR:?}/test-functions" + +do_test "$@" diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service index 4615a2229a..e8bac7250a 100644 --- a/test/fuzz/fuzz-unit-file/directives.service +++ b/test/fuzz/fuzz-unit-file/directives.service @@ -161,6 +161,7 @@ ExecStartPost= ExecStartPre= ExecStop= ExecStopPost= +ExitType= ExtensionImages= FailureAction= FileDescriptorStoreMax= diff --git a/test/units/testsuite-56.service b/test/units/testsuite-56.service new file mode 100644 index 0000000000..d8ad589ca0 --- /dev/null +++ b/test/units/testsuite-56.service @@ -0,0 +1,6 @@ +[Unit] +Description=TEST-56-EXIT-TYPE + +[Service] +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-56.sh b/test/units/testsuite-56.sh new file mode 100755 index 0000000000..b167320615 --- /dev/null +++ b/test/units/testsuite-56.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -eux + +systemd-analyze log-level debug + +# Multiple level process tree, parent process stays up +cat >/tmp/test56-exit-cgroup.sh < sleep +sleep infinity & +disown + +# process tree: systemd -> bash -> bash -> sleep +((sleep infinity); true) & + +systemd-notify --ready + +# process tree: systemd -> bash -> sleep +sleep infinity +EOF +chmod +x /tmp/test56-exit-cgroup.sh + +# service should be stopped cleanly +systemd-run --wait --unit=one -p Type=notify -p ExitType=cgroup \ + -p ExecStartPost='bash -c "systemctl stop one &"' \ + /tmp/test56-exit-cgroup.sh + +# same thing with a truthy exec condition +systemd-run --wait --unit=two -p Type=notify -p ExitType=cgroup \ + -p ExecCondition=true \ + -p ExecStartPost='bash -c "systemctl stop two &"' \ + /tmp/test56-exit-cgroup.sh + +# false exec condition: systemd-run should exit immediately with status code: 1 +systemd-run --wait --unit=three -p Type=notify -p ExitType=cgroup \ + -p ExecCondition=false \ + /tmp/test56-exit-cgroup.sh \ + && { echo 'unexpected success'; exit 1; } + +# service should exit uncleanly (main process exits with SIGKILL) +systemd-run --wait --unit=four -p Type=notify -p ExitType=cgroup \ + -p ExecStartPost='bash -c "systemctl kill --signal 9 four &"' \ + /tmp/test56-exit-cgroup.sh \ + && { echo 'unexpected success'; exit 1; } + + +# Multiple level process tree, parent process exits quickly +cat >/tmp/test56-exit-cgroup-parentless.sh < sleep +sleep infinity & + +# process tree: systemd -> bash -> sleep +((sleep infinity); true) & + +systemd-notify --ready +EOF +chmod +x /tmp/test56-exit-cgroup-parentless.sh + +# service should be stopped cleanly +systemd-run --wait --unit=five -p Type=notify -p ExitType=cgroup \ + -p ExecStartPost='bash -c "systemctl stop five &"' \ + /tmp/test56-exit-cgroup-parentless.sh + +# service should still exit cleanly despite SIGKILL (the main process already exited cleanly) +systemd-run --wait --unit=six -p Type=notify -p ExitType=cgroup \ + -p ExecStartPost='bash -c "systemctl kill --signal 9 six &"' \ + /tmp/test56-exit-cgroup-parentless.sh + + +systemd-analyze log-level info + +echo OK >/testok + +exit 0