chiark / gitweb /
service: consider a process exiting with SIGTERM a clean exit
[elogind.git] / service.c
index eb8688671b0d4c132921f748cabd2d7a75b09873..bf91561901f03d3fb8c23769049148962fdaeefa 100644 (file)
--- a/service.c
+++ b/service.c
 #define NEWLINES "\n\r"
 #define LINE_MAX 4096
 
-static const char * const rcnd_table[] = {
-        "/rc0.d",  SPECIAL_RUNLEVEL0_TARGET,
-        "/rc1.d",  SPECIAL_RUNLEVEL1_TARGET,
-        "/rc2.d",  SPECIAL_RUNLEVEL2_TARGET,
-        "/rc3.d",  SPECIAL_RUNLEVEL3_TARGET,
-        "/rc4.d",  SPECIAL_RUNLEVEL4_TARGET,
-        "/rc5.d",  SPECIAL_RUNLEVEL5_TARGET,
-        "/rc6.d",  SPECIAL_RUNLEVEL6_TARGET,
-        "/boot.d", SPECIAL_BASIC_TARGET
+typedef enum RunlevelType {
+        RUNLEVEL_UP,
+        RUNLEVEL_DOWN,
+        RUNLEVEL_BASIC
+} RunlevelType;
+
+static const struct {
+        const char *path;
+        const char *target;
+        const RunlevelType type;
+} rcnd_table[] = {
+        /* Standard SysV runlevels */
+        { "rc0.d",  SPECIAL_RUNLEVEL0_TARGET, RUNLEVEL_DOWN },
+        { "rc1.d",  SPECIAL_RUNLEVEL1_TARGET, RUNLEVEL_UP },
+        { "rc2.d",  SPECIAL_RUNLEVEL2_TARGET, RUNLEVEL_UP },
+        { "rc3.d",  SPECIAL_RUNLEVEL3_TARGET, RUNLEVEL_UP },
+        { "rc4.d",  SPECIAL_RUNLEVEL4_TARGET, RUNLEVEL_UP },
+        { "rc5.d",  SPECIAL_RUNLEVEL5_TARGET, RUNLEVEL_UP },
+        { "rc6.d",  SPECIAL_RUNLEVEL6_TARGET, RUNLEVEL_DOWN },
+
+        /* SuSE style boot.d */
+        { "boot.d", SPECIAL_BASIC_TARGET,     RUNLEVEL_BASIC },
+
+        /* Debian style rcS.d */
+        { "rcS.d",  SPECIAL_BASIC_TARGET,     RUNLEVEL_BASIC },
 };
 
+#define RUNLEVELS_UP "12345"
+/* #define RUNLEVELS_DOWN "06" */
+/* #define RUNLEVELS_BOOT "bBsS" */
+
 static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
         [SERVICE_DEAD] = UNIT_INACTIVE,
         [SERVICE_START_PRE] = UNIT_ACTIVATING,
@@ -273,66 +293,6 @@ static int sysv_exec_commands(Service *s) {
         return 0;
 }
 
-static int priority_from_rcd(Service *s, const char *init_script) {
-        char **p;
-        unsigned i;
-
-        STRV_FOREACH(p, UNIT(s)->meta.manager->sysvrcnd_path)
-                for (i = 0; i < ELEMENTSOF(rcnd_table); i += 2) {
-                        char *path;
-                        DIR *d;
-                        struct dirent *de;
-
-                        if (asprintf(&path, "%s/%s", *p, rcnd_table[i]) < 0)
-                                return -ENOMEM;
-
-                        d = opendir(path);
-                        free(path);
-
-                        if (!d) {
-                                if (errno != ENOENT)
-                                        log_warning("opendir() failed on %s: %s", path, strerror(errno));
-
-                                continue;
-                        }
-
-                        while ((de = readdir(d))) {
-                                int a, b;
-
-                                if (ignore_file(de->d_name))
-                                        continue;
-
-                                if (de->d_name[0] != 'S')
-                                        continue;
-
-                                if (strlen(de->d_name) < 4)
-                                        continue;
-
-                                if (!streq(de->d_name + 3, init_script))
-                                        continue;
-
-                                /* Yay, we found it! Now decode the priority */
-
-                                a = undecchar(de->d_name[1]);
-                                b = undecchar(de->d_name[2]);
-
-                                if (a < 0 || b < 0)
-                                        continue;
-
-                                s->sysv_start_priority = a*10 + b;
-
-                                log_debug("Determined priority %i from link farm for %s", s->sysv_start_priority, UNIT(s)->meta.id);
-
-                                closedir(d);
-                                return 0;
-                        }
-
-                        closedir(d);
-                }
-
-        return 0;
-}
-
 static int service_load_sysv_path(Service *s, const char *path) {
         FILE *f;
         Unit *u;
@@ -414,9 +374,12 @@ static int service_load_sysv_path(Service *s, const char *path) {
                                         continue;
                                 }
 
+                                /* A start priority gathered from the
+                                 * symlink farms is preferred over the
+                                 * data from the LSB header. */
                                 if (start_priority < 0 || start_priority > 99)
                                         log_warning("[%s:%u] Start priority out of range. Ignoring.", path, line);
-                                else
+                                else if (s->sysv_start_priority < 0)
                                         s->sysv_start_priority = start_priority;
 
                                 char_array_0(runlevels);
@@ -434,7 +397,6 @@ static int service_load_sysv_path(Service *s, const char *path) {
                                         s->sysv_runlevels = d;
                                 }
 
-
                         } else if (startswith(t, "description:")) {
 
                                 size_t k = strlen(t);
@@ -629,25 +591,10 @@ static int service_load_sysv_path(Service *s, const char *path) {
                 }
         }
 
-        /* If init scripts have no LSB header, then we enforce the
-         * ordering via the chkconfig priorities. We try to determine
-         * a priority for *all* init scripts here, since they are
-         * needed as soon as at least one non-LSB script is used. */
-
-        if (s->sysv_start_priority < 0) {
-                log_debug("%s has no chkconfig header, trying to determine SysV priority from link farm.", u->meta.id);
-
-                if ((r = priority_from_rcd(s, file_name_from_path(path))) < 0)
-                        goto finish;
-
-                if (s->sysv_start_priority < 0)
-                        log_warning("%s has neither a chkconfig header nor a directory link, cannot order unit!", u->meta.id);
-        }
-
         if ((r = sysv_exec_commands(s)) < 0)
                 goto finish;
 
-        if (!s->sysv_runlevels || chars_intersect("12345", s->sysv_runlevels)) {
+        if (!s->sysv_runlevels || chars_intersect(RUNLEVELS_UP, s->sysv_runlevels)) {
                 /* If there a runlevels configured for this service
                  * but none of the standard ones, then we assume this
                  * is some special kind of service (which might be
@@ -657,14 +604,14 @@ static int service_load_sysv_path(Service *s, const char *path) {
                 if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true)) < 0 ||
                     (r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_BASIC_TARGET, NULL, true)) < 0)
                         goto finish;
-        }
+
+        } else
+                /* Don't timeout special services during boot (like fsck) */
+                s->timeout_usec = 0;
 
         /* Special setting for all SysV services */
         s->valid_no_process = true;
-
-        /* Don't timeout special services during boot (like fsck) */
-        if (s->sysv_runlevels && !chars_intersect("12345", s->sysv_runlevels))
-                s->timeout_usec = -1;
+        s->kill_mode = KILL_PROCESS_GROUP;
 
         u->meta.load_state = UNIT_LOADED;
         r = 0;
@@ -694,8 +641,26 @@ static int service_load_sysv_name(Service *s, const char *name) {
                 path[strlen(path)-8] = 0;
 
                 r = service_load_sysv_path(s, path);
+
+                if (r >= 0 && UNIT(s)->meta.load_state == UNIT_STUB) {
+                        /* Try Debian style .sh source'able init scripts */
+                        strcat(path, ".sh");
+                        r = service_load_sysv_path(s, path);
+                }
+
                 free(path);
 
+                if (r >= 0 && UNIT(s)->meta.load_state == UNIT_STUB) {
+                        /* Try Suse style boot.xxxx init scripts */
+
+                        if (asprintf(&path, "%s/boot.%s", *p, name) < 0)
+                                return -ENOMEM;
+
+                        path[strlen(path)-8] = 0;
+                        r = service_load_sysv_path(s, path);
+                        free(path);
+                }
+
                 if (r < 0)
                         return r;
 
@@ -1241,6 +1206,7 @@ static int service_spawn(
                        argv,
                        &s->exec_context,
                        fds, n_fds,
+                       s->meta.manager->environment,
                        apply_permissions,
                        apply_chroot,
                        UNIT(s)->meta.manager->confirm_spawn,
@@ -1318,6 +1284,7 @@ static void service_enter_dead(Service *s, bool success, bool allow_restart) {
                 s->failure = true;
 
         if (allow_restart &&
+            s->allow_restart &&
             (s->restart == SERVICE_RESTART_ALWAYS ||
              (s->restart == SERVICE_RESTART_ON_SUCCESS && !s->failure))) {
 
@@ -1412,7 +1379,7 @@ static void service_enter_signal(Service *s, ServiceState state, bool success) {
                 }
         }
 
-        if (sent) {
+        if (sent && (s->main_pid > 0 || s->control_pid > 0)) {
                 if (s->timeout_usec > 0)
                         if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0)
                                 goto fail;
@@ -1721,6 +1688,7 @@ static int service_start(Unit *u) {
 
         s->failure = false;
         s->main_pid_known = false;
+        s->allow_restart = true;
 
         service_enter_start_pre(s);
         return 0;
@@ -1754,6 +1722,10 @@ static int service_stop(Unit *u) {
 
         assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED);
 
+        /* This is a user request, so don't do restarts on this
+         * shutdown. */
+        s->allow_restart = false;
+
         service_enter_stop(s, true);
         return 0;
 }
@@ -1799,7 +1771,7 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
          * commands attached here, we will start from the first one
          * again */
         if (s->control_command_id >= 0)
-                unit_serialize_item(u, f, "control-command", mount_exec_command_to_string(s->control_command_id));
+                unit_serialize_item(u, f, "control-command", service_exec_command_to_string(s->control_command_id));
 
         if (s->socket_fd >= 0) {
                 int copy;
@@ -1918,7 +1890,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
         assert(s);
         assert(pid >= 0);
 
-        success = code == CLD_EXITED && status == 0;
+        success = is_clean_exit(code, status);
         s->failure = s->failure || !success;
 
         if (s->main_pid == pid) {
@@ -2181,12 +2153,12 @@ static int service_enumerate(Manager *m) {
         assert(m);
 
         STRV_FOREACH(p, m->sysvrcnd_path)
-                for (i = 0; i < ELEMENTSOF(rcnd_table); i += 2) {
+                for (i = 0; i < ELEMENTSOF(rcnd_table); i ++) {
                         struct dirent *de;
 
                         free(path);
                         path = NULL;
-                        if (asprintf(&path, "%s/%s", *p, rcnd_table[i]) < 0) {
+                        if (asprintf(&path, "%s/%s", *p, rcnd_table[i].path) < 0) {
                                 r = -ENOMEM;
                                 goto finish;
                         }
@@ -2203,6 +2175,7 @@ static int service_enumerate(Manager *m) {
 
                         while ((de = readdir(d))) {
                                 Unit *service;
+                                int a, b;
 
                                 if (ignore_file(de->d_name))
                                         continue;
@@ -2213,9 +2186,15 @@ static int service_enumerate(Manager *m) {
                                 if (strlen(de->d_name) < 4)
                                         continue;
 
+                                a = undecchar(de->d_name[1]);
+                                b = undecchar(de->d_name[2]);
+
+                                if (a < 0 || b < 0)
+                                        continue;
+
                                 free(fpath);
                                 fpath = NULL;
-                                if (asprintf(&fpath, "%s/%s/%s", *p, rcnd_table[i], de->d_name) < 0) {
+                                if (asprintf(&fpath, "%s/%s/%s", *p, rcnd_table[i].path, de->d_name) < 0) {
                                         r = -ENOMEM;
                                         goto finish;
                                 }
@@ -2229,31 +2208,47 @@ static int service_enumerate(Manager *m) {
                                 }
 
                                 free(name);
-                                name = NULL;
-                                if (asprintf(&name, "%s.service", de->d_name+3) < 0) {
+                                if (!(name = new(char, strlen(de->d_name) - 3 + 8 + 1))) {
                                         r = -ENOMEM;
                                         goto finish;
                                 }
 
-                                if ((r = manager_load_unit(m, name, NULL, &service)) < 0)
-                                        goto finish;
+                                if (startswith(de->d_name+3, "boot."))
+                                        /* Drop SuSE-style boot. prefix */
+                                        strcpy(stpcpy(name, de->d_name + 3 + 5), ".service");
+                                else if (endswith(de->d_name+3, ".sh"))
+                                        /* Drop Debian-style .sh suffix */
+                                        strcpy(stpcpy(name, de->d_name + 3) - 3, ".service");
+                                else
+                                        /* Normal init scripts */
+                                        strcpy(stpcpy(name, de->d_name + 3), ".service");
+
+                                if ((r = manager_load_unit_prepare(m, name, NULL, &service)) < 0) {
+                                        log_warning("Failed to prepare unit %s: %s", name, strerror(-r));
+                                        continue;
+                                }
+
+                                if (de->d_name[0] == 'S' &&
+                                    (rcnd_table[i].type == RUNLEVEL_UP || rcnd_table[i].type == RUNLEVEL_BASIC))
+                                        SERVICE(service)->sysv_start_priority =
+                                                MAX(a*10 + b, SERVICE(service)->sysv_start_priority);
+
+                                manager_dispatch_load_queue(m);
+                                service = unit_follow_merge(service);
 
                                 if (de->d_name[0] == 'S') {
                                         Unit *runlevel_target;
 
-                                        if ((r = manager_load_unit(m, rcnd_table[i+1], NULL, &runlevel_target)) < 0)
+                                        if ((r = manager_load_unit(m, rcnd_table[i].target, NULL, &runlevel_target)) < 0)
                                                 goto finish;
 
                                         if ((r = unit_add_dependency(runlevel_target, UNIT_WANTS, service, true)) < 0)
                                                 goto finish;
 
-                                        if ((r = unit_add_dependency(runlevel_target, UNIT_AFTER, service, true)) < 0)
+                                        if ((r = unit_add_dependency(service, UNIT_BEFORE, runlevel_target, true)) < 0)
                                                 goto finish;
 
-                                } else if (de->d_name[0] == 'K' &&
-                                           (streq(rcnd_table[i+1], SPECIAL_RUNLEVEL0_TARGET) ||
-                                            streq(rcnd_table[i+1], SPECIAL_RUNLEVEL6_TARGET))) {
-
+                                } else if (de->d_name[0] == 'K' && rcnd_table[i].type == RUNLEVEL_DOWN) {
                                         Unit *shutdown_target;
 
                                         /* We honour K links only for
@@ -2270,10 +2265,10 @@ static int service_enumerate(Manager *m) {
                                         if ((r = manager_load_unit(m, SPECIAL_SHUTDOWN_TARGET, NULL, &shutdown_target)) < 0)
                                                 goto finish;
 
-                                        if ((r = unit_add_dependency(shutdown_target, UNIT_CONFLICTS, service, true)) < 0)
+                                        if ((r = unit_add_dependency(service, UNIT_CONFLICTS, shutdown_target, true)) < 0)
                                                 goto finish;
 
-                                        if ((r = unit_add_dependency(shutdown_target, UNIT_BEFORE, service, true)) < 0)
+                                        if ((r = unit_add_dependency(service, UNIT_BEFORE, shutdown_target, true)) < 0)
                                                 goto finish;
                                 }
                         }
@@ -2285,7 +2280,9 @@ finish:
         free(path);
         free(fpath);
         free(name);
-        closedir(d);
+
+        if (d)
+                closedir(d);
 
         return r;
 }