]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/core/service.c
core: ExecCondition= for services
[thirdparty/systemd.git] / src / core / service.c
index f72d6d9cbeb70108b73682210641d5bd1e43ab9e..65200ff687026f1aa34ad2df1838752196667c73 100644 (file)
@@ -46,6 +46,7 @@
 
 static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
         [SERVICE_DEAD] = UNIT_INACTIVE,
+        [SERVICE_CONDITION] = UNIT_ACTIVATING,
         [SERVICE_START_PRE] = UNIT_ACTIVATING,
         [SERVICE_START] = UNIT_ACTIVATING,
         [SERVICE_START_POST] = UNIT_ACTIVATING,
@@ -68,6 +69,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
  * consider idle jobs active as soon as we start working on them */
 static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = {
         [SERVICE_DEAD] = UNIT_INACTIVE,
+        [SERVICE_CONDITION] = UNIT_ACTIVE,
         [SERVICE_START_PRE] = UNIT_ACTIVE,
         [SERVICE_START] = UNIT_ACTIVE,
         [SERVICE_START_POST] = UNIT_ACTIVE,
@@ -1073,7 +1075,7 @@ static void service_set_state(Service *s, ServiceState state) {
         service_unwatch_pid_file(s);
 
         if (!IN_SET(state,
-                    SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
+                    SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
                     SERVICE_RUNNING,
                     SERVICE_RELOAD,
                     SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
@@ -1092,7 +1094,7 @@ static void service_set_state(Service *s, ServiceState state) {
         }
 
         if (!IN_SET(state,
-                    SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
+                    SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
                     SERVICE_RELOAD,
                     SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
                     SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
@@ -1108,7 +1110,7 @@ static void service_set_state(Service *s, ServiceState state) {
         }
 
         if (!IN_SET(state,
-                    SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
+                    SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
                     SERVICE_RUNNING, SERVICE_RELOAD,
                     SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
                     SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) &&
@@ -1131,7 +1133,8 @@ static void service_set_state(Service *s, ServiceState state) {
 
         unit_notify(UNIT(s), table[old_state], table[state],
                     (s->reload_result == SERVICE_SUCCESS ? 0 : UNIT_NOTIFY_RELOAD_FAILURE) |
-                    (s->will_auto_restart ? UNIT_NOTIFY_WILL_AUTO_RESTART : 0));
+                    (s->will_auto_restart ? UNIT_NOTIFY_WILL_AUTO_RESTART : 0) |
+                    (s->result == SERVICE_SKIP_CONDITION ? UNIT_NOTIFY_SKIP_CONDITION : 0));
 }
 
 static usec_t service_coldplug_timeout(Service *s) {
@@ -1139,6 +1142,7 @@ static usec_t service_coldplug_timeout(Service *s) {
 
         switch (s->deserialized_state) {
 
+        case SERVICE_CONDITION:
         case SERVICE_START_PRE:
         case SERVICE_START:
         case SERVICE_START_POST:
@@ -1199,7 +1203,7 @@ static int service_coldplug(Unit *u) {
         if (s->control_pid > 0 &&
             pid_is_unwaited(s->control_pid) &&
             IN_SET(s->deserialized_state,
-                   SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
+                   SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
                    SERVICE_RELOAD,
                    SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
                    SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
@@ -1726,6 +1730,7 @@ static bool service_will_restart(Unit *u) {
 }
 
 static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) {
+        ServiceState end_state;
         int r;
 
         assert(s);
@@ -1738,7 +1743,16 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
         if (s->result == SERVICE_SUCCESS)
                 s->result = f;
 
-        unit_log_result(UNIT(s), s->result == SERVICE_SUCCESS, service_result_to_string(s->result));
+        if (s->result == SERVICE_SUCCESS) {
+                unit_log_success(UNIT(s));
+                end_state = SERVICE_DEAD;
+        } else if (s->result == SERVICE_SKIP_CONDITION) {
+                unit_log_skip(UNIT(s), service_result_to_string(s->result));
+                end_state = SERVICE_DEAD;
+        } else {
+                unit_log_failure(UNIT(s), service_result_to_string(s->result));
+                end_state = SERVICE_FAILED;
+        }
 
         if (allow_restart && service_shall_restart(s))
                 s->will_auto_restart = true;
@@ -1747,7 +1761,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
          * SERVICE_FAILED/SERVICE_DEAD before entering into SERVICE_AUTO_RESTART. */
         s->n_keep_fd_store ++;
 
-        service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD);
+        service_set_state(s, end_state);
 
         if (s->will_auto_restart) {
                 s->will_auto_restart = false;
@@ -2200,6 +2214,42 @@ fail:
         service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true);
 }
 
+static void service_enter_condition(Service *s) {
+        int r;
+
+        assert(s);
+
+        service_unwatch_control_pid(s);
+
+        s->control_command = s->exec_command[SERVICE_EXEC_CONDITION];
+        if (s->control_command) {
+
+                r = service_adverse_to_leftover_processes(s);
+                if (r < 0)
+                        goto fail;
+
+                s->control_command_id = SERVICE_EXEC_CONDITION;
+
+                r = service_spawn(s,
+                                  s->control_command,
+                                  s->timeout_start_usec,
+                                  EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_APPLY_TTY_STDIN,
+                                  &s->control_pid);
+
+                if (r < 0)
+                        goto fail;
+
+                service_set_state(s, SERVICE_CONDITION);
+        } else
+                service_enter_start_pre(s);
+
+        return;
+
+fail:
+        log_unit_warning_errno(UNIT(s), r, "Failed to run 'exec-condition' task: %m");
+        service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true);
+}
+
 static void service_enter_restart(Service *s) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         int r;
@@ -2312,7 +2362,7 @@ static void service_run_next_control(Service *s) {
         s->control_command = s->control_command->command_next;
         service_unwatch_control_pid(s);
 
-        if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD))
+        if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD))
                 timeout = s->timeout_start_usec;
         else
                 timeout = s->timeout_stop_usec;
@@ -2321,7 +2371,7 @@ static void service_run_next_control(Service *s) {
                           s->control_command,
                           timeout,
                           EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|
-                          (IN_SET(s->control_command_id, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)|
+                          (IN_SET(s->control_command_id, SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE, SERVICE_EXEC_STOP_POST) ? EXEC_APPLY_TTY_STDIN : 0)|
                           (IN_SET(s->control_command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST) ? EXEC_SETENV_RESULT : 0)|
                           (IN_SET(s->control_command_id, SERVICE_EXEC_START_POST, SERVICE_EXEC_RELOAD, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST) ? EXEC_CONTROL_CGROUP : 0),
                           &s->control_pid);
@@ -2333,7 +2383,7 @@ static void service_run_next_control(Service *s) {
 fail:
         log_unit_warning_errno(UNIT(s), r, "Failed to run next control task: %m");
 
-        if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START_POST, SERVICE_STOP))
+        if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START_POST, SERVICE_STOP))
                 service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES);
         else if (s->state == SERVICE_STOP_POST)
                 service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true);
@@ -2387,7 +2437,7 @@ static int service_start(Unit *u) {
                 return -EAGAIN;
 
         /* Already on it! */
-        if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST))
+        if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST))
                 return 0;
 
         /* A service that will be restarted must be stopped first to
@@ -2439,7 +2489,7 @@ static int service_start(Unit *u) {
 
         u->reset_accounting = true;
 
-        service_enter_start_pre(s);
+        service_enter_condition(s);
         return 1;
 }
 
@@ -2465,7 +2515,7 @@ static int service_stop(Unit *u) {
 
         /* If there's already something running we go directly into
          * kill mode. */
-        if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_STOP_WATCHDOG)) {
+        if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_STOP_WATCHDOG)) {
                 service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS);
                 return 0;
         }
@@ -3267,6 +3317,7 @@ static void service_notify_cgroup_oom_event(Unit *u) {
 
         switch (s->state) {
 
+        case SERVICE_CONDITION:
         case SERVICE_START_PRE:
         case SERVICE_START:
         case SERVICE_START_POST:
@@ -3456,6 +3507,10 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
         } else if (s->control_pid == pid) {
                 s->control_pid = 0;
 
+                /* ExecCondition= calls that exit with (0, 254] should invoke skip-like behavior instead of failing */
+                if (f == SERVICE_FAILURE_EXIT_CODE && s->state == SERVICE_CONDITION && status < 255)
+                        f = SERVICE_SKIP_CONDITION;
+
                 if (s->control_command) {
                         exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status);
 
@@ -3493,6 +3548,13 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
 
                         switch (s->state) {
 
+                        case SERVICE_CONDITION:
+                                if (f == SERVICE_SUCCESS)
+                                        service_enter_start_pre(s);
+                                else
+                                        service_enter_signal(s, SERVICE_STOP_SIGTERM, f);
+                                break;
+
                         case SERVICE_START_PRE:
                                 if (f == SERVICE_SUCCESS)
                                         service_enter_start(s);
@@ -3621,9 +3683,10 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
 
         switch (s->state) {
 
+        case SERVICE_CONDITION:
         case SERVICE_START_PRE:
         case SERVICE_START:
-                log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", s->state == SERVICE_START ? "Start" : "Start-pre");
+                log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", service_state_to_string(s->state));
                 service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT);
                 break;
 
@@ -4165,6 +4228,7 @@ static bool service_needs_console(Unit *u) {
                 return false;
 
         return IN_SET(s->state,
+                      SERVICE_CONDITION,
                       SERVICE_START_PRE,
                       SERVICE_START,
                       SERVICE_START_POST,
@@ -4290,6 +4354,7 @@ static const char* const service_type_table[_SERVICE_TYPE_MAX] = {
 DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType);
 
 static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = {
+        [SERVICE_EXEC_CONDITION] = "ExecCondition",
         [SERVICE_EXEC_START_PRE] = "ExecStartPre",
         [SERVICE_EXEC_START] = "ExecStart",
         [SERVICE_EXEC_START_POST] = "ExecStartPost",
@@ -4328,6 +4393,7 @@ static const char* const service_result_table[_SERVICE_RESULT_MAX] = {
         [SERVICE_FAILURE_WATCHDOG] = "watchdog",
         [SERVICE_FAILURE_START_LIMIT_HIT] = "start-limit-hit",
         [SERVICE_FAILURE_OOM_KILL] = "oom-kill",
+        [SERVICE_SKIP_CONDITION] = "exec-condition",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult);
@@ -4407,6 +4473,7 @@ const UnitVTable service_vtable = {
                 .finished_start_job = {
                         [JOB_DONE]       = "Started %s.",
                         [JOB_FAILED]     = "Failed to start %s.",
+                        [JOB_SKIPPED]    = "Skipped %s.",
                 },
                 .finished_stop_job = {
                         [JOB_DONE]       = "Stopped %s.",