4cad4c
From ab9c835796a27f0fbaee75a90f0311ec456941d8 Mon Sep 17 00:00:00 2001
4cad4c
From: Anita Zhang <the.anitazha@gmail.com>
4cad4c
Date: Fri, 28 Jun 2019 17:02:30 -0700
4cad4c
Subject: [PATCH] core: ExecCondition= for services
4cad4c
4cad4c
Closes #10596
4cad4c
4cad4c
(cherry picked from commit 31cd5f63ce86a0784c4ef869c4d323a11ff14adc)
4cad4c
4cad4c
Resolves: #1737283
4cad4c
---
4cad4c
 TODO                                          |  2 -
4cad4c
 catalog/systemd.catalog.in                    |  7 ++
4cad4c
 doc/TRANSIENT-SETTINGS.md                     |  1 +
4cad4c
 man/systemd.service.xml                       | 20 ++++
4cad4c
 src/basic/unit-def.c                          |  1 +
4cad4c
 src/basic/unit-def.h                          |  1 +
4cad4c
 src/core/dbus-service.c                       |  1 +
4cad4c
 src/core/job.c                                |  3 +-
4cad4c
 src/core/job.h                                |  2 +-
4cad4c
 src/core/load-fragment-gperf.gperf.m4         |  1 +
4cad4c
 src/core/service.c                            | 93 ++++++++++++++++---
4cad4c
 src/core/service.h                            |  2 +
4cad4c
 src/core/unit.c                               | 25 ++++-
4cad4c
 src/core/unit.h                               |  4 +
4cad4c
 src/shared/bus-unit-util.c                    |  2 +-
4cad4c
 src/systemd/sd-messages.h                     |  2 +
4cad4c
 src/test/test-execute.c                       | 50 +++++++++-
4cad4c
 test/fuzz/fuzz-unit-file/directives.service   |  1 +
4cad4c
 test/meson.build                              |  2 +
4cad4c
 .../exec-condition-failed.service             | 11 +++
4cad4c
 test/test-execute/exec-condition-skip.service | 15 +++
4cad4c
 21 files changed, 222 insertions(+), 24 deletions(-)
4cad4c
 create mode 100644 test/test-execute/exec-condition-failed.service
4cad4c
 create mode 100644 test/test-execute/exec-condition-skip.service
4cad4c
4cad4c
diff --git a/TODO b/TODO
4cad4c
index ff1008accf..8f78000089 100644
4cad4c
--- a/TODO
4cad4c
+++ b/TODO
4cad4c
@@ -626,8 +626,6 @@ Features:
4cad4c
 
4cad4c
 * merge unit_kill_common() and unit_kill_context()
4cad4c
 
4cad4c
-* introduce ExecCondition= in services
4cad4c
-
4cad4c
 * EFI:
4cad4c
   - honor language efi variables for default language selection (if there are any?)
4cad4c
   - honor timezone efi variables for default timezone selection (if there are any?)
4cad4c
diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in
4cad4c
index 2492ad2028..dc44414f9d 100644
4cad4c
--- a/catalog/systemd.catalog.in
4cad4c
+++ b/catalog/systemd.catalog.in
4cad4c
@@ -358,6 +358,13 @@ Support: %SUPPORT_URL%
4cad4c
 
4cad4c
 The unit @UNIT@ has entered the 'failed' state with result '@UNIT_RESULT@'.
4cad4c
 
4cad4c
+-- 0e4284a0caca4bfc81c0bb6786972673
4cad4c
+Subject: Unit skipped
4cad4c
+Defined-By: systemd
4cad4c
+Support: %SUPPORT_URL%
4cad4c
+
4cad4c
+The unit @UNIT@ was skipped and has entered the 'dead' state with result '@UNIT_RESULT@'.
4cad4c
+
4cad4c
 -- 50876a9db00f4c40bde1a2ad381c3a1b
4cad4c
 Subject: The system is configured in a way that might cause problems
4cad4c
 Defined-By: systemd
4cad4c
diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md
4cad4c
index 0b2ad66dcb..23fe84e4d1 100644
4cad4c
--- a/doc/TRANSIENT-SETTINGS.md
4cad4c
+++ b/doc/TRANSIENT-SETTINGS.md
4cad4c
@@ -267,6 +267,7 @@ Most service unit settings are available for transient units.
4cad4c
 
4cad4c
 ```
4cad4c
 ✓ PIDFile=
4cad4c
+✓ ExecCondition=
4cad4c
 ✓ ExecStartPre=
4cad4c
 ✓ ExecStart=
4cad4c
 ✓ ExecStartPost=
4cad4c
diff --git a/man/systemd.service.xml b/man/systemd.service.xml
4cad4c
index 315b80e704..54586d1948 100644
4cad4c
--- a/man/systemd.service.xml
4cad4c
+++ b/man/systemd.service.xml
4cad4c
@@ -414,6 +414,26 @@
4cad4c
         </listitem>
4cad4c
       </varlistentry>
4cad4c
 
4cad4c
+      <varlistentry>
4cad4c
+        <term><varname>ExecCondition=</varname></term>
4cad4c
+        <listitem><para>Optional commands that are executed before the command(s) in <varname>ExecStartPre=</varname>.
4cad4c
+        Syntax is the same as for <varname>ExecStart=</varname>, except that multiple command lines are allowed and the
4cad4c
+        commands are executed one after the other, serially.</para>
4cad4c
+
4cad4c
+        <para>The behavior is like an <varname>ExecStartPre=</varname> and condition check hybrid: when an
4cad4c
+        <varname>ExecCondition=</varname> command exits with exit code 1 through 254 (inclusive), the remaining
4cad4c
+        commands are skipped and the unit is <emphasis>not</emphasis> marked as failed. However, if an
4cad4c
+        <varname>ExecCondition=</varname> command exits with 255 or abnormally (e.g. timeout, killed by a
4cad4c
+        signal, etc.), the unit will be considered failed (and remaining commands will be skipped). Exit code of 0 or
4cad4c
+        those matching <varname>SuccessExitStatus=</varname> will continue execution to the next command(s).</para>
4cad4c
+
4cad4c
+        <para>The same recommendations about not running long-running processes in <varname>ExecStartPre=</varname>
4cad4c
+        also applies to <varname>ExecCondition=</varname>. <varname>ExecCondition=</varname> will also run the commands
4cad4c
+        in <varname>ExecStopPost=</varname>, as part of stopping the service, in the case of any non-zero or abnormal
4cad4c
+        exits, like the ones described above.</para>
4cad4c
+        </listitem>
4cad4c
+      </varlistentry>
4cad4c
+
4cad4c
       <varlistentry>
4cad4c
         <term><varname>ExecReload=</varname></term>
4cad4c
         <listitem><para>Commands to execute to trigger a configuration
4cad4c
diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c
4cad4c
index ac6a9b37e8..46593f6e65 100644
4cad4c
--- a/src/basic/unit-def.c
4cad4c
+++ b/src/basic/unit-def.c
4cad4c
@@ -162,6 +162,7 @@ DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState);
4cad4c
 
4cad4c
 static const char* const service_state_table[_SERVICE_STATE_MAX] = {
4cad4c
         [SERVICE_DEAD] = "dead",
4cad4c
+        [SERVICE_CONDITION] = "condition",
4cad4c
         [SERVICE_START_PRE] = "start-pre",
4cad4c
         [SERVICE_START] = "start",
4cad4c
         [SERVICE_START_POST] = "start-post",
4cad4c
diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h
4cad4c
index d7e2d74669..db397a31ed 100644
4cad4c
--- a/src/basic/unit-def.h
4cad4c
+++ b/src/basic/unit-def.h
4cad4c
@@ -101,6 +101,7 @@ typedef enum ScopeState {
4cad4c
 
4cad4c
 typedef enum ServiceState {
4cad4c
         SERVICE_DEAD,
4cad4c
+        SERVICE_CONDITION,
4cad4c
         SERVICE_START_PRE,
4cad4c
         SERVICE_START,
4cad4c
         SERVICE_START_POST,
4cad4c
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c
4cad4c
index 1b4c98c7d2..5f768a77c8 100644
4cad4c
--- a/src/core/dbus-service.c
4cad4c
+++ b/src/core/dbus-service.c
4cad4c
@@ -127,6 +127,7 @@ const sd_bus_vtable bus_service_vtable[] = {
4cad4c
         SD_BUS_PROPERTY("NRestarts", "u", bus_property_get_unsigned, offsetof(Service, n_restarts), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
4cad4c
 
4cad4c
         BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
4cad4c
+        BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
4cad4c
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
4cad4c
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
4cad4c
         BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
4cad4c
diff --git a/src/core/job.c b/src/core/job.c
4cad4c
index b9eee91cf3..870ec0a387 100644
4cad4c
--- a/src/core/job.c
4cad4c
+++ b/src/core/job.c
4cad4c
@@ -870,7 +870,8 @@ static void job_log_done_status_message(Unit *u, uint32_t job_id, JobType t, Job
4cad4c
                 return;
4cad4c
 
4cad4c
         /* Show condition check message if the job did not actually do anything due to failed condition. */
4cad4c
-        if (t == JOB_START && result == JOB_DONE && !u->condition_result) {
4cad4c
+        if ((t == JOB_START && result == JOB_DONE && !u->condition_result) ||
4cad4c
+            (t == JOB_START && result == JOB_SKIPPED)) {
4cad4c
                 log_struct(LOG_INFO,
4cad4c
                            "MESSAGE=Condition check resulted in %s being skipped.", unit_description(u),
4cad4c
                            "JOB_ID=%" PRIu32, job_id,
4cad4c
diff --git a/src/core/job.h b/src/core/job.h
4cad4c
index 2f5f3f3989..189fea20ca 100644
4cad4c
--- a/src/core/job.h
4cad4c
+++ b/src/core/job.h
4cad4c
@@ -85,7 +85,7 @@ enum JobResult {
4cad4c
         JOB_TIMEOUT,             /* Job timeout elapsed */
4cad4c
         JOB_FAILED,              /* Job failed */
4cad4c
         JOB_DEPENDENCY,          /* A required dependency job did not result in JOB_DONE */
4cad4c
-        JOB_SKIPPED,             /* Negative result of JOB_VERIFY_ACTIVE */
4cad4c
+        JOB_SKIPPED,             /* Negative result of JOB_VERIFY_ACTIVE or skip due to ExecCondition= */
4cad4c
         JOB_INVALID,             /* JOB_RELOAD of inactive unit */
4cad4c
         JOB_ASSERT,              /* Couldn't start a unit, because an assert didn't hold */
4cad4c
         JOB_UNSUPPORTED,         /* Couldn't start a unit, because the unit type is not supported on the system */
4cad4c
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
4cad4c
index 161c5a2c82..8883818ff2 100644
4cad4c
--- a/src/core/load-fragment-gperf.gperf.m4
4cad4c
+++ b/src/core/load-fragment-gperf.gperf.m4
4cad4c
@@ -291,6 +291,7 @@ Unit.AssertNull,                 config_parse_unit_condition_null,   0,
4cad4c
 Unit.CollectMode,                config_parse_collect_mode,          0,                             offsetof(Unit, collect_mode)
4cad4c
 m4_dnl
4cad4c
 Service.PIDFile,                 config_parse_unit_path_printf,      0,                             offsetof(Service, pid_file)
4cad4c
+Service.ExecCondition,           config_parse_exec,                  SERVICE_EXEC_CONDITION,        offsetof(Service, exec_command)
4cad4c
 Service.ExecStartPre,            config_parse_exec,                  SERVICE_EXEC_START_PRE,        offsetof(Service, exec_command)
4cad4c
 Service.ExecStart,               config_parse_exec,                  SERVICE_EXEC_START,            offsetof(Service, exec_command)
4cad4c
 Service.ExecStartPost,           config_parse_exec,                  SERVICE_EXEC_START_POST,       offsetof(Service, exec_command)
4cad4c
diff --git a/src/core/service.c b/src/core/service.c
4cad4c
index 2c31e70ef6..92be4280f6 100644
4cad4c
--- a/src/core/service.c
4cad4c
+++ b/src/core/service.c
4cad4c
@@ -41,6 +41,7 @@
4cad4c
 
4cad4c
 static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
4cad4c
         [SERVICE_DEAD] = UNIT_INACTIVE,
4cad4c
+        [SERVICE_CONDITION] = UNIT_ACTIVATING,
4cad4c
         [SERVICE_START_PRE] = UNIT_ACTIVATING,
4cad4c
         [SERVICE_START] = UNIT_ACTIVATING,
4cad4c
         [SERVICE_START_POST] = UNIT_ACTIVATING,
4cad4c
@@ -62,6 +63,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
4cad4c
  * consider idle jobs active as soon as we start working on them */
4cad4c
 static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = {
4cad4c
         [SERVICE_DEAD] = UNIT_INACTIVE,
4cad4c
+        [SERVICE_CONDITION] = UNIT_ACTIVE,
4cad4c
         [SERVICE_START_PRE] = UNIT_ACTIVE,
4cad4c
         [SERVICE_START] = UNIT_ACTIVE,
4cad4c
         [SERVICE_START_POST] = UNIT_ACTIVE,
4cad4c
@@ -1024,7 +1026,7 @@ static void service_set_state(Service *s, ServiceState state) {
4cad4c
         service_unwatch_pid_file(s);
4cad4c
 
4cad4c
         if (!IN_SET(state,
4cad4c
-                    SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
4cad4c
+                    SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
4cad4c
                     SERVICE_RUNNING,
4cad4c
                     SERVICE_RELOAD,
4cad4c
                     SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
4cad4c
@@ -1042,7 +1044,7 @@ static void service_set_state(Service *s, ServiceState state) {
4cad4c
         }
4cad4c
 
4cad4c
         if (!IN_SET(state,
4cad4c
-                    SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
4cad4c
+                    SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
4cad4c
                     SERVICE_RELOAD,
4cad4c
                     SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
4cad4c
                     SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) {
4cad4c
@@ -1057,7 +1059,7 @@ static void service_set_state(Service *s, ServiceState state) {
4cad4c
         }
4cad4c
 
4cad4c
         if (!IN_SET(state,
4cad4c
-                    SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
4cad4c
+                    SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
4cad4c
                     SERVICE_RUNNING, SERVICE_RELOAD,
4cad4c
                     SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
4cad4c
                     SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) &&
4cad4c
@@ -1080,7 +1082,8 @@ static void service_set_state(Service *s, ServiceState state) {
4cad4c
 
4cad4c
         unit_notify(UNIT(s), table[old_state], table[state],
4cad4c
                     (s->reload_result == SERVICE_SUCCESS ? 0 : UNIT_NOTIFY_RELOAD_FAILURE) |
4cad4c
-                    (s->will_auto_restart ? UNIT_NOTIFY_WILL_AUTO_RESTART : 0));
4cad4c
+                    (s->will_auto_restart ? UNIT_NOTIFY_WILL_AUTO_RESTART : 0) |
4cad4c
+                    (s->result == SERVICE_SKIP_CONDITION ? UNIT_NOTIFY_SKIP_CONDITION : 0));
4cad4c
 }
4cad4c
 
4cad4c
 static usec_t service_coldplug_timeout(Service *s) {
4cad4c
@@ -1088,6 +1091,7 @@ static usec_t service_coldplug_timeout(Service *s) {
4cad4c
 
4cad4c
         switch (s->deserialized_state) {
4cad4c
 
4cad4c
+        case SERVICE_CONDITION:
4cad4c
         case SERVICE_START_PRE:
4cad4c
         case SERVICE_START:
4cad4c
         case SERVICE_START_POST:
4cad4c
@@ -1143,7 +1147,7 @@ static int service_coldplug(Unit *u) {
4cad4c
         if (s->control_pid > 0 &&
4cad4c
             pid_is_unwaited(s->control_pid) &&
4cad4c
             IN_SET(s->deserialized_state,
4cad4c
-                   SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
4cad4c
+                   SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
4cad4c
                    SERVICE_RELOAD,
4cad4c
                    SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
4cad4c
                    SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) {
4cad4c
@@ -1667,6 +1671,7 @@ static bool service_will_restart(Unit *u) {
4cad4c
 }
4cad4c
 
4cad4c
 static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) {
4cad4c
+        ServiceState end_state;
4cad4c
         int r;
4cad4c
 
4cad4c
         assert(s);
4cad4c
@@ -1679,10 +1684,16 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
4cad4c
         if (s->result == SERVICE_SUCCESS)
4cad4c
                 s->result = f;
4cad4c
 
4cad4c
-        if (s->result == SERVICE_SUCCESS)
4cad4c
+        if (s->result == SERVICE_SUCCESS) {
4cad4c
                 unit_log_success(UNIT(s));
4cad4c
-        else
4cad4c
+                end_state = SERVICE_DEAD;
4cad4c
+        } else if (s->result == SERVICE_SKIP_CONDITION) {
4cad4c
+                unit_log_skip(UNIT(s), service_result_to_string(s->result));
4cad4c
+                end_state = SERVICE_DEAD;
4cad4c
+        } else {
4cad4c
                 unit_log_failure(UNIT(s), service_result_to_string(s->result));
4cad4c
+                end_state = SERVICE_FAILED;
4cad4c
+        }
4cad4c
 
4cad4c
         if (allow_restart && service_shall_restart(s))
4cad4c
                 s->will_auto_restart = true;
4cad4c
@@ -1691,7 +1702,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
4cad4c
          * SERVICE_FAILED/SERVICE_DEAD before entering into SERVICE_AUTO_RESTART. */
4cad4c
         s->n_keep_fd_store ++;
4cad4c
 
4cad4c
-        service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD);
4cad4c
+        service_set_state(s, end_state);
4cad4c
 
4cad4c
         if (s->will_auto_restart) {
4cad4c
                 s->will_auto_restart = false;
4cad4c
@@ -2110,6 +2121,40 @@ fail:
4cad4c
         service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true);
4cad4c
 }
4cad4c
 
4cad4c
+static void service_enter_condition(Service *s) {
4cad4c
+        int r;
4cad4c
+
4cad4c
+        assert(s);
4cad4c
+
4cad4c
+        service_unwatch_control_pid(s);
4cad4c
+
4cad4c
+        s->control_command = s->exec_command[SERVICE_EXEC_CONDITION];
4cad4c
+        if (s->control_command) {
4cad4c
+
4cad4c
+                unit_warn_leftover_processes(UNIT(s));
4cad4c
+
4cad4c
+                s->control_command_id = SERVICE_EXEC_CONDITION;
4cad4c
+
4cad4c
+                r = service_spawn(s,
4cad4c
+                                  s->control_command,
4cad4c
+                                  s->timeout_start_usec,
4cad4c
+                                  EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_APPLY_TTY_STDIN,
4cad4c
+                                  &s->control_pid);
4cad4c
+
4cad4c
+                if (r < 0)
4cad4c
+                        goto fail;
4cad4c
+
4cad4c
+                service_set_state(s, SERVICE_CONDITION);
4cad4c
+        } else
4cad4c
+                service_enter_start_pre(s);
4cad4c
+
4cad4c
+        return;
4cad4c
+
4cad4c
+fail:
4cad4c
+        log_unit_warning_errno(UNIT(s), r, "Failed to run 'exec-condition' task: %m");
4cad4c
+        service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true);
4cad4c
+}
4cad4c
+
4cad4c
 static void service_enter_restart(Service *s) {
4cad4c
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
4cad4c
         int r;
4cad4c
@@ -2222,7 +2267,7 @@ static void service_run_next_control(Service *s) {
4cad4c
         s->control_command = s->control_command->command_next;
4cad4c
         service_unwatch_control_pid(s);
4cad4c
 
4cad4c
-        if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD))
4cad4c
+        if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD))
4cad4c
                 timeout = s->timeout_start_usec;
4cad4c
         else
4cad4c
                 timeout = s->timeout_stop_usec;
4cad4c
@@ -2231,7 +2276,7 @@ static void service_run_next_control(Service *s) {
4cad4c
                           s->control_command,
4cad4c
                           timeout,
4cad4c
                           EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|
4cad4c
-                          (IN_SET(s->control_command_id, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)|
4cad4c
+                          (IN_SET(s->control_command_id, SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)|
4cad4c
                           (IN_SET(s->control_command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST) ? EXEC_SETENV_RESULT : 0),
4cad4c
                           &s->control_pid);
4cad4c
         if (r < 0)
4cad4c
@@ -2242,7 +2287,7 @@ static void service_run_next_control(Service *s) {
4cad4c
 fail:
4cad4c
         log_unit_warning_errno(UNIT(s), r, "Failed to run next control task: %m");
4cad4c
 
4cad4c
-        if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START_POST, SERVICE_STOP))
4cad4c
+        if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START_POST, SERVICE_STOP))
4cad4c
                 service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES);
4cad4c
         else if (s->state == SERVICE_STOP_POST)
4cad4c
                 service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true);
4cad4c
@@ -2296,7 +2341,7 @@ static int service_start(Unit *u) {
4cad4c
                 return -EAGAIN;
4cad4c
 
4cad4c
         /* Already on it! */
4cad4c
-        if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST))
4cad4c
+        if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST))
4cad4c
                 return 0;
4cad4c
 
4cad4c
         /* A service that will be restarted must be stopped first to
4cad4c
@@ -2344,7 +2389,9 @@ static int service_start(Unit *u) {
4cad4c
                 s->flush_n_restarts = false;
4cad4c
         }
4cad4c
 
4cad4c
-        service_enter_start_pre(s);
4cad4c
+        u->reset_accounting = true;
4cad4c
+
4cad4c
+        service_enter_condition(s);
4cad4c
         return 1;
4cad4c
 }
4cad4c
 
4cad4c
@@ -2370,7 +2417,7 @@ static int service_stop(Unit *u) {
4cad4c
 
4cad4c
         /* If there's already something running we go directly into
4cad4c
          * kill mode. */
4cad4c
-        if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD)) {
4cad4c
+        if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD)) {
4cad4c
                 service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
4cad4c
                 return 0;
4cad4c
         }
4cad4c
@@ -3303,6 +3350,10 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
4cad4c
         } else if (s->control_pid == pid) {
4cad4c
                 s->control_pid = 0;
4cad4c
 
4cad4c
+                /* ExecCondition= calls that exit with (0, 254] should invoke skip-like behavior instead of failing */
4cad4c
+                if (f == SERVICE_FAILURE_EXIT_CODE && s->state == SERVICE_CONDITION && status < 255)
4cad4c
+                        f = SERVICE_SKIP_CONDITION;
4cad4c
+
4cad4c
                 if (s->control_command) {
4cad4c
                         exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status);
4cad4c
 
4cad4c
@@ -3338,6 +3389,13 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
4cad4c
 
4cad4c
                         switch (s->state) {
4cad4c
 
4cad4c
+                        case SERVICE_CONDITION:
4cad4c
+                                if (f == SERVICE_SUCCESS)
4cad4c
+                                        service_enter_start_pre(s);
4cad4c
+                                else
4cad4c
+                                        service_enter_signal(s, SERVICE_STOP_SIGTERM, f);
4cad4c
+                                break;
4cad4c
+
4cad4c
                         case SERVICE_START_PRE:
4cad4c
                                 if (f == SERVICE_SUCCESS)
4cad4c
                                         service_enter_start(s);
4cad4c
@@ -3462,9 +3520,10 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
4cad4c
 
4cad4c
         switch (s->state) {
4cad4c
 
4cad4c
+        case SERVICE_CONDITION:
4cad4c
         case SERVICE_START_PRE:
4cad4c
         case SERVICE_START:
4cad4c
-                log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", s->state == SERVICE_START ? "Start" : "Start-pre");
4cad4c
+                log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", service_state_to_string(s->state));
4cad4c
                 service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT);
4cad4c
                 break;
4cad4c
 
4cad4c
@@ -3975,6 +4034,7 @@ static bool service_needs_console(Unit *u) {
4cad4c
                 return false;
4cad4c
 
4cad4c
         return IN_SET(s->state,
4cad4c
+                      SERVICE_CONDITION,
4cad4c
                       SERVICE_START_PRE,
4cad4c
                       SERVICE_START,
4cad4c
                       SERVICE_START_POST,
4cad4c
@@ -4014,6 +4074,7 @@ static const char* const service_type_table[_SERVICE_TYPE_MAX] = {
4cad4c
 DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType);
4cad4c
 
4cad4c
 static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = {
4cad4c
+        [SERVICE_EXEC_CONDITION] = "ExecCondition",
4cad4c
         [SERVICE_EXEC_START_PRE] = "ExecStartPre",
4cad4c
         [SERVICE_EXEC_START] = "ExecStart",
4cad4c
         [SERVICE_EXEC_START_POST] = "ExecStartPost",
4cad4c
@@ -4043,6 +4104,7 @@ static const char* const service_result_table[_SERVICE_RESULT_MAX] = {
4cad4c
         [SERVICE_FAILURE_CORE_DUMP] = "core-dump",
4cad4c
         [SERVICE_FAILURE_WATCHDOG] = "watchdog",
4cad4c
         [SERVICE_FAILURE_START_LIMIT_HIT] = "start-limit-hit",
4cad4c
+        [SERVICE_SKIP_CONDITION] = "exec-condition",
4cad4c
 };
4cad4c
 
4cad4c
 DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult);
4cad4c
@@ -4118,6 +4180,7 @@ const UnitVTable service_vtable = {
4cad4c
                 .finished_start_job = {
4cad4c
                         [JOB_DONE]       = "Started %s.",
4cad4c
                         [JOB_FAILED]     = "Failed to start %s.",
4cad4c
+                        [JOB_SKIPPED]    = "Skipped %s.",
4cad4c
                 },
4cad4c
                 .finished_stop_job = {
4cad4c
                         [JOB_DONE]       = "Stopped %s.",
4cad4c
diff --git a/src/core/service.h b/src/core/service.h
4cad4c
index 1206e3cdda..62b78cadf1 100644
4cad4c
--- a/src/core/service.h
4cad4c
+++ b/src/core/service.h
4cad4c
@@ -36,6 +36,7 @@ typedef enum ServiceType {
4cad4c
 } ServiceType;
4cad4c
 
4cad4c
 typedef enum ServiceExecCommand {
4cad4c
+        SERVICE_EXEC_CONDITION,
4cad4c
         SERVICE_EXEC_START_PRE,
4cad4c
         SERVICE_EXEC_START,
4cad4c
         SERVICE_EXEC_START_POST,
4cad4c
@@ -67,6 +68,7 @@ typedef enum ServiceResult {
4cad4c
         SERVICE_FAILURE_CORE_DUMP,
4cad4c
         SERVICE_FAILURE_WATCHDOG,
4cad4c
         SERVICE_FAILURE_START_LIMIT_HIT,
4cad4c
+        SERVICE_SKIP_CONDITION,
4cad4c
         _SERVICE_RESULT_MAX,
4cad4c
         _SERVICE_RESULT_INVALID = -1
4cad4c
 } ServiceResult;
4cad4c
diff --git a/src/core/unit.c b/src/core/unit.c
4cad4c
index ccb0106719..61799bf9e3 100644
4cad4c
--- a/src/core/unit.c
4cad4c
+++ b/src/core/unit.c
4cad4c
@@ -2227,6 +2227,7 @@ static void unit_update_on_console(Unit *u) {
4cad4c
 
4cad4c
 static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags) {
4cad4c
         bool unexpected = false;
4cad4c
+        JobResult result;
4cad4c
 
4cad4c
         assert(j);
4cad4c
 
4cad4c
@@ -2249,8 +2250,16 @@ static bool unit_process_job(Job *j, UnitActiveState ns, UnitNotifyFlags flags)
4cad4c
                 else if (j->state == JOB_RUNNING && ns != UNIT_ACTIVATING) {
4cad4c
                         unexpected = true;
4cad4c
 
4cad4c
-                        if (UNIT_IS_INACTIVE_OR_FAILED(ns))
4cad4c
-                                job_finish_and_invalidate(j, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false);
4cad4c
+                        if (UNIT_IS_INACTIVE_OR_FAILED(ns)) {
4cad4c
+                                if (ns == UNIT_FAILED)
4cad4c
+                                        result = JOB_FAILED;
4cad4c
+                                else if (FLAGS_SET(flags, UNIT_NOTIFY_SKIP_CONDITION))
4cad4c
+                                        result = JOB_SKIPPED;
4cad4c
+                                else
4cad4c
+                                        result = JOB_DONE;
4cad4c
+
4cad4c
+                                job_finish_and_invalidate(j, result, true, false);
4cad4c
+                        }
4cad4c
                 }
4cad4c
 
4cad4c
                 break;
4cad4c
@@ -5484,6 +5493,18 @@ void unit_log_failure(Unit *u, const char *result) {
4cad4c
                    "UNIT_RESULT=%s", result);
4cad4c
 }
4cad4c
 
4cad4c
+void unit_log_skip(Unit *u, const char *result) {
4cad4c
+        assert(u);
4cad4c
+        assert(result);
4cad4c
+
4cad4c
+        log_struct(LOG_INFO,
4cad4c
+                   "MESSAGE_ID=" SD_MESSAGE_UNIT_SKIPPED_STR,
4cad4c
+                   LOG_UNIT_ID(u),
4cad4c
+                   LOG_UNIT_INVOCATION_ID(u),
4cad4c
+                   LOG_UNIT_MESSAGE(u, "Skipped due to '%s'.", result),
4cad4c
+                   "UNIT_RESULT=%s", result);
4cad4c
+}
4cad4c
+
4cad4c
 static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
4cad4c
         [COLLECT_INACTIVE] = "inactive",
4cad4c
         [COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",
4cad4c
diff --git a/src/core/unit.h b/src/core/unit.h
4cad4c
index 4ae1b38624..39179f5fd4 100644
4cad4c
--- a/src/core/unit.h
4cad4c
+++ b/src/core/unit.h
4cad4c
@@ -658,6 +658,7 @@ int unit_kill_common(Unit *u, KillWho who, int signo, pid_t main_pid, pid_t cont
4cad4c
 typedef enum UnitNotifyFlags {
4cad4c
         UNIT_NOTIFY_RELOAD_FAILURE    = 1 << 0,
4cad4c
         UNIT_NOTIFY_WILL_AUTO_RESTART = 1 << 1,
4cad4c
+        UNIT_NOTIFY_SKIP_CONDITION    = 1 << 2,
4cad4c
 } UnitNotifyFlags;
4cad4c
 
4cad4c
 void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlags flags);
4cad4c
@@ -806,6 +807,9 @@ int unit_pid_attachable(Unit *unit, pid_t pid, sd_bus_error *error);
4cad4c
 
4cad4c
 void unit_log_success(Unit *u);
4cad4c
 void unit_log_failure(Unit *u, const char *result);
4cad4c
+/* unit_log_skip is for cases like ExecCondition= where a unit is considered "done"
4cad4c
+ * after some execution, rather than succeeded or failed. */
4cad4c
+void unit_log_skip(Unit *u, const char *result);
4cad4c
 
4cad4c
 /* Macros which append UNIT= or USER_UNIT= to the message */
4cad4c
 
4cad4c
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
4cad4c
index 8f3b463c6b..e0b2cfb170 100644
4cad4c
--- a/src/shared/bus-unit-util.c
4cad4c
+++ b/src/shared/bus-unit-util.c
4cad4c
@@ -1334,7 +1334,7 @@ static int bus_append_service_property(sd_bus_message *m, const char *field, con
4cad4c
                 return bus_append_safe_atou(m, field, eq);
4cad4c
 
4cad4c
         if (STR_IN_SET(field,
4cad4c
-                       "ExecStartPre", "ExecStart", "ExecStartPost",
4cad4c
+                       "ExecCondition", "ExecStartPre", "ExecStart", "ExecStartPost",
4cad4c
                        "ExecReload", "ExecStop", "ExecStopPost"))
4cad4c
 
4cad4c
                 return bus_append_exec_command(m, field, eq);
4cad4c
diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h
4cad4c
index e7ef81b597..bdd4fd3974 100644
4cad4c
--- a/src/systemd/sd-messages.h
4cad4c
+++ b/src/systemd/sd-messages.h
4cad4c
@@ -111,6 +111,8 @@ _SD_BEGIN_DECLARATIONS;
4cad4c
 #define SD_MESSAGE_UNIT_FAILURE_RESULT    SD_ID128_MAKE(d9,b3,73,ed,55,a6,4f,eb,82,42,e0,2d,be,79,a4,9c)
4cad4c
 #define SD_MESSAGE_UNIT_FAILURE_RESULT_STR \
4cad4c
                                           SD_ID128_MAKE_STR(d9,b3,73,ed,55,a6,4f,eb,82,42,e0,2d,be,79,a4,9c)
4cad4c
+#define SD_MESSAGE_UNIT_SKIPPED           SD_ID128_MAKE(0e,42,84,a0,ca,ca,4b,fc,81,c0,bb,67,86,97,26,73)
4cad4c
+#define SD_MESSAGE_UNIT_SKIPPED_STR       SD_ID128_MAKE_STR(0e,42,84,a0,ca,ca,4b,fc,81,c0,bb,67,86,97,26,73)
4cad4c
 
4cad4c
 #define SD_MESSAGE_SPAWN_FAILED           SD_ID128_MAKE(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7)
4cad4c
 #define SD_MESSAGE_SPAWN_FAILED_STR       SD_ID128_MAKE_STR(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7)
4cad4c
diff --git a/src/test/test-execute.c b/src/test/test-execute.c
4cad4c
index e42d0d30a8..882e866ea9 100644
4cad4c
--- a/src/test/test-execute.c
4cad4c
+++ b/src/test/test-execute.c
4cad4c
@@ -30,7 +30,7 @@
4cad4c
 
4cad4c
 typedef void (*test_function_t)(Manager *m);
4cad4c
 
4cad4c
-static void check(const char *func, Manager *m, Unit *unit, int status_expected, int code_expected) {
4cad4c
+static void wait_for_service_finish(Manager *m, Unit *unit) {
4cad4c
         Service *service = NULL;
4cad4c
         usec_t ts;
4cad4c
         usec_t timeout = 2 * USEC_PER_MINUTE;
4cad4c
@@ -55,6 +55,17 @@ static void check(const char *func, Manager *m, Unit *unit, int status_expected,
4cad4c
                         exit(EXIT_FAILURE);
4cad4c
                 }
4cad4c
         }
4cad4c
+}
4cad4c
+
4cad4c
+static void check_main_result(const char *func, Manager *m, Unit *unit, int status_expected, int code_expected) {
4cad4c
+        Service *service = NULL;
4cad4c
+
4cad4c
+        assert_se(m);
4cad4c
+        assert_se(unit);
4cad4c
+
4cad4c
+        wait_for_service_finish(m, unit);
4cad4c
+
4cad4c
+        service = SERVICE(unit);
4cad4c
         exec_status_dump(&service->main_exec_status, stdout, "\t");
4cad4c
         if (service->main_exec_status.status != status_expected) {
4cad4c
                 log_error("%s: %s: exit status %d, expected %d",
4cad4c
@@ -70,6 +81,25 @@ static void check(const char *func, Manager *m, Unit *unit, int status_expected,
4cad4c
         }
4cad4c
 }
4cad4c
 
4cad4c
+static void check_service_result(const char *func, Manager *m, Unit *unit, ServiceResult result_expected) {
4cad4c
+        Service *service = NULL;
4cad4c
+
4cad4c
+        assert_se(m);
4cad4c
+        assert_se(unit);
4cad4c
+
4cad4c
+        wait_for_service_finish(m, unit);
4cad4c
+
4cad4c
+        service = SERVICE(unit);
4cad4c
+
4cad4c
+        if (service->result != result_expected) {
4cad4c
+                log_error("%s: %s: service end result %s, expected %s",
4cad4c
+                          func, unit->id,
4cad4c
+                          service_result_to_string(service->result),
4cad4c
+                          service_result_to_string(result_expected));
4cad4c
+                abort();
4cad4c
+        }
4cad4c
+}
4cad4c
+
4cad4c
 static bool check_nobody_user_and_group(void) {
4cad4c
         static int cache = -1;
4cad4c
         struct passwd *p;
4cad4c
@@ -140,7 +170,17 @@ static void test(const char *func, Manager *m, const char *unit_name, int status
4cad4c
 
4cad4c
         assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0);
4cad4c
         assert_se(unit_start(unit) >= 0);
4cad4c
-        check(func, m, unit, status_expected, code_expected);
4cad4c
+        check_main_result(func, m, unit, status_expected, code_expected);
4cad4c
+}
4cad4c
+
4cad4c
+static void test_service(const char *func, Manager *m, const char *unit_name, ServiceResult result_expected) {
4cad4c
+        Unit *unit;
4cad4c
+
4cad4c
+        assert_se(unit_name);
4cad4c
+
4cad4c
+        assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0);
4cad4c
+        assert_se(unit_start(unit) >= 0);
4cad4c
+        check_service_result(func, m, unit, result_expected);
4cad4c
 }
4cad4c
 
4cad4c
 static void test_exec_bindpaths(Manager *m) {
4cad4c
@@ -669,6 +709,11 @@ static void test_exec_standardoutput_append(Manager *m) {
4cad4c
         test(__func__, m, "exec-standardoutput-append.service", 0, CLD_EXITED);
4cad4c
 }
4cad4c
 
4cad4c
+static void test_exec_condition(Manager *m) {
4cad4c
+        test_service(__func__, m, "exec-condition-failed.service", SERVICE_FAILURE_EXIT_CODE);
4cad4c
+        test_service(__func__, m, "exec-condition-skip.service", SERVICE_SKIP_CONDITION);
4cad4c
+}
4cad4c
+
4cad4c
 typedef struct test_entry {
4cad4c
         test_function_t f;
4cad4c
         const char *name;
4cad4c
@@ -709,6 +754,7 @@ int main(int argc, char *argv[]) {
4cad4c
                 entry(test_exec_ambientcapabilities),
4cad4c
                 entry(test_exec_bindpaths),
4cad4c
                 entry(test_exec_capabilityboundingset),
4cad4c
+                entry(test_exec_condition),
4cad4c
                 entry(test_exec_cpuaffinity),
4cad4c
                 entry(test_exec_environment),
4cad4c
                 entry(test_exec_environmentfile),
4cad4c
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
4cad4c
index eab1820e20..9d0530df72 100644
4cad4c
--- a/test/fuzz/fuzz-unit-file/directives.service
4cad4c
+++ b/test/fuzz/fuzz-unit-file/directives.service
4cad4c
@@ -83,6 +83,7 @@ DirectoryNotEmpty=
4cad4c
 Documentation=
4cad4c
 DynamicUser=
4cad4c
 ExecReload=
4cad4c
+ExecCondition=
4cad4c
 ExecStart=
4cad4c
 ExecStartPost=
4cad4c
 ExecStartPre=
4cad4c
diff --git a/test/meson.build b/test/meson.build
4cad4c
index 4d1c51048c..070731c4a9 100644
4cad4c
--- a/test/meson.build
4cad4c
+++ b/test/meson.build
4cad4c
@@ -42,6 +42,8 @@ test_data_files = '''
4cad4c
         test-execute/exec-capabilityboundingset-merge.service
4cad4c
         test-execute/exec-capabilityboundingset-reset.service
4cad4c
         test-execute/exec-capabilityboundingset-simple.service
4cad4c
+        test-execute/exec-condition-failed.service
4cad4c
+        test-execute/exec-condition-skip.service
4cad4c
         test-execute/exec-cpuaffinity1.service
4cad4c
         test-execute/exec-cpuaffinity2.service
4cad4c
         test-execute/exec-cpuaffinity3.service
4cad4c
diff --git a/test/test-execute/exec-condition-failed.service b/test/test-execute/exec-condition-failed.service
4cad4c
new file mode 100644
4cad4c
index 0000000000..4a406dc17f
4cad4c
--- /dev/null
4cad4c
+++ b/test/test-execute/exec-condition-failed.service
4cad4c
@@ -0,0 +1,11 @@
4cad4c
+[Unit]
4cad4c
+Description=Test for exec condition that fails the unit
4cad4c
+
4cad4c
+[Service]
4cad4c
+Type=oneshot
4cad4c
+
4cad4c
+# exit 255 will fail the unit
4cad4c
+ExecCondition=/bin/sh -c 'exit 255'
4cad4c
+
4cad4c
+# This should not get run
4cad4c
+ExecStart=/bin/sh -c 'true'
4cad4c
diff --git a/test/test-execute/exec-condition-skip.service b/test/test-execute/exec-condition-skip.service
4cad4c
new file mode 100644
4cad4c
index 0000000000..9450e8442a
4cad4c
--- /dev/null
4cad4c
+++ b/test/test-execute/exec-condition-skip.service
4cad4c
@@ -0,0 +1,15 @@
4cad4c
+[Unit]
4cad4c
+Description=Test for exec condition that triggers skipping
4cad4c
+
4cad4c
+[Service]
4cad4c
+Type=oneshot
4cad4c
+
4cad4c
+# exit codes [1, 254] will result in skipping the rest of execution
4cad4c
+ExecCondition=/bin/sh -c 'exit 0'
4cad4c
+ExecCondition=/bin/sh -c 'exit 254'
4cad4c
+
4cad4c
+# This would normally fail the unit but will not get run due to the skip above
4cad4c
+ExecCondition=/bin/sh -c 'exit 255'
4cad4c
+
4cad4c
+# This should not get run
4cad4c
+ExecStart=/bin/sh -c 'true'