chiark / gitweb /
service: timeout for oneshot services
[elogind.git] / src / core / service.c
index 02d1aa845b4f2663a9397713c47f4eb71f4c4a35..8941271679adde950d7471d5b854cc46b254b614 100644 (file)
@@ -38,6 +38,7 @@
 #include "bus-errors.h"
 #include "exit-status.h"
 #include "def.h"
+#include "path-util.h"
 #include "util.h"
 #include "utf8.h"
 
@@ -107,8 +108,29 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
         [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
 };
 
+/* For Type=idle we never want to delay any other jobs, hence we
+ * 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_START_PRE] = UNIT_ACTIVE,
+        [SERVICE_START] = UNIT_ACTIVE,
+        [SERVICE_START_POST] = UNIT_ACTIVE,
+        [SERVICE_RUNNING] = UNIT_ACTIVE,
+        [SERVICE_EXITED] = UNIT_ACTIVE,
+        [SERVICE_RELOAD] = UNIT_RELOADING,
+        [SERVICE_STOP] = UNIT_DEACTIVATING,
+        [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING,
+        [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING,
+        [SERVICE_STOP_POST] = UNIT_DEACTIVATING,
+        [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
+        [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
+        [SERVICE_FAILED] = UNIT_FAILED,
+        [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
+};
+
 static void service_init(Unit *u) {
         Service *s = SERVICE(u);
+        int i;
 
         assert(u);
         assert(u->load_state == UNIT_STUB);
@@ -128,6 +150,9 @@ static void service_init(Unit *u) {
         s->guess_main_pid = true;
 
         exec_context_init(&s->exec_context);
+        for (i = 0; i < RLIMIT_NLIMITS; i++)
+                if (UNIT(s)->manager->rlimit[i])
+                        s->exec_context.rlimit[i] = newdup(struct rlimit, UNIT(s)->manager->rlimit[i], 1);
 
         RATELIMIT_INIT(s->start_limit, 10*USEC_PER_SEC, 5);
 
@@ -259,9 +284,6 @@ static void service_done(Unit *u) {
         s->pid_file = NULL;
 
 #ifdef HAVE_SYSV_COMPAT
-        free(s->sysv_path);
-        s->sysv_path = NULL;
-
         free(s->sysv_runlevels);
         s->sysv_runlevels = NULL;
 #endif
@@ -499,17 +521,21 @@ static int sysv_exec_commands(Service *s) {
         ExecCommand *c;
 
         assert(s);
-        assert(s->sysv_path);
+        assert(s->is_sysv);
+        assert(UNIT(s)->source_path);
 
-        if (!(c = exec_command_new(s->sysv_path, "start")))
+        c = exec_command_new(UNIT(s)->source_path, "start");
+        if (!c)
                 return -ENOMEM;
         exec_command_append_list(s->exec_command+SERVICE_EXEC_START, c);
 
-        if (!(c = exec_command_new(s->sysv_path, "stop")))
+        c = exec_command_new(UNIT(s)->source_path, "stop");
+        if (!c)
                 return -ENOMEM;
         exec_command_append_list(s->exec_command+SERVICE_EXEC_STOP, c);
 
-        if (!(c = exec_command_new(s->sysv_path, "reload")))
+        c = exec_command_new(UNIT(s)->source_path, "reload");
+        if (!c)
                 return -ENOMEM;
         exec_command_append_list(s->exec_command+SERVICE_EXEC_RELOAD, c);
 
@@ -535,7 +561,8 @@ static int service_load_sysv_path(Service *s, const char *path) {
 
         u = UNIT(s);
 
-        if (!(f = fopen(path, "re"))) {
+        f = fopen(path, "re");
+        if (!f) {
                 r = errno == ENOENT ? 0 : -errno;
                 goto finish;
         }
@@ -546,13 +573,13 @@ static int service_load_sysv_path(Service *s, const char *path) {
                 goto finish;
         }
 
-        free(s->sysv_path);
-        if (!(s->sysv_path = strdup(path))) {
+        free(u->source_path);
+        u->source_path = strdup(path);
+        if (!u->source_path) {
                 r = -ENOMEM;
                 goto finish;
         }
-
-        s->sysv_mtime = timespec_load(&st.st_mtim);
+        u->source_mtime = timespec_load(&st.st_mtim);
 
         if (null_or_empty(&st)) {
                 u->load_state = UNIT_MASKED;
@@ -560,6 +587,8 @@ static int service_load_sysv_path(Service *s, const char *path) {
                 goto finish;
         }
 
+        s->is_sysv = true;
+
         while (!feof(f)) {
                 char l[LINE_MAX], *t;
 
@@ -722,7 +751,7 @@ static int service_load_sysv_path(Service *s, const char *path) {
                                                 goto finish;
                                         }
 
-                                        r = sysv_translate_facility(n, file_name_from_path(path), &m);
+                                        r = sysv_translate_facility(n, path_get_file_name(path), &m);
                                         free(n);
 
                                         if (r < 0)
@@ -772,7 +801,7 @@ static int service_load_sysv_path(Service *s, const char *path) {
                                                 goto finish;
                                         }
 
-                                        r = sysv_translate_facility(n, file_name_from_path(path), &m);
+                                        r = sysv_translate_facility(n, path_get_file_name(path), &m);
 
                                         if (r < 0) {
                                                 log_error("[%s:%u] Failed to translate LSB dependency %s, ignoring: %s", path, line, n, strerror(-r));
@@ -1220,6 +1249,10 @@ static int service_load(Unit *u) {
                 if (s->type == _SERVICE_TYPE_INVALID)
                         s->type = s->bus_name ? SERVICE_DBUS : SERVICE_SIMPLE;
 
+                /* Oneshot services have disabled timeout by default */
+                if (s->type == SERVICE_ONESHOT && !s->timeout_defined)
+                        s->timeout_usec = 0;
+
                 service_fix_output(s);
 
                 if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0)
@@ -1332,12 +1365,10 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
         }
 
 #ifdef HAVE_SYSV_COMPAT
-        if (s->sysv_path)
+        if (s->is_sysv)
                 fprintf(f,
-                        "%sSysV Init Script Path: %s\n"
                         "%sSysV Init Script has LSB Header: %s\n"
                         "%sSysVEnabled: %s\n",
-                        prefix, s->sysv_path,
                         prefix, yes_no(s->sysv_has_lsb),
                         prefix, yes_no(s->sysv_enabled));
 
@@ -1464,8 +1495,11 @@ static void service_notify_sockets_dead(Service *s, bool failed_permanent) {
 
 static void service_set_state(Service *s, ServiceState state) {
         ServiceState old_state;
+        const UnitActiveState *table;
         assert(s);
 
+        table = s->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table;
+
         old_state = s->state;
         s->state = state;
 
@@ -1548,7 +1582,7 @@ static void service_set_state(Service *s, ServiceState state) {
         if (old_state != state)
                 log_debug("%s changed %s -> %s", UNIT(s)->id, service_state_to_string(old_state), service_state_to_string(state));
 
-        unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], s->reload_result == SERVICE_SUCCESS);
+        unit_notify(UNIT(s), table[old_state], table[state], s->reload_result == SERVICE_SUCCESS);
         s->reload_result = SERVICE_SUCCESS;
 }
 
@@ -1964,7 +1998,7 @@ static void service_enter_signal(Service *s, ServiceState state, ServiceResult f
                                 if ((r = set_put(pid_set, LONG_TO_PTR(s->control_pid))) < 0)
                                         goto fail;
 
-                        r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, pid_set, NULL);
+                        r = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, sig, true, false, pid_set, NULL);
                         if (r < 0) {
                                 if (r != -EAGAIN && r != -ESRCH && r != -ENOENT)
                                         log_warning("Failed to kill control group: %s", strerror(-r));
@@ -2111,7 +2145,7 @@ static void service_enter_start(Service *s) {
         /* We want to ensure that nobody leaks processes from
          * START_PRE here, so let's go on a killing spree, People
          * should not spawn long running processes from START_PRE. */
-        cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, NULL, "control");
+        cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, true, NULL, "control");
 
         if (s->type == SERVICE_FORKING) {
                 s->control_command_id = SERVICE_EXEC_START;
@@ -2127,7 +2161,7 @@ static void service_enter_start(Service *s) {
 
         r = service_spawn(s,
                           c,
-                          s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY,
+                          s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY || s->type == SERVICE_ONESHOT,
                           true,
                           true,
                           true,
@@ -2187,7 +2221,7 @@ static void service_enter_start_pre(Service *s) {
 
                 /* Before we start anything, let's clear up what might
                  * be left from previous runs. */
-                cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, NULL, "control");
+                cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, true, NULL, "control");
 
                 s->control_command_id = SERVICE_EXEC_START_PRE;
 
@@ -2227,6 +2261,8 @@ static void service_enter_restart(Service *s) {
 
                 if ((r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch)) < 0)
                         goto fail;
+
+                return;
         }
 
         /* Any units that are bound to this service must also be
@@ -2340,7 +2376,7 @@ static void service_run_next_main(Service *s) {
 
         r = service_spawn(s,
                           s->main_command,
-                          false,
+                          true,
                           true,
                           true,
                           true,
@@ -2429,7 +2465,16 @@ static int service_start(Unit *u) {
             s->state == SERVICE_START_POST)
                 return 0;
 
-        assert(s->state == SERVICE_DEAD || s->state == SERVICE_FAILED || s->state == SERVICE_AUTO_RESTART);
+        /* A service that will be restarted must be stopped first to
+         * trigger BindTo and/or OnFailure dependencies. If a user
+         * does not want to wait for the holdoff time to elapse, the
+         * service should be manually restarted, not started. */
+        if (s->state == SERVICE_AUTO_RESTART) {
+                log_warning("%s automatic restart is pending, must be stopped before issuing start request.", UNIT(s)->id);
+                return -ECANCELED;
+        }
+
+        assert(s->state == SERVICE_DEAD || s->state == SERVICE_FAILED);
 
         /* Make sure we don't enter a busy loop of some kind. */
         r = service_start_limit_test(s);
@@ -2453,8 +2498,7 @@ static int service_stop(Unit *u) {
 
         assert(s);
 
-        /* This is a user request, so don't do restarts on this
-         * shutdown. */
+        /* Don't create restart jobs from here. */
         s->forbid_restart = true;
 
         /* Already on it */
@@ -2466,9 +2510,9 @@ static int service_stop(Unit *u) {
             s->state == SERVICE_FINAL_SIGKILL)
                 return 0;
 
-        /* Don't allow a restart */
+        /* A restart will be scheduled or is in progress. */
         if (s->state == SERVICE_AUTO_RESTART) {
-                service_set_state(s, SERVICE_DEAD);
+                service_enter_dead(s, SERVICE_SUCCESS, false);
                 return 0;
         }
 
@@ -2677,9 +2721,13 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
 }
 
 static UnitActiveState service_active_state(Unit *u) {
+        const UnitActiveState *table;
+
         assert(u);
 
-        return state_translation_table[SERVICE(u)->state];
+        table = SERVICE(u)->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table;
+
+        return table[SERVICE(u)->state];
 }
 
 static const char *service_sub_state_to_string(Unit *u) {
@@ -2701,7 +2749,7 @@ static bool service_check_gc(Unit *u) {
                 return true;
 
 #ifdef HAVE_SYSV_COMPAT
-        if (s->sysv_path)
+        if (s->is_sysv)
                 return true;
 #endif
 
@@ -2924,6 +2972,11 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                 if (f != SERVICE_SUCCESS)
                         s->result = f;
 
+                /* Immediately get rid of the cgroup, so that the
+                 * kernel doesn't delay the cgroup empty messages for
+                 * the service cgroup any longer than necessary */
+                cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, SIGKILL, true, true, NULL, "control");
+
                 if (s->control_command &&
                     s->control_command->command_next &&
                     f == SERVICE_SUCCESS) {
@@ -3606,29 +3659,6 @@ static void service_reset_failed(Unit *u) {
         s->reload_result = SERVICE_SUCCESS;
 }
 
-static bool service_need_daemon_reload(Unit *u) {
-        Service *s = SERVICE(u);
-
-        assert(s);
-
-#ifdef HAVE_SYSV_COMPAT
-        if (s->sysv_path) {
-                struct stat st;
-
-                zero(st);
-                if (stat(s->sysv_path, &st) < 0)
-                        /* What, cannot access this anymore? */
-                        return true;
-
-                if (s->sysv_mtime > 0 &&
-                    timespec_load(&st.st_mtim) != s->sysv_mtime)
-                        return true;
-        }
-#endif
-
-        return false;
-}
-
 static int service_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusError *error) {
         Service *s = SERVICE(u);
         int r = 0;
@@ -3674,7 +3704,7 @@ static int service_kill(Unit *u, KillWho who, KillMode mode, int signo, DBusErro
                                 r = q;
                                 goto finish;
                         }
-                q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, pid_set, NULL);
+                q = cgroup_bonding_kill_list(UNIT(s)->cgroup_bondings, signo, false, false, pid_set, NULL);
                 if (q < 0)
                         if (q != -EAGAIN && q != -ESRCH && q != -ENOENT)
                                 r = q;
@@ -3774,7 +3804,6 @@ const UnitVTable service_vtable = {
                 "Unit\0"
                 "Service\0"
                 "Install\0",
-        .show_status = true,
 
         .init = service_init,
         .done = service_done,
@@ -3807,8 +3836,6 @@ const UnitVTable service_vtable = {
 
         .reset_failed = service_reset_failed,
 
-        .need_daemon_reload = service_need_daemon_reload,
-
         .cgroup_notify_empty = service_cgroup_notify_event,
         .notify_message = service_notify_message,
 
@@ -3820,6 +3847,23 @@ const UnitVTable service_vtable = {
         .bus_invalidating_properties =  bus_service_invalidating_properties,
 
 #ifdef HAVE_SYSV_COMPAT
-        .enumerate = service_enumerate
+        .enumerate = service_enumerate,
 #endif
+        .status_message_formats = {
+                .starting_stopping = {
+                        [0] = "Starting %s...",
+                        [1] = "Stopping %s...",
+                },
+                .finished_start_job = {
+                        [JOB_DONE]       = "Started %s.",
+                        [JOB_FAILED]     = "Failed to start %s.",
+                        [JOB_DEPENDENCY] = "Dependency failed for %s.",
+                        [JOB_TIMEOUT]    = "Timed out starting %s.",
+                },
+                .finished_stop_job = {
+                        [JOB_DONE]       = "Stopped %s.",
+                        [JOB_FAILED]     = "Stopped (with error) %s.",
+                        [JOB_TIMEOUT]    = "Timed out stopping %s.",
+                },
+        },
 };