]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Introduce ExitType
authorHenri Chain <henri.chain@enioka.com>
Wed, 24 Feb 2021 15:13:21 +0000 (16:13 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 31 Mar 2021 08:26:07 +0000 (10:26 +0200)
18 files changed:
docs/TRANSIENT-SETTINGS.md
man/org.freedesktop.systemd1.xml
man/systemd.service.xml
shell-completion/bash/systemd-run
shell-completion/zsh/_systemd-run
src/core/dbus-service.c
src/core/load-fragment-gperf.gperf.m4
src/core/load-fragment.c
src/core/load-fragment.h
src/core/service.c
src/core/service.h
src/shared/bus-unit-util.c
src/xdg-autostart-generator/xdg-autostart-service.c
test/TEST-56-EXIT-TYPE/Makefile [new symlink]
test/TEST-56-EXIT-TYPE/test.sh [new file with mode: 0755]
test/fuzz/fuzz-unit-file/directives.service
test/units/testsuite-56.service [new file with mode: 0644]
test/units/testsuite-56.sh [new file with mode: 0755]

index 9f69a3162a0d8e7cb1c08a51eba8f26dee2479af..9fa856ec21c91ecc6e42fd8f558f71a9714dbc37 100644 (file)
@@ -303,6 +303,7 @@ Most service unit settings are available for transient units.
 ✓ ExecStartPre=
 ✓ ExecStop=
 ✓ ExecStopPost=
+✓ ExitType=
 ✓ FileDescriptorStoreMax=
 ✓ GuessMainPID=
 ✓ NonBlocking=
index aff43217e1690703a7bcaeaafd900f3b88651668..614871bce206ec72004e6973fa8e3e3fb57fca66 100644 (file)
@@ -2250,6 +2250,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 = '...';
@@ -2808,6 +2810,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--property Type is not documented!-->
 
+    <!--property ExitType is not documented!-->
+
     <!--property Restart is not documented!-->
 
     <!--property PIDFile is not documented!-->
@@ -3320,6 +3324,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <variablelist class="dbus-property" generated="True" extra-ref="Type"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="ExitType"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="Restart"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="PIDFile"/>
index 350bc5f8e5bca5054c2702ed046ab9bfbe2d65f4..50d1a1d85d638f2404c2b943df6226bb48924a76 100644 (file)
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>ExitType=</varname></term>
+
+        <listitem>
+          <para>Configures the process exit type for this service unit. One of <option>main</option> or
+          <option>cgroup</option>:</para>
+
+          <itemizedlist>
+            <listitem><para>If set to <option>main</option> (the default), the service manager
+            will consider the unit stopped when the main process, which is determined according to the `Type`, exits.
+            </para></listitem>
+
+            <listitem><para>The <option>cgroup</option> 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. The exit status of the service is that of the last
+            process in the cgroup to exit.</para></listitem>
+          </itemizedlist>
+
+          <para>It is generally recommended to use <varname>ExitType=</varname><option>main</option> when a service has
+          a known forking model and a main process can reliably be determined. <varname>ExitType=</varname>
+          <option>cgroup</option> is well suited for transient or automatically generated services, such as graphical
+          applications inside of a desktop environment.</para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>RemainAfterExit=</varname></term>
 
index 76b9700f79fd5e8dd0dbc4d7cee1459810e52eae..c5db8b77bd8f3dbee88a07494fdd47a8360e2ff0 100644 (file)
@@ -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=
index bd70c13ba38c4505005cb156dc1ecb7b2fdc009b..d7af226105e7ad86180ec4f4b179373e2923e7b2 100644 (file)
@@ -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= \
index f7cdb51eba883c83b8472173dc01342f47326788..73906ebab73d23862b6ae4e71e1f752cb20fe479 100644 (file)
@@ -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),
@@ -377,6 +379,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);
@@ -414,6 +417,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);
 
index 21bbcffe41af6d84ecbcc4f31c287a318b4dc579..5ef785c0deea156e4606834653ab5282c47faeb1 100644 (file)
@@ -359,6 +359,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)
index c6fc4fe083f989f74956979430c0ee69f2bf1222..ead7f7bf117961429be4669a5c85da4438fbb785 100644 (file)
@@ -130,6 +130,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");
@@ -5748,6 +5749,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" },
index b8a6d5feadc57fed460cfea1bf516c8e6c1af75e..4746a8a792b2674f841bb3c9673be6b359d099bc 100644 (file)
@@ -32,6 +32,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);
index df3ed05cfc6e8e6114af3b6f52dd0c45816dd858..550db4063126337f8e03f1c3ad1c3ba16612441d 100644 (file)
@@ -1621,18 +1621,25 @@ static int control_pid_good(Service *s) {
         return s->control_pid > 0;
 }
 
-static int cgroup_good(Service *s) {
-        int r;
-
+static int cgroup_empty(Service *s) {
         assert(s);
 
-        /* Returns 0 if the cgroup is empty or doesn't exist, > 0 if it is exists and is populated, < 0 if we can't
-         * figure it out */
+        /* Returns 0 if there is no cgroup, > 0 if is empty or doesn't exist, < 0 if we can't figure it out */
 
         if (!UNIT(s)->cgroup_path)
                 return 0;
 
-        r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path);
+        return cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path);
+}
+
+
+static int cgroup_good(Service *s) {
+        int r;
+
+        /* Returns 0 if the cgroup is empty or doesn't exist, > 0 if it is exists and is populated, < 0 if we can't
+         * figure it out */
+
+        r = cgroup_empty(s);
         if (r < 0)
                 return r;
 
@@ -3398,7 +3405,14 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
         else
                 assert_not_reached("Unknown code");
 
-        if (s->main_pid == pid) {
+        /* Services with ExitType=cgroup ignore the main PID for purposes of exit status */
+        if (s->exit_type == SERVICE_EXIT_CGROUP && s->main_pid == pid) {
+                service_unwatch_main_pid(s);
+                s->main_pid_known = false;
+        }
+
+        if ((s->exit_type == SERVICE_EXIT_MAIN && s->main_pid == pid) ||
+            (s->exit_type == SERVICE_EXIT_CGROUP && cgroup_empty(s) && !control_pid_good(s))) {
                 /* Forking services may occasionally move to a new PID.
                  * As long as they update the PID file before exiting the old
                  * PID, they're fine. */
@@ -3431,7 +3445,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
 
                 unit_log_process_exit(
                                 u,
-                                "Main process",
+                                s->exit_type == SERVICE_EXIT_CGROUP ? "Last process" : "Main process",
                                 service_exec_command_to_string(SERVICE_EXEC_START),
                                 f == SERVICE_SUCCESS,
                                 code, status);
@@ -4448,6 +4462,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",
index af474aa40e90a0ebe6d7aac8a47cfafb987702b9..92c1caf79491131c8fa8fc67af7cc6b0622057c2 100644 (file)
@@ -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;
@@ -226,6 +234,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_;
 
index a75178068b507f661985eaa77f4e6b43792f0545..84d4729334c682d91da077b3b6936a1c79c1fc87 100644 (file)
@@ -1947,6 +1947,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",
index 1528432f43329fa9edeeb59bb1f089e531ba609b..00d8b76539844a0ab218c7e8db454c9acd59192c 100644 (file)
@@ -599,6 +599,7 @@ int xdg_autostart_service_generate_unit(
         fprintf(f,
                 "\n[Service]\n"
                 "Type=exec\n"
+                "ExitType=cgroup\n"
                 "ExecStart=:%s\n"
                 "Restart=no\n"
                 "TimeoutSec=5s\n"
diff --git a/test/TEST-56-EXIT-TYPE/Makefile b/test/TEST-56-EXIT-TYPE/Makefile
new file mode 120000 (symlink)
index 0000000..e9f93b1
--- /dev/null
@@ -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 (executable)
index 0000000..fc321e4
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+set -e
+TEST_DESCRIPTION="test ExitType=cgroup"
+. $TEST_BASE_DIR/test-functions
+
+do_test "$@" 56
index 55204463c8d31f253f9a7e4e6dd8d81ba5124e12..310dbd6add2be3b09e1e344f122a1b677511bd29 100644 (file)
@@ -94,6 +94,7 @@ ExecStartPre=
 ExecStop=
 ExecStopPost=
 ExecStopPre=
+ExitType=
 FailureAction=
 FileDescriptorName=
 FileDescriptorStoreMax=
diff --git a/test/units/testsuite-56.service b/test/units/testsuite-56.service
new file mode 100644 (file)
index 0000000..d8ad589
--- /dev/null
@@ -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 (executable)
index 0000000..9de03e1
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+set -ex
+
+systemd-analyze log-level debug
+
+# Multiple level process tree, parent process stays up
+cat >/tmp/test56-exit-cgroup.sh <<EOF
+#!/usr/bin/env bash
+set -eux
+
+# process tree: systemd -> sleep
+sleep infinity &
+disown
+
+# process tree: systemd -> bash -> bash -> sleep
+((sleep infinity); true) &
+
+# process tree: systemd -> bash -> sleep
+sleep infinity
+EOF
+chmod +x /tmp/test56-exit-cgroup.sh
+
+# service should be stopped cleanly
+(sleep 1; systemctl stop one) &
+systemd-run --wait --unit=one -p ExitType=cgroup /tmp/test56-exit-cgroup.sh
+
+# same thing with a truthy exec condition
+(sleep 1; systemctl stop two) &
+systemd-run --wait --unit=two -p ExitType=cgroup -p ExecCondition=true /tmp/test56-exit-cgroup.sh
+
+# false exec condition: systemd-run should exit immediately with status code: 1
+! systemd-run --wait --unit=three -p ExitType=cgroup -p ExecCondition=false /tmp/test56-exit-cgroup.sh
+
+# service should exit uncleanly
+(sleep 1; systemctl kill --signal 9 four) &
+! systemd-run --wait --unit=four -p ExitType=cgroup /tmp/test56-exit-cgroup.sh
+
+
+# Multiple level process tree, parent process exits quickly
+cat >/tmp/test56-exit-cgroup-parentless.sh <<EOF
+#!/usr/bin/env bash
+set -eux
+
+# process tree: systemd -> sleep
+sleep infinity &
+
+# process tree: systemd -> bash -> sleep
+((sleep infinity); true) &
+EOF
+chmod +x /tmp/test56-exit-cgroup-parentless.sh
+
+# service should be stopped cleanly
+(sleep 1; systemctl stop five) &
+systemd-run --wait --unit=five -p ExitType=cgroup /tmp/test56-exit-cgroup-parentless.sh
+
+# service should exit uncleanly
+(sleep 1; systemctl kill --signal 9 six) &
+! systemd-run --wait --unit=six -p ExitType=cgroup /tmp/test56-exit-cgroup-parentless.sh
+
+
+# Multiple level process tree, parent process exits uncleanly but last process exits cleanly
+cat >/tmp/test56-exit-cgroup-clean.sh <<EOF
+#!/usr/bin/env bash
+set -eux
+
+# process tree: systemd -> bash -> sleep
+(sleep 1; true) &
+
+exit 255
+EOF
+chmod +x /tmp/test56-exit-cgroup-clean.sh
+
+# service should exit cleanly and be garbage-collected
+systemd-run --wait --unit=seven -p ExitType=cgroup /tmp/test56-exit-cgroup-clean.sh
+
+
+# Multiple level process tree, parent process exits cleanly but last process exits uncleanly
+cat >/tmp/test56-exit-cgroup-unclean.sh <<EOF
+#!/usr/bin/env bash
+set -eux
+
+# process tree: systemd -> bash -> sleep
+(sleep 1; exit 255) &
+EOF
+chmod +x /tmp/test56-exit-cgroup-unclean.sh
+
+# service should exit uncleanly after 1 second
+! systemd-run --wait --unit=eight -p ExitType=cgroup /tmp/test56-exit-cgroup-unclean.sh
+
+systemd-analyze log-level info
+
+echo OK > /testok
+
+exit 0