first attempt at proper service/socket logic
authorLennart Poettering <lennart@poettering.net>
Tue, 26 Jan 2010 03:18:44 +0000 (04:18 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 26 Jan 2010 03:18:44 +0000 (04:18 +0100)
32 files changed:
automount.c
conf-parser.c
conf-parser.h
device.c
execute.c
execute.h
hashmap.c
hashmap.h
job.c
job.h
list.h
load-fragment.c
macro.h
main.c
manager.c
manager.h
milestone.c
mount.c
name.c
name.h
service.c
service.h
set.c
set.h
snapshot.c
socket.c
socket.h
strv.c
strv.h
timer.c
util.c
util.h

index 84691e6..b64bacb 100644 (file)
@@ -8,13 +8,13 @@
 #include "load-fstab.h"
 #include "load-dropin.h"
 
-static int automount_load(Name *n) {
+static int automount_init(Name *n) {
         int r;
         Automount *a = AUTOMOUNT(n);
 
         assert(a);
 
-        exec_context_defaults(&a->exec_context);
+        exec_context_init(&a->exec_context);
 
         /* Load a .automount file */
         if ((r = name_load_fragment(n)) < 0 && errno != -ENOENT)
@@ -31,6 +31,13 @@ static int automount_load(Name *n) {
         return 0;
 }
 
+static void automount_done(Name *n) {
+        Automount *d = AUTOMOUNT(n);
+
+        assert(d);
+        free(d->path);
+}
+
 static void automount_dump(Name *n, FILE *f, const char *prefix) {
 
         static const char* const state_table[_AUTOMOUNT_STATE_MAX] = {
@@ -67,7 +74,7 @@ static void automount_dump(Name *n, FILE *f, const char *prefix) {
         for (c = 0; c < _AUTOMOUNT_EXEC_MAX; c++) {
                 ExecCommand *i;
 
-                LIST_FOREACH(i, s->exec_command[c])
+                LIST_FOREACH(command, i, s->exec_command[c])
                         fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path);
         }
 }
@@ -88,24 +95,13 @@ static NameActiveState automount_active_state(Name *n) {
         return table[AUTOMOUNT(n)->state];
 }
 
-static void automount_free_hook(Name *n) {
-        Automount *d = AUTOMOUNT(n);
-
-        assert(d);
-        free(d->path);
-}
-
 const NameVTable automount_vtable = {
         .suffix = ".mount",
 
-        .load = automount_load,
-        .dump = automount_dump,
-
-        .start = NULL,
-        .stop = NULL,
-        .reload = NULL,
+        .init = automount_init,
+        .done = automount_done,
 
-        .active_state = automount_active_state,
+        .dump = automount_dump,
 
-        .free_hook = automount_free_hook
+        .active_state = automount_active_state
 };
index 2ea6911..2618858 100644 (file)
@@ -337,6 +337,36 @@ int config_parse_string(
         return 0;
 }
 
+int config_parse_path(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        char **s = data;
+        char *n;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (*rvalue != '/') {
+                log_error("[%s:%u] Not an absolute path: %s", filename, line, rvalue);
+                return -EINVAL;
+        }
+
+        if (!(n = strdup(rvalue)))
+                return -ENOMEM;
+
+        free(*s);
+        *s = n;
+
+        return 0;
+}
 
 int config_parse_strv(
                 const char *filename,
@@ -360,7 +390,7 @@ int config_parse_strv(
         assert(data);
 
         k = strv_length(*sv);
-        FOREACH_WORD(w, &l, rvalue, state)
+        FOREACH_WORD_QUOTED(w, l, rvalue, state)
                 k++;
 
         if (!(n = new(char*, k+1)))
@@ -368,7 +398,7 @@ int config_parse_strv(
 
         for (k = 0; (*sv)[k]; k++)
                 n[k] = (*sv)[k];
-        FOREACH_WORD(w, &l, rvalue, state)
+        FOREACH_WORD_QUOTED(w, l, rvalue, state)
                 if (!(n[k++] = strndup(w, l)))
                         goto fail;
 
@@ -381,6 +411,7 @@ int config_parse_strv(
 fail:
         for (; k > 0; k--)
                 free(n[k-1]);
+        free(n);
 
         return -ENOMEM;
 }
index 25b77eb..a8bed39 100644 (file)
@@ -29,6 +29,7 @@ int config_parse_unsigned(const char *filename, unsigned line, const char *secti
 int config_parse_size(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
 int config_parse_bool(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
 int config_parse_string(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
+int config_parse_path(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
 int config_parse_strv(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
 
 #endif
index 79847c4..ec0a40a 100644 (file)
--- a/device.c
+++ b/device.c
@@ -4,6 +4,13 @@
 #include "device.h"
 #include "strv.h"
 
+static void device_done(Name *n) {
+        Device *d = DEVICE(n);
+
+        assert(d);
+        strv_free(d->sysfs);
+}
+
 static void device_dump(Name *n, FILE *f, const char *prefix) {
 
         static const char* const state_table[_DEVICE_STATE_MAX] = {
@@ -24,24 +31,12 @@ static NameActiveState device_active_state(Name *n) {
         return DEVICE(n)->state == DEVICE_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
 }
 
-static void device_free_hook(Name *n) {
-        Device *d = DEVICE(n);
-
-        assert(d);
-        strv_free(d->sysfs);
-}
-
 const NameVTable device_vtable = {
         .suffix = ".device",
 
-        .load = name_load_fragment_and_dropin,
+        .init = name_load_fragment_and_dropin,
+        .done = device_done,
         .dump = device_dump,
 
-        .start = NULL,
-        .stop = NULL,
-        .reload = NULL,
-
-        .active_state = device_active_state,
-
-        .free_hook = device_free_hook
+        .active_state = device_active_state
 };
index 9bb8351..8cd4f60 100644 (file)
--- a/execute.c
+++ b/execute.c
 /*-*- Mode: C; c-basic-offset: 8 -*-*/
 
 #include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
 
 #include "execute.h"
 #include "strv.h"
 #include "macro.h"
 #include "util.h"
 
-int exec_spawn(const ExecCommand *command, const ExecContext *context, pid_t *ret) {
+static int close_fds(int except[], unsigned n_except) {
+        DIR *d;
+        struct dirent *de;
+        int r = 0;
+
+        /* Modifies the fds array! (sorts it) */
+
+        if (!(d = opendir("/proc/self/fd")))
+                return -errno;
+
+        while ((de = readdir(d))) {
+                int fd;
+
+                if (de->d_name[0] == '.')
+                        continue;
+
+                if ((r = safe_atoi(de->d_name, &fd)) < 0)
+                        goto finish;
+
+                if (fd < 3)
+                        continue;
+
+                if (fd == dirfd(d))
+                        continue;
+
+                if (except) {
+                        bool found;
+                        unsigned i;
+
+                        found = false;
+                        for (i = 0; i < n_except; i++)
+                                if (except[i] == fd) {
+                                        found = true;
+                                        break;
+                                }
+
+                        if (found)
+                                continue;
+                }
+
+                if ((r = close_nointr(fd)) < 0)
+                        goto finish;
+        }
+
+finish:
+        closedir(d);
+        return r;
+}
+
+static int shift_fds(int fds[], unsigned n_fds) {
+        int start, restart_from;
+
+        if (n_fds <= 0)
+                return 0;
+
+        assert(fds);
+
+        start = 0;
+        for (;;) {
+                int i;
+
+                restart_from = -1;
+
+                for (i = start; i < (int) n_fds; i++) {
+                        int nfd;
+
+                        /* Already at right index? */
+                        if (fds[i] == i+3)
+                                continue;
+
+                        if ((nfd = fcntl(fds[i], F_DUPFD, i+3)) < 0)
+                                return -errno;
+
+                        assert_se(close_nointr(fds[i]));
+                        fds[i] = nfd;
+
+                        /* Hmm, the fd we wanted isn't free? Then
+                         * let's remember that and try again from here*/
+                        if (nfd != i+3 && restart_from < 0)
+                                restart_from = i;
+                }
+
+                if (restart_from < 0)
+                        break;
+
+                start = restart_from;
+        }
+
+        return 0;
+}
+
+int exec_spawn(const ExecCommand *command, const ExecContext *context, int *fds, unsigned n_fds, pid_t *ret) {
+        pid_t pid;
+
         assert(command);
         assert(context);
         assert(ret);
+        assert(fds || n_fds <= 0);
+
+        if ((pid = fork()) < 0)
+                return -errno;
+
+        if (pid == 0) {
+                char **e, **f = NULL;
+                int i, r;
+                char t[16];
+                /* child */
 
+                umask(context->umask);
+
+                if (chdir(context->directory ? context->directory : "/") < 0) {
+                        r = EXIT_CHDIR;
+                        goto fail;
+                }
+
+                snprintf(t, sizeof(t), "%i", context->oom_adjust);
+                char_array_0(t);
+
+                if (write_one_line_file("/proc/self/oom_adj", t) < 0) {
+                        r = EXIT_OOM_ADJUST;
+                        goto fail;
+                }
+
+                if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) {
+                        r = EXIT_NICE;
+                        goto fail;
+                }
+
+                if (close_fds(fds, n_fds) < 0 ||
+                    shift_fds(fds, n_fds) < 0) {
+                        r = EXIT_FDS;
+                        goto fail;
+                }
+
+                for (i = 0; i < RLIMIT_NLIMITS; i++) {
+                        if (!context->rlimit[i])
+                                continue;
+
+                        if (setrlimit(i, context->rlimit[i]) < 0) {
+                                r = EXIT_LIMITS;
+                                goto fail;
+                        }
+                }
+
+                if (n_fds > 0) {
+                        char a[64], b[64];
+                        char *listen_env[3] = {
+                                a,
+                                b,
+                                NULL
+                        };
+
+                        snprintf(a, sizeof(a), "LISTEN_PID=%llu", (unsigned long long) getpid());
+                        snprintf(b, sizeof(b), "LISTEN_FDS=%u", n_fds);
+
+                        a[sizeof(a)-1] = 0;
+                        b[sizeof(b)-1] = 0;
+
+                        if (context->environment) {
+                                if (!(f = strv_merge(listen_env, context->environment))) {
+                                        r = EXIT_MEMORY;
+                                        goto fail;
+                                }
+                                e = f;
+                        } else
+                                e = listen_env;
+
+                } else
+                        e = context->environment;
+
+                execve(command->path, command->argv, e);
+                r = EXIT_EXEC;
+
+        fail:
+                strv_free(f);
+                _exit(r);
+        }
+
+        *ret = pid;
         return 0;
 }
 
-void exec_context_free(ExecContext *c) {
+void exec_context_init(ExecContext *c) {
+        assert(c);
+
+        c->umask = 0002;
+        cap_clear(c->capabilities);
+        c->oom_adjust = 0;
+        c->nice = 0;
+}
+
+void exec_context_done(ExecContext *c) {
         unsigned l;
 
         assert(c);
 
         strv_free(c->environment);
+        c->environment = NULL;
 
-        for (l = 0; l < ELEMENTSOF(c->rlimit); l++)
+        for (l = 0; l < ELEMENTSOF(c->rlimit); l++) {
                 free(c->rlimit[l]);
+                c->rlimit[l] = NULL;
+        }
+
+        free(c->directory);
+        c->directory = NULL;
 
-        free(c->chdir);
         free(c->user);
+        c->user = NULL;
+
         free(c->group);
-        free(c->supplementary_groups);
+        c->group = NULL;
+
+        strv_free(c->supplementary_groups);
+        c->supplementary_groups = NULL;
 }
 
 void exec_command_free_list(ExecCommand *c) {
         ExecCommand *i;
 
         while ((i = c)) {
-                LIST_REMOVE(ExecCommand, c, i);
+                LIST_REMOVE(ExecCommand, command, c, i);
 
                 free(i->path);
                 free(i->argv);
@@ -43,6 +240,16 @@ void exec_command_free_list(ExecCommand *c) {
         }
 }
 
+void exec_command_free_array(ExecCommand **c, unsigned n) {
+        unsigned i;
+
+        for (i = 0; i < n; i++) {
+                exec_command_free_list(c[i]);
+                c[i] = NULL;
+        }
+}
+
+
 void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
         assert(c);
         assert(f);
@@ -52,17 +259,20 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
 
         fprintf(f,
                 "%sUmask: %04o\n"
-                "%sDumpable: %s\n"
-                "%sDirectory: %s\n",
+                "%sDirectory: %s\n"
+                "%sNice: %i\n"
+                "%sOOMAdjust: %i\n",
                 prefix, c->umask,
-                prefix, yes_no(c->dumpable),
-                prefix, c->chdir ? c->chdir : "/");
+                prefix, c->directory ? c->directory : "/",
+                prefix, c->nice,
+                prefix, c->oom_adjust);
 }
 
-void exec_context_defaults(ExecContext *c) {
-        assert(c);
+void exec_status_fill(ExecStatus *s, pid_t pid, int code, int status) {
+        assert(s);
 
-        c->umask = 0002;
-        cap_clear(c->capabilities);
-        c->dumpable = true;
+        s->pid = pid;
+        s->code = code;
+        s->status = status;
+        s->timestamp = now(CLOCK_REALTIME);
 }
index 552e4a9..e9bd686 100644 (file)
--- a/execute.h
+++ b/execute.h
@@ -14,10 +14,11 @@ typedef struct ExecContext ExecContext;
 #include <stdio.h>
 
 #include "list.h"
+#include "util.h"
 
 struct ExecStatus {
         pid_t pid;
-        time_t timestamp;
+        usec_t timestamp;
         int code;     /* as in siginfo_t::si_code */
         int status;   /* as in sigingo_t::si_status */
 };
@@ -25,20 +26,20 @@ struct ExecStatus {
 struct ExecCommand {
         char *path;
         char **argv;
-        ExecStatus last_exec_status;
-        LIST_FIELDS(ExecCommand);
+        ExecStatus exec_status;
+        LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */
 };
 
 struct ExecContext {
         char **environment;
         mode_t umask;
         struct rlimit *rlimit[RLIMIT_NLIMITS];
-        cap_t capabilities;
-        bool capabilities_set:1;
-        bool dumpable:1;
         int oom_adjust;
         int nice;
-        char *chdir;
+        char *directory;
+
+        cap_t capabilities;
+        bool capabilities_set:1;
 
         /* since resolving these names might might involve socket
          * connections and we don't want to deadlock ourselves these
@@ -48,13 +49,40 @@ struct ExecContext {
         char **supplementary_groups;
 };
 
-int exec_spawn(const ExecCommand *command, const ExecContext *context, pid_t *ret);
+typedef enum ExitStatus {
+        /* EXIT_SUCCESS defined by libc */
+        /* EXIT_FAILURE defined by libc */
+        EXIT_INVALIDARGUMENT = 2,
+        EXIT_NOTIMPLEMENTED = 3,
+        EXIT_NOPERMISSION = 4,
+        EXIT_NOTINSTALLED = 5,
+        EXIT_NOTCONFIGURED = 6,
+        EXIT_NOTRUNNING = 7,
+
+        /* The LSB suggests that error codes >= 200 are "reserved". We
+         * use them here under the assumption that they hence are
+         * unused by init scripts.
+         *
+         * http://refspecs.freestandards.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */
+
+        EXIT_CHDIR = 200,
+        EXIT_NICE,
+        EXIT_FDS,
+        EXIT_EXEC,
+        EXIT_MEMORY,
+        EXIT_LIMITS,
+        EXIT_OOM_ADJUST
+} ExitStatus;
+
+int exec_spawn(const ExecCommand *command, const ExecContext *context, int *fds, unsigned n_fds, pid_t *ret);
 
-void exec_context_free(ExecContext *c);
 void exec_command_free_list(ExecCommand *c);
+void exec_command_free_array(ExecCommand **c, unsigned n);
 
+void exec_context_init(ExecContext *c);
+void exec_context_done(ExecContext *c);
 void exec_context_dump(ExecContext *c, FILE* f, const char *prefix);
 
-void exec_context_defaults(ExecContext *c);
+void exec_status_fill(ExecStatus *s, pid_t pid, int code, int status);
 
 #endif
index a81bcc4..5bcf692 100644 (file)
--- a/hashmap.c
+++ b/hashmap.c
@@ -69,6 +69,18 @@ Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) {
         return h;
 }
 
+int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func) {
+        assert(h);
+
+        if (*h)
+                return 0;
+
+        if (!(*h = hashmap_new(hash_func, compare_func)))
+                return -ENOMEM;
+
+        return 0;
+}
+
 static void remove_entry(Hashmap *h, struct hashmap_entry *e) {
         assert(h);
         assert(e);
@@ -248,26 +260,26 @@ void* hashmap_remove_value(Hashmap *h, const void *key, void *value) {
         return value;
 }
 
-void *hashmap_iterate(Hashmap *h, void **state, const void **key) {
+void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) {
         struct hashmap_entry *e;
 
-        assert(state);
+        assert(i);
 
         if (!h)
                 goto at_end;
 
-        if (*state == (void*) -1)
+        if (*i == ITERATOR_LAST)
                 goto at_end;
 
-        if (!*state && !h->iterate_list_head)
+        if (*i == ITERATOR_FIRST && !h->iterate_list_head)
                 goto at_end;
 
-        e = *state ? *state : h->iterate_list_head;
+        e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry*) *i;
 
         if (e->iterate_next)
-                *state = e->iterate_next;
+                *i = (Iterator) e->iterate_next;
         else
-                *state = (void*) -1;
+                *i = ITERATOR_LAST;
 
         if (key)
                 *key = e->key;
@@ -275,7 +287,7 @@ void *hashmap_iterate(Hashmap *h, void **state, const void **key) {
         return e->value;
 
 at_end:
-        *state = (void *) -1;
+        *i = ITERATOR_LAST;
 
         if (key)
                 *key = NULL;
@@ -283,26 +295,26 @@ at_end:
         return NULL;
 }
 
-void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key) {
+void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key) {
         struct hashmap_entry *e;
 
-        assert(state);
+        assert(i);
 
         if (!h)
                 goto at_beginning;
 
-        if (*state == (void*) -1)
+        if (*i == ITERATOR_FIRST)
                 goto at_beginning;
 
-        if (!*state && !h->iterate_list_tail)
+        if (*i == ITERATOR_LAST && !h->iterate_list_tail)
                 goto at_beginning;
 
-        e = *state ? *state : h->iterate_list_tail;
+        e = *i == ITERATOR_LAST ? h->iterate_list_tail : (struct hashmap_entry*) *i;
 
         if (e->iterate_previous)
-                *state = e->iterate_previous;
+                *i = (Iterator) e->iterate_previous;
         else
-                *state = (void*) -1;
+                *i = ITERATOR_FIRST;
 
         if (key)
                 *key = e->key;
@@ -310,7 +322,7 @@ void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key) {
         return e->value;
 
 at_beginning:
-        *state = (void *) -1;
+        *i = ITERATOR_FIRST;
 
         if (key)
                 *key = NULL;
@@ -318,6 +330,23 @@ at_beginning:
         return NULL;
 }
 
+void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i) {
+        unsigned hash;
+        struct hashmap_entry *e;
+
+        if (!h)
+                return NULL;
+
+        hash = h->hash_func(key) % NBUCKETS;
+
+        if (!(e = hash_scan(h, hash, key)))
+                return NULL;
+
+        *i = (Iterator) e;
+
+        return e->value;
+}
+
 void* hashmap_first(Hashmap *h) {
 
         if (!h)
index ae7f74b..e4a02e8 100644 (file)
--- a/hashmap.h
+++ b/hashmap.h
  * instantiate an object for each Hashmap use. */
 
 typedef struct Hashmap Hashmap;
+typedef struct _IteratorStruct _IteratorStruct;
+typedef _IteratorStruct* Iterator;
+
+#define ITERATOR_FIRST ((Iterator) 0)
+#define ITERATOR_LAST ((Iterator) -1)
 
 typedef unsigned (*hash_func_t)(const void *p);
 typedef int (*compare_func_t)(const void *a, const void *b);
@@ -24,6 +29,7 @@ int trivial_compare_func(const void *a, const void *b);
 Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func);
 void hashmap_free(Hashmap *h);
 Hashmap *hashmap_copy(Hashmap *h);
+int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func);
 
 int hashmap_put(Hashmap *h, const void *key, void *value);
 int hashmap_replace(Hashmap *h, const void *key, void *value);
@@ -36,21 +42,22 @@ int hashmap_merge(Hashmap *h, Hashmap *other);
 unsigned hashmap_size(Hashmap *h);
 bool hashmap_isempty(Hashmap *h);
 
-void *hashmap_iterate(Hashmap *h, void **state, const void **key);
-void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key);
+void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key);
+void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key);
+void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i);
 
 void hashmap_clear(Hashmap *h);
 void *hashmap_steal_first(Hashmap *h);
 void* hashmap_first(Hashmap *h);
 void* hashmap_last(Hashmap *h);
 
-#define HASHMAP_FOREACH(e, h, state) \
-        for ((state) = NULL, (e) = hashmap_iterate((h), &(state), NULL); (e); (e) = hashmap_iterate((h), &(state), NULL))
+#define HASHMAP_FOREACH(e, h, i) \
+        for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); (e); (e) = hashmap_iterate((h), &(i), NULL))
 
-#define HASHMAP_FOREACH_KEY(e, k, h, state) \
-        for ((state) = NULL, (e) = hashmap_iterate((h), &(state), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(state), (const void**) &(k)))
+#define HASHMAP_FOREACH_KEY(e, k, h, i) \
+        for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(i), (const void**) &(k)))
 
-#define HASHMAP_FOREACH_BACKWARDS(e, h, state) \
-        for ((state) = NULL, (e) = hashmap_iterate_backwards((h), &(state), NULL); (e); (e) = hashmap_iterate_backwards((h), &(state), NULL))
+#define HASHMAP_FOREACH_BACKWARDS(e, h, i) \
+        for ((i) = ITERATE_LAST, (e) = hashmap_iterate_backwards((h), &(i), NULL); (e); (e) = hashmap_iterate_backwards((h), &(i), NULL))
 
 #endif
diff --git a/job.c b/job.c
index 7accf72..81d7a19 100644 (file)
--- a/job.c
+++ b/job.c
@@ -276,33 +276,8 @@ bool job_type_is_conflicting(JobType a, JobType b) {
         return (a == JOB_STOP) != (b == JOB_STOP);
 }
 
-bool job_type_is_applicable(JobType j, NameType n) {
-        assert(j >= 0 && j < _JOB_TYPE_MAX);
-        assert(n >= 0 && n < _NAME_TYPE_MAX);
-
-        switch (j) {
-                case JOB_VERIFY_ACTIVE:
-                case JOB_START:
-                        return true;
-
-                case JOB_STOP:
-                case JOB_RESTART:
-                case JOB_TRY_RESTART:
-                        return name_type_can_start(n);
-
-                case JOB_RELOAD:
-                        return name_type_can_reload(n);
-
-                case JOB_RELOAD_OR_START:
-                        return name_type_can_reload(n) && name_type_can_start(n);
-
-                default:
-                        assert_not_reached("Invalid job type");
-        }
-}
-
 bool job_is_runnable(Job *j) {
-        void *state;
+        Iterator i;
         Name *other;
 
         assert(j);
@@ -323,7 +298,7 @@ bool job_is_runnable(Job *j) {
                  * dependencies, regardless whether they are
                  * starting or stopping something. */
 
-                SET_FOREACH(other, j->name->meta.dependencies[NAME_AFTER], state)
+                SET_FOREACH(other, j->name->meta.dependencies[NAME_AFTER], i)
                         if (other->meta.job)
                                 return false;
         }
@@ -331,7 +306,7 @@ bool job_is_runnable(Job *j) {
         /* Also, if something else is being stopped and we should
          * change state after it, then lets wait. */
 
-        SET_FOREACH(other, j->name->meta.dependencies[NAME_BEFORE], state)
+        SET_FOREACH(other, j->name->meta.dependencies[NAME_BEFORE], i)
                 if (other->meta.job &&
                     (other->meta.job->type == JOB_STOP ||
                      other->meta.job->type == JOB_RESTART ||
@@ -356,12 +331,17 @@ int job_run_and_invalidate(Job *j) {
         int r;
         assert(j);
 
-        if (!job_is_runnable(j))
-                return -EAGAIN;
+        if (j->in_run_queue) {
+                LIST_REMOVE(Job, run_queue, j->manager->run_queue, j);
+                j->in_run_queue = false;
+        }
 
         if (j->state != JOB_WAITING)
                 return 0;
 
+        if (!job_is_runnable(j))
+                return -EAGAIN;
+
         j->state = JOB_RUNNING;
 
         switch (j->type) {
@@ -437,16 +417,18 @@ int job_run_and_invalidate(Job *j) {
 
 int job_finish_and_invalidate(Job *j, bool success) {
         Name *n;
-        void *state;
         Name *other;
         NameType t;
+        Iterator i;
 
         assert(j);
 
+        /* Patch restart jobs so that they become normal start jobs */
         if (success && (j->type == JOB_RESTART || j->type == JOB_TRY_RESTART)) {
                 j->state = JOB_RUNNING;
                 j->type = JOB_START;
-                return job_run_and_invalidate(j);
+                job_schedule_run(j);
+                return 0;
         }
 
         n = j->name;
@@ -460,14 +442,14 @@ int job_finish_and_invalidate(Job *j, bool success) {
                     t == JOB_VERIFY_ACTIVE ||
                     t == JOB_RELOAD_OR_START) {
 
-                        SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], state)
+                        SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], i)
                                 if (other->meta.job &&
                                     (other->meta.type == JOB_START ||
                                      other->meta.type == JOB_VERIFY_ACTIVE ||
                                      other->meta.type == JOB_RELOAD_OR_START))
                                         job_finish_and_invalidate(other->meta.job, false);
 
-                        SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRED_BY], state)
+                        SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRED_BY], i)
                                 if (other->meta.job &&
                                     !other->meta.job->forced &&
                                     (other->meta.type == JOB_START ||
@@ -477,7 +459,7 @@ int job_finish_and_invalidate(Job *j, bool success) {
 
                 } else if (t == JOB_STOP) {
 
-                        SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], state)
+                        SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], i)
                                 if (other->meta.job &&
                                     (t == JOB_START ||
                                      t == JOB_VERIFY_ACTIVE ||
@@ -487,12 +469,23 @@ int job_finish_and_invalidate(Job *j, bool success) {
         }
 
         /* Try to start the next jobs that can be started */
-        SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], i)
                 if (other->meta.job)
-                        job_run_and_invalidate(other->meta.job);
-        SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], state)
+                        job_schedule_run(other->meta.job);
+        SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], i)
                 if (other->meta.job)
-                        job_run_and_invalidate(other->meta.job);
+                        job_schedule_run(other->meta.job);
 
         return 0;
 }
+
+void job_schedule_run(Job *j) {
+        assert(j);
+        assert(j->linked);
+
+        if (j->in_run_queue)
+                return;
+
+        LIST_PREPEND(Job, run_queue, j->manager->run_queue, j);
+        j->in_run_queue = true;
+}
diff --git a/job.h b/job.h
index fbfaa16..165feb6 100644 (file)
--- a/job.h
+++ b/job.h
@@ -56,9 +56,8 @@ struct JobDependency {
 
         bool matters;
 
-        /* Linked list for the subjects, resp objects */
-        JobDependency *subject_prev, *subject_next;
-        JobDependency *object_prev, *object_next;
+        LIST_FIELDS(JobDependency, subject);
+        LIST_FIELDS(JobDependency, object);
 };
 
 struct Job {
@@ -71,11 +70,12 @@ struct Job {
         JobState state;
 
         bool linked:1;
+        bool in_run_queue:1;
         bool matters_to_anchor:1;
         bool forced:1;
 
-        /* These fields are used only while building a transaction */
-        Job *transaction_next, *transaction_prev;
+        LIST_FIELDS(Job, transaction);
+        LIST_FIELDS(Job, run_queue);
 
         JobDependency *subject_list;
         JobDependency *object_list;
@@ -83,6 +83,7 @@ struct Job {
         /* Used for graph algs as a "I have been here" marker */
         Job* marker;
         unsigned generation;
+
 };
 
 Job* job_new(Manager *m, JobType type, Name *name);
@@ -102,8 +103,8 @@ int job_type_merge(JobType *a, JobType b);
 bool job_type_is_mergeable(JobType a, JobType b);
 bool job_type_is_superset(JobType a, JobType b);
 bool job_type_is_conflicting(JobType a, JobType b);
-bool job_type_is_applicable(JobType j, NameType n);
 
+void job_schedule_run(Job *j);
 int job_run_and_invalidate(Job *j);
 int job_finish_and_invalidate(Job *j, bool success);
 
diff --git a/list.h b/list.h
index c255e08..a655529 100644 (file)
--- a/list.h
+++ b/list.h
@@ -6,85 +6,95 @@
 /* The head of the linked list. Use this in the structure that shall
  * contain the head of the linked list */
 #define LIST_HEAD(t,name)                                               \
-    t *name
+        t *name
 
 /* The pointers in the linked list's items. Use this in the item structure */
-#define LIST_FIELDS(t)                                                  \
-    t *next, *prev
+#define LIST_FIELDS(t,name)                                             \
+        t *name##_next, *name##_prev
 
 /* Initialize the list's head */
-#define LIST_HEAD_INIT(t,item)                                          \
+#define LIST_HEAD_INIT(t,head)                                          \
         do {                                                            \
-                (item) = (t*) NULL; }                                   \
+                (head) = NULL; }                                        \
         while(false)
 
 /* Initialize a list item */
-#define LIST_INIT(t,item)                                               \
+#define LIST_INIT(t,name,item)                                          \
         do {                                                            \
                 t *_item = (item);                                      \
                 assert(_item);                                          \
-                _item->prev = _item->next = NULL;                       \
+                _item->name##_prev = _item->name##_next = NULL;         \
         } while(false)
 
 /* Prepend an item to the list */
-#define LIST_PREPEND(t,head,item)                                       \
+#define LIST_PREPEND(t,name,head,item)                                  \
         do {                                                            \
                 t **_head = &(head), *_item = (item);                   \
                 assert(_item);                                          \
-                if ((_item->next = *_head))                             \
-                        _item->next->prev = _item;                      \
-                _item->prev = NULL;                                     \
+                if ((_item->name##_next = *_head))                      \
+                        _item->name##_next->name##_prev = _item;        \
+                _item->name##_prev = NULL;                              \
                 *_head = _item;                                         \
         } while(false)
 
 /* Remove an item from the list */
-#define LIST_REMOVE(t,head,item)                                        \
+#define LIST_REMOVE(t,name,head,item)                                   \
         do {                                                            \
                 t **_head = &(head), *_item = (item);                   \
                 assert(_item);                                          \
-                if (_item->next)                                        \
-                        _item->next->prev = _item->prev;                \
-                if (_item->prev)                                        \
-                        _item->prev->next = _item->next;                \
+                if (_item->name##_next)                                 \
+                        _item->name##_next->name##_prev = _item->name##_prev; \
+                if (_item->name##_prev)                                 \
+                        _item->name##_prev->name##_next = _item->name##_next; \
                 else {                                                  \
                         assert(*_head == _item);                        \
-                        *_head = _item->next;                           \
+                        *_head = _item->name##_next;                    \
                 }                                                       \
-                _item->next = _item->prev = NULL;                       \
+                _item->name##_next = _item->name##_prev = NULL;         \
         } while(false)
 
 /* Find the head of the list */
-#define LIST_FIND_HEAD(t,item,head)                                     \
+#define LIST_FIND_HEAD(t,name,item,head)                                \
         do {                                                            \
-                t **_head = (head), *_item = (item);                    \
-                *_head = _item;                                         \
-                assert(_head);                                          \
-                while ((*_head)->prev)                                  \
-                        *_head = (*_head)->prev;                        \
+                t *_item = (item);                                      \
+                assert(_item);                                          \
+                while ((_item->name##_prev)                             \
+                       _item = _item->name##_prev;                      \
+                (head) = _item;                                         \
+        } while (false)
+
+/* Find the head of the list */
+#define LIST_FIND_TAIL(t,name,item,tail)                                \
+        do {                                                            \
+                t *_item = (item);                                      \
+                assert(_item);                                          \
+                while (_item->name##_next)                              \
+                        _item = _item->name##_next;                     \
+                (tail) = _item;                                         \
         } while (false)
 
 /* Insert an item after another one (a = where, b = what) */
-#define LIST_INSERT_AFTER(t,head,a,b)                                   \
+#define LIST_INSERT_AFTER(t,name,head,a,b)                              \
         do {                                                            \
                 t **_head = &(head), *_a = (a), *_b = (b);              \
                 assert(_b);                                             \
                 if (!_a) {                                              \
-                        if ((_b->next = *_head))                        \
-                                _b->next->prev = _b;                    \
-                        _b->prev = NULL;                                \
+                        if ((_b->name##_next = *_head))                 \
+                                _b->name##_next->name##_prev = _b;      \
+                        _b->name##_prev = NULL;                         \
                         *_head = _b;                                    \
                 } else {                                                \
-                        if ((_b->next = _a->next))                      \
-                                _b->next->prev = _b;                    \
-                        _b->prev = _a;                                  \
-                        _a->next = _b;                                  \
+                        if ((_b->name##_next = _a->name##_next))        \
+                                _b->name##_next->name##_prev = _b;      \
+                        _b->name##_prev = _a;                           \
+                        _a->name##_next = _b;                           \
                 }                                                       \
         } while(false)
 
-#define LIST_FOREACH(i,head)                                            \
-        for (i = (head); i; i = i->next)
+#define LIST_FOREACH(name,i,head)                                       \
+        for ((i) = (head); (i); (i) = (i)->name##_next)
 
-#define LIST_FOREACH_SAFE(i,n,head)                                     \
-        for (i = (head); i && ((n = i->next), 1); i = n)
+#define LIST_FOREACH_SAFE(name,i,n,head)                                \
+        for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n))
 
 #endif
index e85c1d4..5570ae5 100644 (file)
@@ -3,6 +3,7 @@
 #include <assert.h>
 #include <errno.h>
 #include <string.h>
+#include <linux/oom.h>
 
 #include "name.h"
 #include "strv.h"
@@ -44,9 +45,8 @@ static int config_parse_deps(
                 if (r < 0)
                         return r;
 
-                if (!*set)
-                        if (!(*set = set_new(trivial_hash_func, trivial_compare_func)))
-                                return -ENOMEM;
+                if ((r = set_ensure_allocated(set, trivial_hash_func, trivial_compare_func)) < 0)
+                        return r;
 
                 if ((r = set_put(*set, other)) < 0)
                         return r;
@@ -177,12 +177,12 @@ static int config_parse_listen(
         }
 
         p->fd = -1;
-        LIST_PREPEND(SocketPort, s->ports, p);
+        LIST_PREPEND(SocketPort, port, s->ports, p);
 
         return 0;
 }
 
-static int config_parse_bind(
+static int config_parse_socket_bind(
                 const char *filename,
                 unsigned line,
                 const char *section,
@@ -211,6 +211,256 @@ static int config_parse_bind(
         return 0;
 }
 
+static int config_parse_nice(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        int *i = data, priority, r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((r = safe_atoi(rvalue, &priority)) < 0) {
+                log_error("[%s:%u] Failed to parse nice priority: %s", filename, line, rvalue);
+                return r;
+        }
+
+        if (priority < PRIO_MIN || priority >= PRIO_MAX) {
+                log_error("[%s:%u] Nice priority out of range: %s", filename, line, rvalue);
+                return -ERANGE;
+        }
+
+        *i = priority;
+        return 0;
+}
+
+static int config_parse_oom_adjust(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        int *i = data, oa, r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((r = safe_atoi(rvalue, &oa)) < 0) {
+                log_error("[%s:%u] Failed to parse OOM adjust value: %s", filename, line, rvalue);
+                return r;
+        }
+
+        if (oa < OOM_DISABLE || oa > OOM_ADJUST_MAX) {
+                log_error("[%s:%u] OOM adjust value out of range: %s", filename, line, rvalue);
+                return -ERANGE;
+        }
+
+        *i = oa;
+        return 0;
+}
+
+static int config_parse_umask(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        mode_t *m = data;
+        long l;
+        char *x = NULL;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        errno = 0;
+        l = strtol(rvalue, &x, 8);
+        if (!x || *x || errno) {
+                log_error("[%s:%u] Failed to parse umask value: %s", filename, line, rvalue);
+                return errno ? -errno : -EINVAL;
+        }
+
+        if (l < 0000 || l > 0777) {
+                log_error("[%s:%u] umask value out of range: %s", filename, line, rvalue);
+                return -ERANGE;
+        }
+
+        *m = (mode_t) l;
+        return 0;
+}
+
+static int config_parse_exec(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        ExecCommand **e = data, *ee, *nce = NULL;
+        char **n;
+        char *w;
+        unsigned k;
+        size_t l;
+        char *state;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        k = 0;
+        FOREACH_WORD_QUOTED(w, l, rvalue, state)
+                k++;
+
+        if (!(n = new(char*, k+1)))
+                return -ENOMEM;
+
+        FOREACH_WORD_QUOTED(w, l, rvalue, state)
+                if (!(n[k++] = strndup(w, l)))
+                        goto fail;
+
+        n[k] = NULL;
+
+        if (!n[0] || n[0][0] != '/') {
+                log_error("[%s:%u] Invalid executable path in command line: %s", filename, line, rvalue);
+                strv_free(n);
+                return -EINVAL;
+        }
+
+        if (!(nce = new0(ExecCommand, 1)))
+                goto fail;
+
+        nce->argv = n;
+        if (!(nce->path = strdup(n[0])))
+                goto fail;
+
+        if (*e) {
+                /* It's kinda important that we keep the order here */
+                LIST_FIND_TAIL(ExecCommand, command, *e, ee);
+                LIST_INSERT_AFTER(ExecCommand, command, *e, ee, nce);
+        } else
+                *e = nce;
+
+        return 0;
+
+fail:
+        for (; k > 0; k--)
+                free(n[k-1]);
+        free(n);
+
+        free(nce);
+
+        return -ENOMEM;
+}
+
+static int config_parse_usec(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        usec_t *usec = data;
+        unsigned long long u;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((r = safe_atollu(rvalue, &u)) < 0) {
+                log_error("[%s:%u] Failed to parse time value: %s", filename, line, rvalue);
+                return r;
+        }
+
+        /* We actually assume the user configures seconds. Later on we
+         * might choose to support suffixes for time values, to
+         * configure bigger or smaller units */
+
+        *usec = u * USEC_PER_SEC;
+
+        return 0;
+}
+
+static int config_parse_service_type(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Service *s = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (streq(rvalue, "forking"))
+                s->type = SERVICE_FORKING;
+        else if (streq(rvalue, "simple"))
+                s->type = SERVICE_SIMPLE;
+        else {
+                log_error("[%s:%u] Failed to parse service type: %s", filename, line, rvalue);
+                return -EBADMSG;
+        }
+
+        return 0;
+}
+
+static int config_parse_service_restart(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Service *s = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (streq(rvalue, "once"))
+                s->restart = SERVICE_ONCE;
+        else if (streq(rvalue, "on-success"))
+                s->type = SERVICE_RESTART_ON_SUCCESS;
+        else if (streq(rvalue, "always"))
+                s->type = SERVICE_RESTART_ALWAYS;
+        else {
+                log_error("[%s:%u] Failed to parse service type: %s", filename, line, rvalue);
+                return -EBADMSG;
+        }
+
+        return 0;
+}
+
 int name_load_fragment(Name *n) {
 
         static const char* const section_table[_NAME_TYPE_MAX] = {
@@ -224,32 +474,64 @@ int name_load_fragment(Name *n) {
                 [NAME_SNAPSHOT]  = "Snapshot"
         };
 
+#define EXEC_CONTEXT_CONFIG_ITEMS(context, section) \
+                { "Directory",              config_parse_path,            &(context).directory,                              section   }, \
+                { "User",                   config_parse_string,          &(context).user,                                   section   }, \
+                { "Group",                  config_parse_string,          &(context).group,                                  section   }, \
+                { "SupplementaryGroups",    config_parse_strv,            &(context).supplementary_groups,                   section   }, \
+                { "Nice",                   config_parse_nice,            &(context).nice,                                   section   }, \
+                { "OOMAdjust",              config_parse_oom_adjust,      &(context).oom_adjust,                             section   }, \
+                { "UMask",                  config_parse_umask,           &(context).umask,                                  section   }, \
+                { "Environment",            config_parse_strv,            &(context).environment,                            section   }
+
         const ConfigItem items[] = {
-                { "Names",                  config_parse_names,    &n->meta.names,                           "Meta"   },
-                { "Description",            config_parse_string,   &n->meta.description,                     "Meta"   },
-                { "Requires",               config_parse_deps,     n->meta.dependencies+NAME_REQUIRES,       "Meta"   },
-                { "SoftRequires",           config_parse_deps,     n->meta.dependencies+NAME_SOFT_REQUIRES,  "Meta"   },
-                { "Wants",                  config_parse_deps,     n->meta.dependencies+NAME_WANTS,          "Meta"   },
-                { "Requisite",              config_parse_deps,     n->meta.dependencies+NAME_REQUISITE,      "Meta"   },
-                { "SoftRequisite",          config_parse_deps,     n->meta.dependencies+NAME_SOFT_REQUISITE, "Meta"   },
-                { "Conflicts",              config_parse_deps,     n->meta.dependencies+NAME_CONFLICTS,      "Meta"   },
-                { "Before",                 config_parse_deps,     n->meta.dependencies+NAME_BEFORE,         "Meta"   },
-                { "After",                  config_parse_deps,     n->meta.dependencies+NAME_AFTER,          "Meta"   },
-                { "ListenStream",           config_parse_listen,   &n->socket,                               "Socket" },
-                { "ListenDatagram",         config_parse_listen,   &n->socket,                               "Socket" },
-                { "ListenSequentialPacket", config_parse_listen,   &n->socket,                               "Socket" },
-                { "ListenFIFO",             config_parse_listen,   &n->socket,                               "Socket" },
-                { "BindIPv6Only",           config_parse_bind,     &n->socket,                               "Socket" },
-                { "Backlog",                config_parse_unsigned, &n->socket.backlog,                       "Socket" },
+                { "Names",                  config_parse_names,           &n->meta.names,                                    "Meta"    },
+                { "Description",            config_parse_string,          &n->meta.description,                              "Meta"    },
+                { "Requires",               config_parse_deps,            n->meta.dependencies+NAME_REQUIRES,                "Meta"    },
+                { "SoftRequires",           config_parse_deps,            n->meta.dependencies+NAME_SOFT_REQUIRES,           "Meta"    },
+                { "Wants",                  config_parse_deps,            n->meta.dependencies+NAME_WANTS,                   "Meta"    },
+                { "Requisite",              config_parse_deps,            n->meta.dependencies+NAME_REQUISITE,               "Meta"    },
+                { "SoftRequisite",          config_parse_deps,            n->meta.dependencies+NAME_SOFT_REQUISITE,          "Meta"    },
+                { "Conflicts",              config_parse_deps,            n->meta.dependencies+NAME_CONFLICTS,               "Meta"    },
+                { "Before",                 config_parse_deps,            n->meta.dependencies+NAME_BEFORE,                  "Meta"    },
+                { "After",                  config_parse_deps,            n->meta.dependencies+NAME_AFTER,                   "Meta"    },
+
+                { "PIDFile",                config_parse_path,            &n->service.pid_file,                              "Service" },
+                { "ExecStartPre",           config_parse_exec,            &n->service.exec_command[SERVICE_EXEC_START_PRE],  "Service" },
+                { "ExecStart",              config_parse_exec,            &n->service.exec_command[SERVICE_EXEC_START],      "Service" },
+                { "ExecStartPost",          config_parse_exec,            &n->service.exec_command[SERVICE_EXEC_START_POST], "Service" },
+                { "ExecReload",             config_parse_exec,            &n->service.exec_command[SERVICE_EXEC_RELOAD],     "Service" },
+                { "ExecStop",               config_parse_exec,            &n->service.exec_command[SERVICE_EXEC_STOP],       "Service" },
+                { "ExecStopPost",           config_parse_exec,            &n->service.exec_command[SERVICE_EXEC_STOP_POST],  "Service" },
+                { "RestartSec",             config_parse_usec,            &n->service.restart_usec,                          "Service" },
+                { "TimeoutSec",             config_parse_usec,            &n->service.timeout_usec,                          "Service" },
+                { "Type",                   config_parse_service_type,    &n->service,                                       "Service" },
+                { "Restart",                config_parse_service_restart, &n->service,                                       "Service" },
+                EXEC_CONTEXT_CONFIG_ITEMS(n->service.exec_context, "Service"),
+
+                { "ListenStream",           config_parse_listen,          &n->socket,                                        "Socket"  },
+                { "ListenDatagram",         config_parse_listen,          &n->socket,                                        "Socket"  },
+                { "ListenSequentialPacket", config_parse_listen,          &n->socket,                                        "Socket"  },
+                { "ListenFIFO",             config_parse_listen,          &n->socket,                                        "Socket"  },
+                { "BindIPv6Only",           config_parse_socket_bind,     &n->socket,                                        "Socket"  },
+                { "Backlog",                config_parse_unsigned,        &n->socket.backlog,                                "Socket"  },
+                { "ExecStartPre",           config_parse_exec,            &n->service.exec_command[SOCKET_EXEC_START_PRE],   "Socket"  },
+                { "ExecStartPost",          config_parse_exec,            &n->service.exec_command[SOCKET_EXEC_START_POST],  "Socket"  },
+                { "ExecStopPre",            config_parse_exec,            &n->service.exec_command[SOCKET_EXEC_STOP_PRE],    "Socket"  },
+                { "ExecStopPost",           config_parse_exec,            &n->service.exec_command[SOCKET_EXEC_STOP_POST],   "Socket"  },
+                EXEC_CONTEXT_CONFIG_ITEMS(n->socket.exec_context, "Socket"),
+
+                EXEC_CONTEXT_CONFIG_ITEMS(n->automount.exec_context, "Automount"),
+
                 { NULL, NULL, NULL, NULL }
         };
 
-        const
+#undef EXEC_CONTEXT_CONFIG_ITEMS
 
         char *t;
         int r;
-        void *state;
         const char *sections[3];
+        Iterator i;
 
         assert(n);
         assert(n->meta.load_state == NAME_STUB);
@@ -258,12 +540,18 @@ int name_load_fragment(Name *n) {
         sections[1] = section_table[n->meta.type];
         sections[2] = NULL;
 
-        SET_FOREACH(t, n->meta.names, state)
-                if ((r = config_parse(t, sections, items, n)) < 0)
-                        goto fail;
+        SET_FOREACH(t, n->meta.names, i) {
 
-        r = 0;
+                /* Try to find a name we can load this with */
+                if ((r = config_parse(t, sections, items, n)) == -ENOENT)
+                        continue;
 
-fail:
-        return r;
+                /* Yay, we succeeded! Now let's call this our identifier */
+                if (r == 0)
+                        n->meta.id = t;
+
+                return r;
+        }
+
+        return -ENOENT;
 }
diff --git a/macro.h b/macro.h
index ed09750..0223a40 100644 (file)
--- a/macro.h
+++ b/macro.h
@@ -76,4 +76,6 @@ static inline size_t ALIGN(size_t l) {
 #define memzero(x,l) (memset((x), 0, (l)))
 #define zero(x) (memzero(&(x), sizeof(x)))
 
+#define char_array_0(x) x[sizeof(x)-1] = 0;
+
 #endif
diff --git a/main.c b/main.c
index 47292bb..a7738f0 100644 (file)
--- a/main.c
+++ b/main.c
@@ -42,8 +42,6 @@ int main(int argc, char *argv[]) {
         printf("- By jobs:\n");
         manager_dump_jobs(m, stdout, "\t");
 
-        manager_run_jobs(m);
-
         manager_loop(m);
 
         retval = 0;
index 0cc4d26..7d24494 100644 (file)
--- a/manager.c
+++ b/manager.c
@@ -217,8 +217,8 @@ static int delete_one_unmergeable_job(Manager *m, Job *j) {
 
         /* We rely here on the fact that if a merged with b does not
          * merge with c, either a or b merge with c neither */
-        for (; j; j = j->transaction_next)
-                for (k = j->transaction_next; k; k = k->transaction_next) {
+        LIST_FOREACH(transaction, j, j)
+                LIST_FOREACH(transaction, k, j->transaction_next) {
                         Job *d;
 
                         /* Is this one mergeable? Then skip it */
@@ -245,19 +245,19 @@ static int delete_one_unmergeable_job(Manager *m, Job *j) {
 
 static int transaction_merge_jobs(Manager *m) {
         Job *j;
-        void *state;
+        Iterator i;
         int r;
 
         assert(m);
 
         /* First step, check whether any of the jobs for one specific
          * task conflict. If so, try to drop one of them. */
-        HASHMAP_FOREACH(j, m->transaction_jobs, state) {
+        HASHMAP_FOREACH(j, m->transaction_jobs, i) {
                 JobType t;
                 Job *k;
 
                 t = j->type;
-                for (k = j->transaction_next; k; k = k->transaction_next) {
+                LIST_FOREACH(transaction, k, j->transaction_next) {
                         if ((r = job_type_merge(&t, k->type)) >= 0)
                                 continue;
 
@@ -277,12 +277,12 @@ static int transaction_merge_jobs(Manager *m) {
         }
 
         /* Second step, merge the jobs. */
-        HASHMAP_FOREACH(j, m->transaction_jobs, state) {
+        HASHMAP_FOREACH(j, m->transaction_jobs, i) {
                 JobType t = j->type;
                 Job *k;
 
                 /* Merge all transactions */
-                for (k = j->transaction_next; k; k = k->transaction_next)
+                LIST_FOREACH(transaction, k, j->transaction_next)
                         assert_se(job_type_merge(&t, k->type) == 0);
 
                 /* If an active job is mergeable, merge it too */
@@ -311,7 +311,7 @@ static bool name_matters_to_anchor(Name *n, Job *j) {
         /* Checks whether at least one of the jobs for this name
          * matters to the anchor. */
 
-        for (; j; j = j->transaction_next)
+        LIST_FOREACH(transaction, j, j)
                 if (j->matters_to_anchor)
                         return true;
 
@@ -319,7 +319,7 @@ static bool name_matters_to_anchor(Name *n, Job *j) {
 }
 
 static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned generation) {
-        void *state;
+        Iterator i;
         Name *n;
         int r;
 
@@ -368,7 +368,7 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned
 
         /* We assume that the the dependencies are bidirectional, and
          * hence can ignore NAME_AFTER */
-        SET_FOREACH(n, j->name->meta.dependencies[NAME_BEFORE], state) {
+        SET_FOREACH(n, j->name->meta.dependencies[NAME_BEFORE], i) {
                 Job *o;
 
                 /* Is there a job for this name? */
@@ -390,7 +390,7 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned
 static int transaction_verify_order(Manager *m, unsigned *generation) {
         Job *j;
         int r;
-        void *state;
+        Iterator i;
 
         assert(m);
         assert(generation);
@@ -398,7 +398,7 @@ static int transaction_verify_order(Manager *m, unsigned *generation) {
         /* Check if the ordering graph is cyclic. If it is, try to fix
          * that up by dropping one of the jobs. */
 
-        HASHMAP_FOREACH(j, m->transaction_jobs, state)
+        HASHMAP_FOREACH(j, m->transaction_jobs, i)
                 if ((r = transaction_verify_order_one(m, j, NULL, (*generation)++)) < 0)
                         return r;
 
@@ -413,12 +413,12 @@ static void transaction_collect_garbage(Manager *m) {
         /* Drop jobs that are not required by any other job */
 
         do {
-                void *state;
+                Iterator i;
                 Job *j;
 
                 again = false;
 
-                HASHMAP_FOREACH(j, m->transaction_jobs, state) {
+                HASHMAP_FOREACH(j, m->transaction_jobs, i) {
                         if (j->object_list)
                                 continue;
 
@@ -432,7 +432,7 @@ static void transaction_collect_garbage(Manager *m) {
 }
 
 static int transaction_is_destructive(Manager *m, JobMode mode) {
-        void *state;
+        Iterator i;
         Job *j;
 
         assert(m);
@@ -440,7 +440,7 @@ static int transaction_is_destructive(Manager *m, JobMode mode) {
         /* Checks whether applying this transaction means that
          * existing jobs would be replaced */
 
-        HASHMAP_FOREACH(j, m->transaction_jobs, state) {
+        HASHMAP_FOREACH(j, m->transaction_jobs, i) {
 
                 /* Assume merged */
                 assert(!j->transaction_prev);
@@ -464,12 +464,12 @@ static void transaction_minimize_impact(Manager *m) {
 
         do {
                 Job *j;
-                void *state;
+                Iterator i;
 
                 again = false;
 
-                HASHMAP_FOREACH(j, m->transaction_jobs, state) {
-                        for (; j; j = j->transaction_next) {
+                HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+                        LIST_FOREACH(transaction, j, j) {
 
                                 /* If it matters, we shouldn't drop it */
                                 if (j->matters_to_anchor)
@@ -497,13 +497,13 @@ static void transaction_minimize_impact(Manager *m) {
 }
 
 static int transaction_apply(Manager *m, JobMode mode) {
-        void *state;
+        Iterator i;
         Job *j;
         int r;
 
         /* Moves the transaction jobs to the set of active jobs */
 
-        HASHMAP_FOREACH(j, m->transaction_jobs, state) {
+        HASHMAP_FOREACH(j, m->transaction_jobs, i) {
                 /* Assume merged */
                 assert(!j->transaction_prev);
                 assert(!j->transaction_next);
@@ -543,7 +543,7 @@ static int transaction_apply(Manager *m, JobMode mode) {
 
 rollback:
 
-        HASHMAP_FOREACH(j, m->transaction_jobs, state) {
+        HASHMAP_FOREACH(j, m->transaction_jobs, i) {
                 if (j->linked)
                         continue;
 
@@ -637,7 +637,7 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool f
 
         f = hashmap_get(m->transaction_jobs, name);
 
-        for (j = f; j; j = j->transaction_next) {
+        LIST_FOREACH(transaction, j, f) {
                 assert(j->name == name);
 
                 if (j->type == type) {
@@ -652,21 +652,18 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool f
         else if (!(j = job_new(m, type, name)))
                 return NULL;
 
-        if ((r = hashmap_replace(m->transaction_jobs, name, j)) < 0) {
-                job_free(j);
-                return NULL;
-        }
-
-        j->transaction_next = f;
-
-        if (f)
-                f->transaction_prev = j;
-
         j->generation = 0;
         j->marker = NULL;
         j->matters_to_anchor = false;
         j->forced = force;
 
+        LIST_PREPEND(Job, transaction, f, j);
+
+        if ((r = hashmap_replace(m->transaction_jobs, name, f)) < 0) {
+                job_free(j);
+                return NULL;
+        }
+
         if (is_new)
                 *is_new = true;
 
@@ -708,7 +705,7 @@ void manager_transaction_unlink_job(Manager *m, Job *j) {
 
 static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *name, Job *by, bool matters, bool force, Job **_ret) {
         Job *ret;
-        void *state;
+        Iterator i;
         Name *dep;
         int r;
         bool is_new;
@@ -720,7 +717,7 @@ static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *
         if (name->meta.load_state != NAME_LOADED)
                 return -EINVAL;
 
-        if (!job_type_is_applicable(type, name->meta.type))
+        if (!name_job_is_applicable(name, type))
                 return -EBADR;
 
         /* First add the job. */
@@ -734,28 +731,28 @@ static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *
         if (is_new) {
                 /* Finally, recursively add in all dependencies. */
                 if (type == JOB_START || type == JOB_RELOAD_OR_START) {
-                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRES], state)
+                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRES], i)
                                 if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
                                         goto fail;
-                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUIRES], state)
+                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUIRES], i)
                                 if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !force, force, NULL)) < 0 && r != -EBADR)
                                         goto fail;
-                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_WANTS], state)
+                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_WANTS], i)
                                 if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, force, NULL)) < 0 && r != -EBADR)
                                         goto fail;
-                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUISITE], state)
+                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUISITE], i)
                                 if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
                                         goto fail;
-                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUISITE], state)
+                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUISITE], i)
                                 if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !force, force, NULL)) < 0 && r != -EBADR)
                                         goto fail;
-                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_CONFLICTS], state)
+                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_CONFLICTS], i)
                                 if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
                                         goto fail;
 
                 } else if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
 
-                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRED_BY], state)
+                        SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRED_BY], i)
                                 if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
                                         goto fail;
                 }
@@ -805,14 +802,14 @@ Name *manager_get_name(Manager *m, const char *name) {
         return hashmap_get(m->names, name);
 }
 
-static int dispatch_load_queue(Manager *m) {
+static void dispatch_load_queue(Manager *m) {
         Meta *meta;
 
         assert(m);
 
         /* Make sure we are not run recursively */
         if (m->dispatching_load_queue)
-                return 0;
+                return;
 
         m->dispatching_load_queue = true;
 
@@ -820,50 +817,36 @@ static int dispatch_load_queue(Manager *m) {
          * tries to load its data until the queue is empty */
 
         while ((meta = m->load_queue)) {
+                assert(meta->linked);
+                assert(meta->in_load_queue);
+
                 name_load(NAME(meta));
-                LIST_REMOVE(Meta, m->load_queue, meta);
         }
 
         m->dispatching_load_queue = false;
-
-        return 0;
 }
 
 int manager_load_name(Manager *m, const char *name, Name **_ret) {
         Name *ret;
-        NameType t;
         int r;
-        char *n;
 
         assert(m);
         assert(name);
         assert(_ret);
 
-        if (!name_is_valid(name))
-                return -EINVAL;
-
         /* This will load the service information files, but not actually
          * start any services or anything */
 
-        if ((ret = manager_get_name(m, name)))
-                goto finish;
-
-        if ((t = name_type_from_string(name)) == _NAME_TYPE_INVALID)
-                return -EINVAL;
+        if ((ret = manager_get_name(m, name))) {
+                *_ret = ret;
+                return 0;
+        }
 
         if (!(ret = name_new(m)))
                 return -ENOMEM;
 
-        ret->meta.type = t;
-
-        if (!(n = strdup(name))) {
+        if ((r = name_add_name(ret, name)) < 0) {
                 name_free(ret);
-                return -ENOMEM;
-        }
-
-        if ((r = set_put(ret->meta.names, n)) < 0) {
-                name_free(ret);
-                free(n);
                 return r;
         }
 
@@ -872,38 +855,36 @@ int manager_load_name(Manager *m, const char *name, Name **_ret) {
                 return r;
         }
 
-        /* At this point the new entry is created and linked. However,
+        /* At this point the new entry is created and linked. However
          * not loaded. Now load this entry and all its dependencies
          * recursively */
 
         dispatch_load_queue(m);
 
-finish:
-
         *_ret = ret;
         return 0;
 }
 
 void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) {
-        void *state;
+        Iterator i;
         Job *j;
 
         assert(s);
         assert(f);
 
-        HASHMAP_FOREACH(j, s->jobs, state)
+        HASHMAP_FOREACH(j, s->jobs, i)
                 job_dump(j, f, prefix);
 }
 
 void manager_dump_names(Manager *s, FILE *f, const char *prefix) {
-        void *state;
+        Iterator i;
         Name *n;
         const char *t;
 
         assert(s);
         assert(f);
 
-        HASHMAP_FOREACH_KEY(n, t, s->names, state)
+        HASHMAP_FOREACH_KEY(n, t, s->names, i)
                 if (name_id(n) == t)
                         name_dump(n, f, prefix);
 }
@@ -919,19 +900,25 @@ void manager_clear_jobs(Manager *m) {
                 job_free(j);
 }
 
-void manager_run_jobs(Manager *m) {
+void manager_dispatch_run_queue(Manager *m) {
         Job *j;
-        void *state;
-        int r;
 
-        HASHMAP_FOREACH(j, m->jobs, state) {
-                r = job_run_and_invalidate(j);
+        if (m->dispatching_run_queue)
+                return;
+
+        m->dispatching_run_queue = true;
 
-                /* FIXME... the list of jobs might have changed */
+        while ((j = m->run_queue)) {
+                assert(j->linked);
+                assert(j->in_run_queue);
+
+                job_run_and_invalidate(j);
         }
+
+        m->dispatching_run_queue = false;
 }
 
-int manager_dispatch_sigchld(Manager *m) {
+static int manager_dispatch_sigchld(Manager *m) {
         assert(m);
 
         for (;;) {
@@ -945,6 +932,9 @@ int manager_dispatch_sigchld(Manager *m) {
                 if (si.si_pid == 0)
                         break;
 
+                if (si.si_code != CLD_EXITED && si.si_code != CLD_KILLED && si.si_code != CLD_DUMPED)
+                        continue;
+
                 if (!(n = hashmap_remove(m->watch_pids, UINT32_TO_PTR(si.si_pid))))
                         continue;
 
@@ -954,7 +944,7 @@ int manager_dispatch_sigchld(Manager *m) {
         return 0;
 }
 
-int manager_process_signal_fd(Manager *m) {
+static int manager_process_signal_fd(Manager *m) {
         ssize_t n;
         struct signalfd_siginfo sfsi;
         bool sigchld = false;
@@ -978,20 +968,77 @@ int manager_process_signal_fd(Manager *m) {
         }
 
         if (sigchld)
-                manager_dispatch_sigchld(m);
+                return manager_dispatch_sigchld(m);
+
+        return 0;
+}
+
+static int process_event(Manager *m, struct epoll_event *ev) {
+        int r;
+
+        assert(m);
+        assert(ev);
+
+        switch (ev->data.u32) {
+
+                case MANAGER_SIGNAL:
+                        assert(ev->data.fd == m->signal_fd);
+
+                        /* An incoming signal? */
+                        if (ev->events != POLLIN)
+                                return -EINVAL;
+
+                        if ((r = manager_process_signal_fd(m)) < 0)
+                                return -r;
+
+                        break;
+
+                case MANAGER_FD: {
+                        Name *n;
+
+                        /* Some fd event, to be dispatched to the names */
+                        assert_se(n = ev->data.ptr);
+                        NAME_VTABLE(n)->fd_event(n, ev->data.fd, ev->events);
+                        break;
+                }
+
+                case MANAGER_TIMER: {
+                        Name *n;
+                        uint64_t u;
+                        ssize_t k;
+
+                        /* Some timer event, to be dispatched to the names */
+                        if ((k = read(ev->data.fd, &u, sizeof(u))) != sizeof(u)) {
+
+                                if (k < 0 && (errno == EINTR || errno == EAGAIN))
+                                        break;
+
+                                return k < 0 ? -errno : -EIO;
+                        }
+
+                        assert_se(n = ev->data.ptr);
+                        NAME_VTABLE(n)->timer_event(n, ev->data.fd, u);
+                        break;
+                }
+
+                default:
+                        assert_not_reached("Unknown epoll event type.");
+        }
 
         return 0;
 }
 
 int manager_loop(Manager *m) {
         int r;
-        struct epoll_event events[32];
 
         assert(m);
 
         for (;;) {
+                struct epoll_event events[32];
                 int n, i;
 
+                manager_dispatch_run_queue(m);
+
                 if ((n = epoll_wait(m->epoll_fd, events, ELEMENTSOF(events), -1)) < 0) {
 
                         if (errno == -EINTR)
@@ -1000,23 +1047,8 @@ int manager_loop(Manager *m) {
                         return -errno;
                 }
 
-                for (i = 0; i < n; i++) {
-
-                        if (events[i].data.fd == m->signal_fd) {
-
-                                /* An incoming signal? */
-                                if (events[i].events != POLLIN)
-                                        return -EINVAL;
-
-                                if ((r = manager_process_signal_fd(m)) < 0)
-                                        return -r;
-                        } else {
-                                Name *n;
-
-                                /* Some other fd event, to be dispatched to the names */
-                                assert_se(n = events[i].data.ptr);
-                                NAME_VTABLE(n)->fd_event(n, events[i].data.fd, events[i].events);
-                        }
-                }
+                for (i = 0; i < n; i++)
+                        if ((r = process_event(m, events + i)) < 0)
+                                return r;
         }
 }
index 6e0b500..ddc9610 100644 (file)
--- a/manager.h
+++ b/manager.h
@@ -8,6 +8,7 @@
 #include <stdio.h>
 
 typedef struct Manager Manager;
+typedef enum ManagerEventType ManagerEventType;
 
 #include "name.h"
 #include "job.h"
@@ -15,6 +16,12 @@ typedef struct Manager Manager;
 #include "list.h"
 #include "set.h"
 
+enum ManagerEventType {
+        MANAGER_SIGNAL,
+        MANAGER_FD,
+        MANAGER_TIMER
+};
+
 struct Manager {
         uint32_t current_job_id;
 
@@ -29,11 +36,15 @@ struct Manager {
         /* Names that need to be loaded */
         LIST_HEAD(Meta, load_queue); /* this is actually more a stack than a queue, but uh. */
 
+        /* Jobs that need to be run */
+        LIST_HEAD(Job, run_queue);   /* more a stack than a queue, too */
+
         /* Jobs to be added */
         Hashmap *transaction_jobs;      /* Name object => Job object list 1:1 */
         JobDependency *transaction_anchor;
 
         bool dispatching_load_queue:1;
+        bool dispatching_run_queue:1;
 
         Hashmap *watch_pids;  /* pid => Name object n:1 */
 
@@ -57,7 +68,7 @@ void manager_transaction_unlink_job(Manager *m, Job *j);
 
 void manager_clear_jobs(Manager *m);
 
-void manager_run_jobs(Manager *m);
+void manager_dispatch_run_queue(Manager *m);
 int manager_loop(Manager *m);
 
 #endif
index ad8080a..fd4a689 100644 (file)
@@ -4,11 +4,7 @@
 #include "milestone.h"
 #include "load-fragment.h"
 
-static NameActiveState milestone_active_state(Name *n) {
-        return MILESTONE(n)->state == MILESTONE_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
-}
-
-static void milestone_free_hook(Name *n) {
+static void milestone_done(Name *n) {
         Milestone *m = MILESTONE(n);
 
         assert(m);
@@ -16,17 +12,15 @@ static void milestone_free_hook(Name *n) {
         /* Nothing here for now */
 }
 
+static NameActiveState milestone_active_state(Name *n) {
+        return MILESTONE(n)->state == MILESTONE_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
+}
+
 const NameVTable milestone_vtable = {
         .suffix = ".milestone",
 
-        .load = name_load_fragment,
-        .dump = NULL,
-
-        .start = NULL,
-        .stop = NULL,
-        .reload = NULL,
-
-        .active_state = milestone_active_state,
+        .init = name_load_fragment,
+        .done = milestone_done,
 
-        .free_hook = milestone_free_hook
+        .active_state = milestone_active_state
 };
diff --git a/mount.c b/mount.c
index fa92e5b..21d3477 100644 (file)
--- a/mount.c
+++ b/mount.c
@@ -8,7 +8,7 @@
 #include "load-fstab.h"
 #include "load-dropin.h"
 
-static int mount_load(Name *n) {
+static int mount_init(Name *n) {
         int r;
         Mount *m = MOUNT(n);
 
@@ -29,6 +29,13 @@ static int mount_load(Name *n) {
         return r;
 }
 
+static void mount_done(Name *n) {
+        Mount *d = MOUNT(n);
+
+        assert(d);
+        free(d->path);
+}
+
 static void mount_dump(Name *n, FILE *f, const char *prefix) {
 
         static const char* const state_table[_MOUNT_STATE_MAX] = {
@@ -63,24 +70,13 @@ static NameActiveState mount_active_state(Name *n) {
         return table[MOUNT(n)->state];
 }
 
-static void mount_free_hook(Name *n) {
-        Mount *d = MOUNT(n);
-
-        assert(d);
-        free(d->path);
-}
-
 const NameVTable mount_vtable = {
         .suffix = ".mount",
 
-        .load = mount_load,
-        .dump = mount_dump,
+        .init = mount_init,
+        .done = mount_done,
 
-        .start = NULL,
-        .stop = NULL,
-        .reload = NULL,
+        .dump = mount_dump,
 
         .active_state = mount_active_state,
-
-        .free_hook = mount_free_hook
 };
diff --git a/name.c b/name.c
index e922428..dae2a3f 100644 (file)
--- a/name.c
+++ b/name.c
@@ -4,6 +4,8 @@
 #include <errno.h>
 #include <string.h>
 #include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <sys/poll.h>
 
 #include "set.h"
 #include "name.h"
@@ -77,7 +79,6 @@ Name *name_new(Manager *m) {
                 return NULL;
         }
 
-        /* Not much initialization happening here at this time */
         n->meta.manager = m;
         n->meta.type = _NAME_TYPE_INVALID;
 
@@ -86,10 +87,40 @@ Name *name_new(Manager *m) {
         return n;
 }
 
+int name_add_name(Name *n, const char *text) {
+        NameType t;
+        char *s;
+        int r;
+
+        assert(n);
+        assert(text);
+
+        if ((t = name_type_from_string(text)) == _NAME_TYPE_INVALID)
+                return -EINVAL;
+
+        if (n->meta.type != _NAME_TYPE_INVALID && t != n->meta.type)
+                return -EINVAL;
+
+        if (!(s = strdup(text)))
+                return -ENOMEM;
+
+        if ((r = set_put(n->meta.names, s)) < 0) {
+                free(s);
+                return r;
+        }
+
+        n->meta.type = t;
+
+        if (!n->meta.id)
+                n->meta.id = s;
+
+        return 0;
+}
+
 /* FIXME: Does not rollback on failure! */
 int name_link_names(Name *n, bool replace) {
         char *t;
-        void *state;
+        Iterator i;
         int r;
 
         assert(n);
@@ -99,7 +130,7 @@ int name_link_names(Name *n, bool replace) {
 
         /* Link all names that aren't linked yet. */
 
-        SET_FOREACH(t, n->meta.names, state)
+        SET_FOREACH(t, n->meta.names, i)
                 if (replace) {
                         if ((r = hashmap_replace(n->meta.manager->names, t, n)) < 0)
                                 return r;
@@ -125,24 +156,26 @@ int name_link(Name *n) {
 
         if ((r = name_link_names(n, false)) < 0) {
                 char *t;
-                void *state;
+                Iterator i;
 
                 /* Rollback the registered names */
-                SET_FOREACH(t, n->meta.names, state)
+                SET_FOREACH(t, n->meta.names, i)
                         hashmap_remove_value(n->meta.manager->names, t, n);
 
                 n->meta.linked = false;
                 return r;
         }
 
-        if (n->meta.load_state == NAME_STUB)
-                LIST_PREPEND(Meta, n->meta.manager->load_queue, &n->meta);
+        if (n->meta.load_state == NAME_STUB) {
+                LIST_PREPEND(Meta, load_queue, n->meta.manager->load_queue, &n->meta);
+                n->meta.in_load_queue = true;
+        }
 
         return 0;
 }
 
 static void bidi_set_free(Name *name, Set *s) {
-        void *state;
+        Iterator i;
         Name *other;
 
         assert(name);
@@ -150,7 +183,7 @@ static void bidi_set_free(Name *name, Set *s) {
         /* Frees the set and makes sure we are dropped from the
          * inverse pointers */
 
-        SET_FOREACH(other, s, state) {
+        SET_FOREACH(other, s, i) {
                 NameDependency d;
 
                 for (d = 0; d < _NAME_DEPENDENCY_MAX; d++)
@@ -169,13 +202,13 @@ void name_free(Name *name) {
         /* Detach from next 'bigger' objects */
         if (name->meta.linked) {
                 char *t;
-                void *state;
+                Iterator i;
 
-                SET_FOREACH(t, name->meta.names, state)
+                SET_FOREACH(t, name->meta.names, i)
                         hashmap_remove_value(name->meta.manager->names, t, name);
 
-                if (name->meta.load_state == NAME_STUB)
-                        LIST_REMOVE(Meta, name->meta.manager->load_queue, &name->meta);
+                if (name->meta.in_load_queue)
+                        LIST_REMOVE(Meta, load_queue, name->meta.manager->load_queue, &name->meta);
         }
 
         /* Free data and next 'smaller' objects */
@@ -185,8 +218,8 @@ void name_free(Name *name) {
         for (d = 0; d < _NAME_DEPENDENCY_MAX; d++)
                 bidi_set_free(name, name->meta.dependencies[d]);
 
-        if (NAME_VTABLE(name)->free_hook)
-                NAME_VTABLE(name)->free_hook(name);
+        if (NAME_VTABLE(name)->done)
+                NAME_VTABLE(name)->done(name);
 
         free(name->meta.description);
 
@@ -212,13 +245,11 @@ static int ensure_in_set(Set **s, void *data) {
         assert(s);
         assert(data);
 
-        if (!*s)
-                if (!(*s = set_new(trivial_hash_func, trivial_compare_func)))
-                        return -ENOMEM;
+        if ((r = set_ensure_allocated(s, trivial_hash_func, trivial_compare_func)) < 0)
+                return r;
 
         if ((r = set_put(*s, data)) < 0)
-                if (r != -EEXIST)
-                        return r;
+                return r;
 
         return 0;
 }
@@ -279,7 +310,7 @@ int name_merge(Name *name, Name *other) {
 /* FIXME: Does not rollback on failure! */
 static int augment(Name *n) {
         int r;
-        void* state;
+        Iterator i;
         Name *other;
 
         assert(n);
@@ -287,29 +318,29 @@ static int augment(Name *n) {
         /* Adds in the missing links to make all dependencies
          * bidirectional. */
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], i)
                 if ((r = ensure_in_set(&other->meta.dependencies[NAME_AFTER], n)) < 0)
                         return r;
-        SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], i)
                 if ((r = ensure_in_set(&other->meta.dependencies[NAME_BEFORE], n)) < 0)
                         return r;
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], i)
                 if ((r = ensure_in_set(&other->meta.dependencies[NAME_CONFLICTS], n)) < 0)
                         return r;
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], i)
                 if ((r = ensure_in_set(&other->meta.dependencies[NAME_REQUIRED_BY], n)) < 0)
                         return r;
-        SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], i)
                 if ((r = ensure_in_set(&other->meta.dependencies[NAME_REQUIRED_BY], n)) < 0)
                         return r;
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], i)
                 if ((r = ensure_in_set(&other->meta.dependencies[NAME_SOFT_REQUIRED_BY], n)) < 0)
                         return r;
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], i)
                 if ((r = ensure_in_set(&other->meta.dependencies[NAME_WANTED_BY], n)) < 0)
                         return r;
 
@@ -331,6 +362,9 @@ int name_sanitize(Name *n) {
 const char* name_id(Name *n) {
         assert(n);
 
+        if (n->meta.id)
+                return n->meta.id;
+
         return set_first(n->meta.names);
 }
 
@@ -372,9 +406,9 @@ void name_dump(Name *n, FILE *f, const char *prefix) {
                 [NAME_AFTER] = "After",
         };
 
-        void *state;
         char *t;
         NameDependency d;
+        Iterator i;
 
         assert(n);
 
@@ -391,17 +425,16 @@ void name_dump(Name *n, FILE *f, const char *prefix) {
                 prefix, load_state_table[n->meta.load_state],
                 prefix, active_state_table[name_active_state(n)]);
 
-        SET_FOREACH(t, n->meta.names, state)
+        SET_FOREACH(t, n->meta.names, i)
                 fprintf(f, "%s\tName: %s\n", prefix, t);
 
         for (d = 0; d < _NAME_DEPENDENCY_MAX; d++) {
-                void *state;
                 Name *other;
 
                 if (set_isempty(n->meta.dependencies[d]))
                         continue;
 
-                SET_FOREACH(other, n->meta.dependencies[d], state)
+                SET_FOREACH(other, n->meta.dependencies[d], i)
                         fprintf(f, "%s\t%s: %s\n", prefix, dependency_table[d], name_id(other));
         }
 
@@ -423,13 +456,13 @@ void name_dump(Name *n, FILE *f, const char *prefix) {
 
 static int verify_type(Name *name) {
         char *n;
-        void *state;
+        Iterator i;
 
         assert(name);
 
         /* Checks that all aliases of this name have the same and valid type */
 
-        SET_FOREACH(n, name->meta.names, state) {
+        SET_FOREACH(n, name->meta.names, i) {
                 NameType t;
 
                 if ((t = name_type_from_string(n)) == _NAME_TYPE_INVALID)
@@ -472,14 +505,19 @@ int name_load(Name *name) {
 
         assert(name);
 
+        if (name->meta.in_load_queue) {
+                LIST_REMOVE(Meta, load_queue, name->meta.manager->load_queue, &name->meta);
+                name->meta.in_load_queue = false;
+        }
+
         if (name->meta.load_state != NAME_STUB)
                 return 0;
 
         if ((r = verify_type(name)) < 0)
                 return r;
 
-        if (NAME_VTABLE(name)->load)
-                if ((r = NAME_VTABLE(name)->load(name)) < 0)
+        if (NAME_VTABLE(name)->init)
+                if ((r = NAME_VTABLE(name)->init(name)) < 0)
                         goto fail;
 
         if ((r = name_sanitize(name)) < 0)
@@ -513,8 +551,11 @@ int name_start(Name *n) {
         if (NAME_IS_ACTIVE_OR_RELOADING(state))
                 return -EALREADY;
 
-        if (state == NAME_ACTIVATING)
-                return 0;
+        /* We don't suppress calls to ->start() here when we are
+         * already starting, to allow this request to be used as a
+         * "hurry up" call, for example when the name is in some "auto
+         * restart" state where it waits for a holdoff timer to elapse
+         * before it will start again. */
 
         return NAME_VTABLE(n)->start(n);
 }
@@ -558,7 +599,7 @@ int name_reload(Name *n) {
 
         assert(n);
 
-        if (!NAME_VTABLE(n)->reload)
+        if (!name_can_reload(n))
                 return -EBADR;
 
         state = name_active_state(n);
@@ -573,45 +614,58 @@ int name_reload(Name *n) {
 
 bool name_type_can_reload(NameType t) {
         assert(t >= 0 && t < _NAME_TYPE_MAX);
+
         return !!name_vtable[t]->reload;
 }
 
+bool name_can_reload(Name *n) {
+        assert(n);
+
+        if (!name_type_can_reload(n->meta.type))
+                return false;
+
+        if (!NAME_VTABLE(n)->can_reload)
+                return true;
+
+        return NAME_VTABLE(n)->can_reload(n);
+}
+
 static void retroactively_start_dependencies(Name *n) {
-        void *state;
+        Iterator i;
         Name *other;
 
         assert(n);
         assert(NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(n)));
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], i)
                 if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
                         manager_add_job(n->meta.manager, JOB_START, other, JOB_REPLACE, true, NULL);
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], i)
                 if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
                         manager_add_job(n->meta.manager, JOB_START, other, JOB_FAIL, false, NULL);
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], i)
                 if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
                         manager_add_job(n->meta.manager, JOB_START, other, JOB_REPLACE, true, NULL);
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], i)
                 if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
                         manager_add_job(n->meta.manager, JOB_START, other, JOB_FAIL, false, NULL);
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], i)
                 if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
                         manager_add_job(n->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL);
 }
 
 static void retroactively_stop_dependencies(Name *n) {
-        void *state;
+        Iterator i;
         Name *other;
 
         assert(n);
         assert(NAME_IS_INACTIVE_OR_DEACTIVATING(name_active_state(n)));
 
-        SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], state)
+        SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], i)
                 if (!NAME_IS_INACTIVE_OR_DEACTIVATING(name_active_state(other)))
                         manager_add_job(n->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL);
 }
@@ -626,6 +680,11 @@ void name_notify(Name *n, NameActiveState os, NameActiveState ns) {
         if (os == ns)
                 return;
 
+        if (!NAME_IS_ACTIVE_OR_RELOADING(os) && NAME_IS_ACTIVE_OR_RELOADING(ns))
+                n->meta.active_enter_timestamp = now(CLOCK_REALTIME);
+        else if (NAME_IS_ACTIVE_OR_RELOADING(os) && !NAME_IS_ACTIVE_OR_RELOADING(ns))
+                n->meta.active_exit_timestamp = now(CLOCK_REALTIME);
+
         if (n->meta.job) {
 
                 if (n->meta.job->state == JOB_WAITING)
@@ -633,7 +692,7 @@ void name_notify(Name *n, NameActiveState os, NameActiveState ns) {
                         /* So we reached a different state for this
                          * job. Let's see if we can run it now if it
                          * failed previously due to EAGAIN. */
-                        job_run_and_invalidate(n->meta.job);
+                        job_schedule_run(n->meta.job);
 
                 else {
                         assert(n->meta.job->state == JOB_RUNNING);
@@ -709,12 +768,17 @@ int name_watch_fd(Name *n, int fd, uint32_t events) {
         zero(ev);
         ev.data.fd = fd;
         ev.data.ptr = n;
+        ev.data.u32 = MANAGER_FD;
         ev.events = events;
 
-        if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
-                return -errno;
+        if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) >= 0)
+                return 0;
 
-        return 0;
+        if (errno == EEXIST)
+                if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_MOD, fd, &ev) >= 0)
+                        return 0;
+
+        return -errno;
 }
 
 void name_unwatch_fd(Name *n, int fd) {
@@ -737,3 +801,121 @@ void name_unwatch_pid(Name *n, pid_t pid) {
 
         hashmap_remove(n->meta.manager->watch_pids, UINT32_TO_PTR(pid));
 }
+
+int name_watch_timer(Name *n, usec_t delay, int *id) {
+        struct epoll_event ev;
+        int fd;
+        struct itimerspec its;
+        int flags;
+        bool ours;
+
+        assert(n);
+        assert(id);
+
+        /* This will try to reuse the old timer if there is one */
+
+        if (*id >= 0) {
+                ours = false;
+                fd = *id;
+
+        } else {
+                ours = true;
+
+                if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0)
+                        return -errno;
+        }
+
+        zero(its);
+
+        if (delay <= 0) {
+                /* Set absolute time in the past, but not 0, since we
+                 * don't want to disarm the timer */
+                its.it_value.tv_sec = 0;
+                its.it_value.tv_nsec = 1;
+
+                flags = TFD_TIMER_ABSTIME;
+        } else {
+                timespec_store(&its.it_value, delay);
+                flags = 0;
+        }
+
+        /* This will also flush the elapse counter */
+        if (timerfd_settime(fd, flags, &its, NULL) < 0)
+                goto fail;
+
+        zero(ev);
+        ev.data.fd = fd;
+        ev.data.ptr = n;
+        ev.data.u32 = MANAGER_TIMER;
+        ev.events = POLLIN;
+
+        if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
+                goto fail;
+
+        *id = fd;
+        return 0;
+
+fail:
+        if (ours)
+                assert_se(close_nointr(fd) == 0);
+
+        return -errno;
+}
+
+void name_unwatch_timer(Name *n, int *id) {
+        assert(n);
+        assert(id);
+
+        if (*id >= 0) {
+                assert_se(epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_DEL, *id, NULL) >= 0);
+                assert_se(close_nointr(*id) == 0);
+
+                *id = -1;
+        }
+}
+
+char *name_change_suffix(const char *t, const char *suffix) {
+        char *e, *n;
+        size_t a, b;
+
+        assert(t);
+        assert(name_is_valid(t));
+        assert(suffix);
+
+        assert_se(e = strrchr(t, '.'));
+        a = e - t;
+        b = strlen(suffix);
+
+        if (!(n = new(char, a + b + 1)))
+                return NULL;
+
+        memcpy(n, t, a);
+        memcpy(n+a, t, b+1);
+
+        return n;
+}
+
+bool name_job_is_applicable(Name *n, JobType j) {
+        assert(n);
+        assert(j >= 0 && j < _JOB_TYPE_MAX);
+
+        switch (j) {
+                case JOB_VERIFY_ACTIVE:
+                case JOB_START:
+                        return true;
+
+                case JOB_STOP:
+                case JOB_RESTART:
+                case JOB_TRY_RESTART:
+                        return name_can_start(n);
+
+                case JOB_RELOAD:
+                        return name_can_reload(n);
+
+                case JOB_RELOAD_OR_START:
+                        return name_can_reload(n) && name_can_start(n);
+
+                default:
+                        assert_not_reached("Invalid job type");
+        }
+}
diff --git a/name.h b/name.h
index 59f3b16..9c638f3 100644 (file)
--- a/name.h
+++ b/name.h
@@ -21,8 +21,11 @@ typedef enum NameDependency NameDependency;
 #include "list.h"
 #include "socket-util.h"
 #include "execute.h"
+#include "util.h"
 
 #define NAME_MAX 32
+#define DEFAULT_TIMEOUT_USEC (20*USEC_PER_SEC)
+#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
 
 enum NameType {
         NAME_SERVICE = 0,
@@ -90,6 +93,8 @@ struct Meta {
         NameType type;
         NameLoadState load_state;
 
+        char *id; /* One name is special because we use it for identification. Points to an entry in the names set */
+
         Set *names;
         Set *dependencies[_NAME_DEPENDENCY_MAX];
 
@@ -100,9 +105,13 @@ struct Meta {
         Job *job;
 
         bool linked:1;
+        bool in_load_queue:1;
+
+        usec_t active_enter_timestamp;
+        usec_t active_exit_timestamp;
 
         /* Load queue */
-        LIST_FIELDS(Meta);
+        LIST_FIELDS(Meta, load_queue);
 };
 
 #include "service.h"
@@ -129,21 +138,27 @@ union Name {
 struct NameVTable {
         const char *suffix;
 
-        int (*load)(Name *n);
+        int (*init)(Name *n);
+        void (*done)(Name *n);
+
         void (*dump)(Name *n, FILE *f, const char *prefix);
 
         int (*start)(Name *n);
         int (*stop)(Name *n);
         int (*reload)(Name *n);
 
+
+        bool (*can_reload)(Name *n);
+
         /* Boils down the more complex internal state of this name to
          * a simpler one that the engine can understand */
         NameActiveState (*active_state)(Name *n);
 
         void (*fd_event)(Name *n, int fd, uint32_t events);
         void (*sigchld_event)(Name *n, pid_t pid, int code, int status);
+        void (*timer_event)(Name *n, int id, uint64_t n_elapsed);
 
-        void (*free_hook)(Name *n);
+        void (*retry)(Name *n);
 };
 
 extern const NameVTable * const name_vtable[_NAME_TYPE_MAX];
@@ -171,10 +186,10 @@ DEFINE_CAST(MOUNT, Mount);
 DEFINE_CAST(AUTOMOUNT, Automount);
 DEFINE_CAST(SNAPSHOT, Snapshot);
 
-NameActiveState name_active_state(Name *name);
-
 bool name_type_can_start(NameType t);
 bool name_type_can_reload(NameType t);
+bool name_can_reload(Name *n);
+#define name_can_start(n) name_type_can_start((n)->meta.type)
 
 NameType name_type_from_string(const char *n);
 bool name_is_valid(const char *n);
@@ -190,6 +205,10 @@ int name_load(Name *name);
 const char* name_id(Name *n);
 const char *name_description(Name *n);
 
+int name_add_name(Name *n, const char *text);
+
+NameActiveState name_active_state(Name *name);
+
 void name_dump(Name *n, FILE *f, const char *prefix);
 
 int name_start(Name *n);
@@ -204,4 +223,11 @@ void name_unwatch_fd(Name *n, int fd);
 int name_watch_pid(Name *n, pid_t pid);
 void name_unwatch_pid(Name *n, pid_t pid);
 
+int name_watch_timer(Name *n, usec_t delay, int *id);
+void name_unwatch_timer(Name *n, int *id);
+
+char *name_change_suffix(const char *t, const char *suffix);
+
+bool name_job_is_applicable(Name *n, JobType j);
+
 #endif
index ac9c39c..e661dc7 100644 (file)
--- a/service.c
+++ b/service.c
@@ -1,11 +1,30 @@
 /*-*- Mode: C; c-basic-offset: 8 -*-*/
 
 #include <errno.h>
+#include <signal.h>
 
 #include "name.h"
 #include "service.h"
 #include "load-fragment.h"
 #include "load-dropin.h"
+#include "log.h"
+
+static const NameActiveState state_table[_SERVICE_STATE_MAX] = {
+        [SERVICE_DEAD] = NAME_INACTIVE,
+        [SERVICE_START_PRE] = NAME_ACTIVATING,
+        [SERVICE_START] = NAME_ACTIVATING,
+        [SERVICE_START_POST] = NAME_ACTIVATING,
+        [SERVICE_RUNNING] = NAME_ACTIVE,
+        [SERVICE_RELOAD] = NAME_ACTIVE_RELOADING,
+        [SERVICE_STOP] = NAME_DEACTIVATING,
+        [SERVICE_STOP_SIGTERM] = NAME_DEACTIVATING,
+        [SERVICE_STOP_SIGKILL] = NAME_DEACTIVATING,
+        [SERVICE_STOP_POST] = NAME_DEACTIVATING,
+        [SERVICE_FINAL_SIGTERM] = NAME_DEACTIVATING,
+        [SERVICE_FINAL_SIGKILL] = NAME_DEACTIVATING,
+        [SERVICE_MAINTAINANCE] = NAME_INACTIVE,
+        [SERVICE_AUTO_RESTART] = NAME_ACTIVATING,
+};
 
 static int service_load_sysv(Service *s) {
         assert(s);
@@ -16,13 +35,26 @@ static int service_load_sysv(Service *s) {
         return -ENOENT;
 }
 
-static int service_load(Name *n) {
+static int service_init(Name *n) {
         int r;
         Service *s = SERVICE(n);
 
         assert(s);
 
-        exec_context_defaults(&s->exec_context);
+        /* First, reset everything to the defaults, in case this is a
+         * reload */
+
+        s->type = 0;
+        s->restart = 0;
+
+        s->timeout_usec = DEFAULT_TIMEOUT_USEC;
+        s->restart_usec = DEFAULT_RESTART_USEC;
+
+        exec_context_init(&s->exec_context);
+
+        s->timer_id = -1;
+
+        s->state = SERVICE_DEAD;
 
         /* Load a .service file */
         r = name_load_fragment(n);
@@ -41,6 +73,33 @@ static int service_load(Name *n) {
         return 0;
 }
 
+static void service_done(Name *n) {
+        Service *s = SERVICE(n);
+
+        assert(s);
+
+        free(s->pid_file);
+        s->pid_file = NULL;
+
+        exec_context_done(&s->exec_context);
+        exec_command_free_array(s->exec_command, _SERVICE_EXEC_MAX);
+        s->control_command = NULL;
+
+        /* This will leak a process, but at least no memory or any of
+         * our resources */
+        if (s->main_pid > 0) {
+                name_unwatch_pid(n, s->main_pid);
+                s->main_pid = 0;
+        }
+
+        if (s->control_pid > 0) {
+                name_unwatch_pid(n, s->control_pid);
+                s->control_pid = 0;
+        }
+
+        name_unwatch_timer(n, &s->timer_id);
+}
+
 static void service_dump(Name *n, FILE *f, const char *prefix) {
 
         static const char* const state_table[_SERVICE_STATE_MAX] = {
@@ -49,27 +108,24 @@ static void service_dump(Name *n, FILE *f, const char *prefix) {
                 [SERVICE_START] = "start",
                 [SERVICE_START_POST] = "post",
                 [SERVICE_RUNNING] = "running",
-                [SERVICE_RELOAD_PRE] = "reload-pre",
                 [SERVICE_RELOAD] = "reload",
-                [SERVICE_RELOAD_POST] = "reload-post",
-                [SERVICE_STOP_PRE] = "stop-pre",
                 [SERVICE_STOP] = "stop",
-                [SERVICE_SIGTERM] = "sigterm",
-                [SERVICE_SIGKILL] = "sigkill",
+                [SERVICE_STOP_SIGTERM] = "stop-sigterm",
+                [SERVICE_STOP_SIGKILL] = "stop-sigkill",
                 [SERVICE_STOP_POST] = "stop-post",
-                [SERVICE_MAINTAINANCE] = "maintainance"
+                [SERVICE_FINAL_SIGTERM] = "final-sigterm",
+                [SERVICE_FINAL_SIGKILL] = "final-sigkill",
+                [SERVICE_MAINTAINANCE] = "maintainance",
+                [SERVICE_AUTO_RESTART] = "auto-restart",
         };
 
         static const char* const command_table[_SERVICE_EXEC_MAX] = {
-                [SERVICE_EXEC_START_PRE] = "StartPre",
-                [SERVICE_EXEC_START] = "Start",
-                [SERVICE_EXEC_START_POST] = "StartPost",
-                [SERVICE_EXEC_RELOAD_PRE] = "ReloadPre",
-                [SERVICE_EXEC_RELOAD] = "Reload",
-                [SERVICE_EXEC_RELOAD_POST] = "ReloadPost",
-                [SERVICE_EXEC_STOP_PRE] = "StopPre",
-                [SERVICE_EXEC_STOP] = "Stop",
-                [SERVICE_EXEC_STOP_POST] = "StopPost",
+                [SERVICE_EXEC_START_PRE] = "ExecStartPre",
+                [SERVICE_EXEC_START] = "ExecStart",
+                [SERVICE_EXEC_START_POST] = "ExecStartPost",
+                [SERVICE_EXEC_RELOAD] = "ExecReload",
+                [SERVICE_EXEC_STOP] = "ExecStop",
+                [SERVICE_EXEC_STOP_POST] = "ExecStopPost",
         };
 
         ServiceExecCommand c;
@@ -81,21 +137,407 @@ static void service_dump(Name *n, FILE *f, const char *prefix) {
                 "%sService State: %s\n",
                 prefix, state_table[s->state]);
 
+        if (s->pid_file)
+                fprintf(f,
+                        "%sPIDFile: %s\n",
+                        prefix, s->pid_file);
+
+
         exec_context_dump(&s->exec_context, f, prefix);
 
         for (c = 0; c < _SERVICE_EXEC_MAX; c++) {
                 ExecCommand *i;
 
-                LIST_FOREACH(i, s->exec_command[c])
+                LIST_FOREACH(command, i, s->exec_command[c])
                         fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path);
         }
 }
 
-static int service_set_state(Service *s, ServiceState state) {
+static int service_load_pid_file(Service *s) {
+        char *k;
+        unsigned long p;
+        int r;
+
+        assert(s);
+
+        if (s->main_pid_known)
+                return 0;
+
+        if (!s->pid_file)
+                return -ENOENT;
+
+        if ((r = read_one_line_file(s->pid_file, &k)) < 0)
+                return r;
+
+        if ((r = safe_atolu(k, &p)) < 0) {
+                free(k);
+                return r;
+        }
+
+        if ((unsigned long) (pid_t) p != p)
+                return -ERANGE;
+
+        s->main_pid = p;
+        s->main_pid_known = true;
+
+        return 0;
+}
+
+static void service_set_state(Service *s, ServiceState state) {
+        ServiceState old_state;
         assert(s);
 
+        old_state = s->state;
         s->state = state;
+
+        if (state != SERVICE_START_PRE &&
+            state != SERVICE_START &&
+            state != SERVICE_START_POST &&
+            state != SERVICE_RELOAD &&
+            state != SERVICE_STOP &&
+            state != SERVICE_STOP_SIGTERM &&
+            state != SERVICE_STOP_SIGKILL &&
+            state != SERVICE_STOP_POST &&
+            state != SERVICE_FINAL_SIGTERM &&
+            state != SERVICE_FINAL_SIGKILL &&
+            state != SERVICE_AUTO_RESTART)
+                name_unwatch_timer(NAME(s), &s->timer_id);
+
+        if (state != SERVICE_START_POST &&
+            state != SERVICE_RUNNING &&
+            state != SERVICE_RELOAD &&
+            state != SERVICE_STOP &&
+            state != SERVICE_STOP_SIGTERM &&
+            state != SERVICE_STOP_SIGKILL)
+                if (s->main_pid >= 0) {
+                        name_unwatch_pid(NAME(s), s->main_pid);
+                        s->main_pid = 0;
+                }
+
+        if (state != SERVICE_START_PRE &&
+            state != SERVICE_START &&
+            state != SERVICE_START_POST &&
+            state != SERVICE_RELOAD &&
+            state != SERVICE_STOP &&
+            state != SERVICE_STOP_SIGTERM &&
+            state != SERVICE_STOP_SIGKILL &&
+            state != SERVICE_STOP_POST &&
+            state != SERVICE_FINAL_SIGTERM &&
+            state != SERVICE_FINAL_SIGKILL)
+                if (s->control_pid >= 0) {
+                        name_unwatch_pid(NAME(s), s->control_pid);
+                        s->control_pid = 0;
+                }
+
+        if (state != SERVICE_START_PRE &&
+            state != SERVICE_START &&
+            state != SERVICE_START_POST &&
+            state != SERVICE_RELOAD &&
+            state != SERVICE_STOP &&
+            state != SERVICE_STOP_POST)
+                s->control_command = NULL;
+
+        name_notify(NAME(s), state_table[old_state], state_table[s->state]);
+}
+
+static int service_spawn(Service *s, ExecCommand *c, bool timeout, pid_t *_pid) {
+        pid_t pid;
+        int r;
+
+        assert(s);
+        assert(c);
+        assert(_pid);
+
+        if (timeout) {
+                if ((r = name_watch_timer(NAME(s), s->timeout_usec, &s->timer_id)) < 0)
+                        goto fail;
+        } else
+                name_unwatch_timer(NAME(s), &s->timer_id);
+
+        if ((r = exec_spawn(c, &s->exec_context, NULL, 0, &pid)) < 0)
+                goto fail;
+
+        if ((r = name_watch_pid(NAME(s), pid)) < 0)
+                /* FIXME: we need to do something here */
+                goto fail;
+
+        *_pid = pid;
+
         return 0;
+
+fail:
+        if (timeout)
+                name_unwatch_timer(NAME(s), &s->timer_id);
+
+        return r;
+}
+
+static void service_enter_dead(Service *s, bool success, bool allow_restart) {
+        int r;
+        assert(s);
+
+        if (!success)
+                s->failure = true;
+
+        if (allow_restart &&
+            (s->restart == SERVICE_RESTART_ALWAYS ||
+             (s->restart == SERVICE_RESTART_ON_SUCCESS && !s->failure))) {
+
+                if ((r = name_watch_timer(NAME(s), s->restart_usec, &s->timer_id)) < 0)
+                        goto fail;
+
+                service_set_state(s, SERVICE_AUTO_RESTART);
+        } else
+                service_set_state(s, s->failure ? SERVICE_MAINTAINANCE : SERVICE_DEAD);
+
+        return;
+
+fail:
+        log_warning("%s failed to run install restart timer: %s", name_id(NAME(s)), strerror(-r));
+        service_enter_dead(s, false, false);
+}
+
+static void service_enter_signal(Service *s, ServiceState state, bool success);
+
+static void service_enter_stop_post(Service *s, bool success) {
+        int r;
+        assert(s);
+
+        if (!success)
+                s->failure = true;
+
+        if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST])) {
+
+                if ((r = service_spawn(s, s->control_command, true, &s->control_pid)) < 0)
+                        goto fail;
+
+                service_set_state(s, SERVICE_STOP_POST);
+        } else
+                service_enter_dead(s, true, true);
+
+        return;
+
+fail:
+        log_warning("%s failed to run stop executable: %s", name_id(NAME(s)), strerror(-r));
+        service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+}
+
+static void service_enter_signal(Service *s, ServiceState state, bool success) {
+        int r;
+        bool sent = false;
+
+        assert(s);
+
+        if (!success)
+                s->failure = true;
+
+        if (s->main_pid > 0 || s->control_pid > 0) {
+                int sig;
+
+                sig = (state == SERVICE_STOP_SIGTERM || state == SERVICE_FINAL_SIGTERM) ? SIGTERM : SIGKILL;
+
+                r = 0;
+                if (s->main_pid > 0) {
+                        if (kill(s->main_pid, sig) < 0 && errno != ESRCH)
+                                r = -errno;
+                        else
+                                sent = true;
+                }
+
+                if (s->control_pid > 0) {
+                        if (kill(s->control_pid, sig) < 0 && errno != ESRCH)
+                                r = -errno;
+                        else
+                                sent = true;
+                }
+
+                if (r < 0)
+                        goto fail;
+
+                service_set_state(s, state);
+        } else
+                service_enter_dead(s, true, true);
+
+        return;
+
+fail:
+        log_warning("%s failed to kill processes: %s", name_id(NAME(s)), strerror(-r));
+
+        if (sent)  {
+                s->failure = true;
+                service_set_state(s, state);
+        } else if (state == SERVICE_STOP_SIGTERM || state == SERVICE_STOP_SIGKILL)
+                service_enter_stop_post(s, false);
+        else
+                service_enter_dead(s, false, true);
+}
+
+static void service_enter_stop(Service *s, bool success) {
+        int r;
+        assert(s);
+
+        if (!success)
+                s->failure = true;
+
+        if ((s->control_command = s->exec_command[SERVICE_EXEC_STOP])) {
+
+                if ((r = service_spawn(s, s->control_command, true, &s->control_pid)) < 0)
+                        goto fail;
+
+                service_set_state(s, SERVICE_STOP);
+        } else
+                service_enter_signal(s, SERVICE_STOP_SIGTERM, true);
+
+        return;
+
+fail:
+        log_warning("%s failed to run stop executable: %s", name_id(NAME(s)), strerror(-r));
+        service_enter_signal(s, SERVICE_STOP_SIGTERM, false);
+}
+
+static void service_enter_start_post(Service *s) {
+        int r;
+        assert(s);
+
+        if ((s->control_command = s->exec_command[SERVICE_EXEC_START_POST])) {
+
+                if ((r = service_spawn(s, s->control_command, true, &s->control_pid)) < 0)
+                        goto fail;
+
+                service_set_state(s, SERVICE_START_POST);
+        } else
+                service_set_state(s, SERVICE_RUNNING);
+
+        return;
+
+fail:
+        log_warning("%s failed to run start-post executable: %s", name_id(NAME(s)), strerror(-r));
+        service_enter_stop(s, false);
+}
+
+static void service_enter_start(Service *s) {
+        pid_t pid;
+        int r;
+
+        assert(s);
+
+        assert(s->exec_command[SERVICE_EXEC_START]);
+        assert(!s->exec_command[SERVICE_EXEC_START]->command_next);
+
+        if ((r = service_spawn(s, s->exec_command[SERVICE_EXEC_START], s->type == SERVICE_FORKING, &pid)) < 0)
+                goto fail;
+
+        if (s->type == SERVICE_SIMPLE) {
+                /* For simple services we immediately start
+                 * the START_POST binaries. */
+
+                s->main_pid = pid;
+                s->main_pid_known = true;
+                service_enter_start_post(s);
+
+        } else  if (s->type == SERVICE_FORKING) {
+
+                /* For forking services we wait until the start
+                 * process exited. */
+
+                s->control_pid = pid;
+                s->control_command = s->exec_command[SERVICE_EXEC_START];
+                service_set_state(s, SERVICE_START);
+        } else
+                assert_not_reached("Unknown service type");
+
+        return;
+
+fail:
+        log_warning("%s failed to run start exectuable: %s", name_id(NAME(s)), strerror(-r));
+        service_enter_stop(s, false);
+}
+
+static void service_enter_start_pre(Service *s) {
+        int r;
+
+        assert(s);
+
+        if ((s->control_command = s->exec_command[SERVICE_EXEC_START_PRE])) {
+
+                if ((r = service_spawn(s, s->control_command, true, &s->control_pid)) < 0)
+                        goto fail;
+
+                service_set_state(s, SERVICE_START_PRE);
+        } else
+                service_enter_start(s);
+
+        return;
+
+fail:
+        log_warning("%s failed to run start-pre executable: %s", name_id(NAME(s)), strerror(-r));
+        service_enter_dead(s, false, true);
+}
+
+static void service_enter_restart(Service *s) {
+        int r;
+        assert(s);
+
+        if ((r = manager_add_job(NAME(s)->meta.manager, JOB_START, NAME(s), JOB_FAIL, false, NULL)) < 0)
+                goto fail;
+
+        log_debug("%s scheduled restart job.", name_id(NAME(s)));
+        service_enter_dead(s, true, false);
+        return;
+
+fail:
+
+        log_warning("%s failed to schedule restart job: %s", name_id(NAME(s)), strerror(-r));
+        service_enter_dead(s, false, false);
+}
+
+static void service_enter_reload(Service *s) {
+        int r;
+
+        assert(s);
+
+        if ((s->control_command = s->exec_command[SERVICE_EXEC_RELOAD])) {
+
+                if ((r = service_spawn(s, s->control_command, true, &s->control_pid)) < 0)
+                        goto fail;
+
+                service_set_state(s, SERVICE_RELOAD);
+        } else
+                service_set_state(s, SERVICE_RUNNING);
+
+        return;
+
+fail:
+        log_warning("%s failed to run reload executable: %s", name_id(NAME(s)), strerror(-r));
+        service_enter_stop(s, false);
+}
+
+static void service_run_next(Service *s, bool success) {
+        int r;
+
+        assert(s);
+        assert(s->control_command);
+        assert(s->control_command->command_next);
+
+        if (!success)
+                s->failure = true;
+
+        s->control_command = s->control_command->command_next;
+
+        if ((r = service_spawn(s, s->control_command, true, &s->control_pid)) < 0)
+                goto fail;
+
+        return;
+
+fail:
+        log_warning("%s failed to run spawn next executable: %s", name_id(NAME(s)), strerror(-r));
+
+        if (s->state == SERVICE_STOP)
+                service_enter_stop_post(s, false);
+        else if (s->state == SERVICE_STOP_POST)
+                service_enter_dead(s, false, true);
+        else
+                service_enter_stop(s, false);
 }
 
 static int service_start(Name *n) {
@@ -103,17 +545,29 @@ static int service_start(Name *n) {
 
         assert(s);
 
-        /* We cannot fulfill this request right now */
-        if (s->state == SERVICE_STOP_PRE ||
-            s->state == SERVICE_STOP ||
-            s->state == SERVICE_SIGTERM ||
-            s->state == SERVICE_SIGKILL ||
-            s->state == SERVICE_STOP_POST)
+        /* We cannot fulfill this request right now, try again later
+         * please! */
+        if (s->state == SERVICE_STOP ||
+            s->state == SERVICE_STOP_SIGTERM ||
+            s->state == SERVICE_STOP_SIGKILL ||
+            s->state == SERVICE_STOP_POST ||
+            s->state == SERVICE_FINAL_SIGTERM ||
+            s->state == SERVICE_FINAL_SIGKILL)
                 return -EAGAIN;
 
-        assert(s->state == SERVICE_DEAD || s->state == SERVICE_MAINTAINANCE);
+        /* Already on it! */
+        if (s->state == SERVICE_START_PRE ||
+            s->state == SERVICE_START ||
+            s->state == SERVICE_START_POST)
+                return 0;
+
+        assert(s->state == SERVICE_DEAD || s->state == SERVICE_MAINTAINANCE || s->state == SERVICE_AUTO_RESTART);
 
-        return service_set_state(s, SERVICE_START_PRE);
+        s->failure = false;
+        s->main_pid_known = false;
+
+        service_enter_start_pre(s);
+        return 0;
 }
 
 static int service_stop(Name *n) {
@@ -121,62 +575,308 @@ static int service_stop(Name *n) {
 
         assert(s);
 
+        if (s->state == SERVICE_START_PRE ||
+            s->state == SERVICE_START ||
+            s->state == SERVICE_START_POST ||
+            s->state == SERVICE_RELOAD)
+                return -EAGAIN;
+
+        if (s->state == SERVICE_AUTO_RESTART) {
+                service_set_state(s, SERVICE_DEAD);
+                return 0;
+        }
+
+        assert(s->state == SERVICE_RUNNING);
 
+        service_enter_stop(s, true);
         return 0;
 }
 
 static int service_reload(Name *n) {
+        Service *s = SERVICE(n);
+
+        assert(s);
+
+        assert(s->state == SERVICE_RUNNING);
+
+        service_enter_reload(s);
         return 0;
 }
 
+static bool service_can_reload(Name *n) {
+        Service *s = SERVICE(n);
+
+        assert(s);
+
+        return !!s->exec_command[SERVICE_EXEC_RELOAD];
+}
+
 static NameActiveState service_active_state(Name *n) {
+        assert(n);
 
-        static const NameActiveState table[_SERVICE_STATE_MAX] = {
-                [SERVICE_DEAD] = NAME_INACTIVE,
-                [SERVICE_START_PRE] = NAME_ACTIVATING,
-                [SERVICE_START] = NAME_ACTIVATING,
-                [SERVICE_START_POST] = NAME_ACTIVATING,
-                [SERVICE_RUNNING] = NAME_ACTIVE,
-                [SERVICE_RELOAD_PRE] = NAME_ACTIVE_RELOADING,
-                [SERVICE_RELOAD] = NAME_ACTIVE_RELOADING,
-                [SERVICE_RELOAD_POST] = NAME_ACTIVE_RELOADING,
-                [SERVICE_STOP_PRE] = NAME_DEACTIVATING,
-                [SERVICE_STOP] = NAME_DEACTIVATING,
-                [SERVICE_SIGTERM] = NAME_DEACTIVATING,
-                [SERVICE_SIGKILL] = NAME_DEACTIVATING,
-                [SERVICE_STOP_POST] = NAME_DEACTIVATING,
-                [SERVICE_MAINTAINANCE] = NAME_INACTIVE,
-        };
+        return state_table[SERVICE(n)->state];
+}
+
+static int main_pid_good(Service *s) {
+        assert(s);
+
+        /* Returns 0 if the pid is dead, 1 if it is good, -1 if we
+         * don't know */
+
+        /* If we know the pid file, then lets just check if it is
+         * still valid */
+        if (s->main_pid_known)
+                return s->main_pid > 0;
+
+        /* We don't know the pid */
+        return -1;
+}
+
+static bool control_pid_good(Service *s) {
+        assert(s);
 
-        return table[SERVICE(n)->state];
+        return s->control_pid > 0;
 }
 
-static void service_free_hook(Name *n) {
+static void service_sigchld_event(Name *n, pid_t pid, int code, int status) {
         Service *s = SERVICE(n);
-        unsigned c;
+        bool success;
 
         assert(s);
+        assert(pid >= 0);
+
+        success = code == CLD_EXITED || status == 0;
+        s->failure = s->failure || !success;
+
+        if (s->main_pid == pid) {
+
+                exec_status_fill(&s->main_exec_status, pid, code, status);
+                s->main_pid = 0;
+
+                if (s->type == SERVICE_SIMPLE) {
+                        assert(s->exec_command[SERVICE_EXEC_START]);
+                        s->exec_command[SERVICE_EXEC_START]->exec_status = s->main_exec_status;
+                }
+
+                log_debug("%s: main process exited, code=%s status=%i", name_id(n), sigchld_code(code), status);
+
+                /* The service exited, so the service is officially
+                 * gone. */
+
+                switch (s->state) {
+
+                case SERVICE_START_POST:
+                case SERVICE_RELOAD:
+                case SERVICE_STOP:
+                        /* Need to wait until the operation is
+                         * done */
+                        break;
+
+                case SERVICE_RUNNING:
+                        service_enter_stop(s, success);
+                        break;
+
+                case SERVICE_STOP_SIGTERM:
+                case SERVICE_STOP_SIGKILL:
+
+                        if (!control_pid_good(s))
+                                service_enter_stop_post(s, success);
 
-        exec_context_free(&s->exec_context);
+                        /* If there is still a control process, wait for that first */
+                        break;
 
-        for (c = 0; c < _SERVICE_EXEC_MAX; c++)
-                exec_command_free_list(s->exec_command[c]);
+                default:
+                        assert_not_reached("Uh, main process died at wrong time.");
+                }
 
-        if (s->socket)
-                s->socket->service = NULL;
+        } else if (s->control_pid == pid) {
+                assert(s->control_command);
+
+                exec_status_fill(&s->control_command->exec_status, pid, code, status);
+                s->control_pid = 0;
+
+                log_debug("%s: control process exited, code=%s status=%i", name_id(n), sigchld_code(code), status);
+
+                /* If we are shutting things down anyway we
+                 * don't care about failing commands. */
+
+                if (s->control_command->command_next &&
+                    (success || (s->state == SERVICE_EXEC_STOP || s->state == SERVICE_EXEC_STOP_POST)))
+
+                        /* There is another command to *
+                         * execute, so let's do that. */
+
+                        service_run_next(s, success);
+
+                else {
+                        /* No further commands for this step, so let's
+                         * figure out what to do next */
+
+                        switch (s->state) {
+
+                        case SERVICE_START_PRE:
+                                if (success)
+                                        service_enter_start(s);
+                                else
+                                        service_enter_stop(s, false);
+                                break;
+
+                        case SERVICE_START:
+                                assert(s->type == SERVICE_FORKING);
+
+                                /* Let's try to load the pid
+                                 * file here if we can. We
+                                 * ignore the return value,
+                                 * since the PID file might
+                                 * actually be created by a
+                                 * START_POST script */
+
+                                if (success) {
+                                        if (s->pid_file)
+                                                service_load_pid_file(s);
+
+                                        service_enter_start_post(s);
+                                } else
+                                        service_enter_stop(s, false);
+
+                                break;
+
+                        case SERVICE_START_POST:
+                                if (success && s->pid_file && !s->main_pid_known) {
+                                        int r;
+
+                                        /* Hmm, let's see if we can
+                                         * load the pid now after the
+                                         * start-post scripts got
+                                         * executed. */
+
+                                        if ((r = service_load_pid_file(s)) < 0)
+                                                log_warning("%s: failed to load PID file %s: %s", name_id(NAME(s)), s->pid_file, strerror(-r));
+                                }
+
+                                /* Fall through */
+
+                        case SERVICE_RELOAD:
+                                if (success) {
+                                        if (main_pid_good(s) != 0)
+                                                service_set_state(s, SERVICE_RUNNING);
+                                        else
+                                                service_enter_stop(s, true);
+                                } else
+                                        service_enter_stop(s, false);
+
+                                break;
+
+                        case SERVICE_STOP:
+                                if (main_pid_good(s) > 0)
+                                        /* Still not dead and we know the PID? Let's go hunting. */
+                                        service_enter_signal(s, SERVICE_STOP_SIGTERM, success);
+                                else
+                                        service_enter_stop_post(s, success);
+                                break;
+
+                        case SERVICE_STOP_SIGTERM:
+                        case SERVICE_STOP_SIGKILL:
+                                if (main_pid_good(s) <= 0)
+                                        service_enter_stop_post(s, success);
+
+                                /* If there is still a service
+                                 * process around, wait until
+                                 * that one quit, too */
+                                break;
+
+                        case SERVICE_STOP_POST:
+                        case SERVICE_FINAL_SIGTERM:
+                        case SERVICE_FINAL_SIGKILL:
+                                service_enter_dead(s, success, true);
+                                break;
+
+                        default:
+                                assert_not_reached("Uh, control process died at wrong time.");
+                        }
+                }
+        } else
+                assert_not_reached("Got SIGCHLD for unkown PID");
+}
+
+static void service_timer_event(Name *n, int id, uint64_t elapsed) {
+        Service *s = SERVICE(n);
+
+        assert(s);
+        assert(elapsed == 1);
+
+        assert(s->timer_id == id);
+
+        switch (s->state) {
+
+        case SERVICE_START_PRE:
+        case SERVICE_START:
+        case SERVICE_START_POST:
+        case SERVICE_RELOAD:
+                log_warning("%s operation timed out. Stopping.", name_id(n));
+                service_enter_stop(s, false);
+                break;
+
+        case SERVICE_STOP:
+                log_warning("%s stopping timed out. Terminating.", name_id(n));
+                service_enter_signal(s, SERVICE_STOP_SIGTERM, false);
+                break;
+
+        case SERVICE_STOP_SIGTERM:
+                log_warning("%s stopping timed out. Killing.", name_id(n));
+                service_enter_signal(s, SERVICE_STOP_SIGKILL, false);
+                break;
+
+        case SERVICE_STOP_SIGKILL:
+                /* Uh, wie sent a SIGKILL and it is still not gone?
+                 * Must be something we cannot kill, so let's just be
+                 * weirded out and continue */
+
+                log_warning("%s still around after SIGKILL. Ignoring.", name_id(n));
+                service_enter_stop_post(s, false);
+                break;
+
+        case SERVICE_STOP_POST:
+                log_warning("%s stopping timed out (2). Terminating.", name_id(n));
+                service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
+                break;
+
+        case SERVICE_FINAL_SIGTERM:
+                log_warning("%s stopping timed out (2). Killing.", name_id(n));
+                service_enter_signal(s, SERVICE_FINAL_SIGKILL, false);
+                break;
+
+        case SERVICE_FINAL_SIGKILL:
+                log_warning("%s still around after SIGKILL (2). Entering maintainance mode.", name_id(n));
+                service_enter_dead(s, false, true);
+                break;
+
+        case SERVICE_AUTO_RESTART:
+                log_debug("%s holdoff time over, scheduling restart.", name_id(n));
+                service_enter_restart(s);
+                break;
+
+        default:
+                assert_not_reached("Timeout at wrong time.");
+        }
 }
 
 const NameVTable service_vtable = {
         .suffix = ".service",
 
-        .load = service_load,
+        .init = service_init,
+        .done = service_done,
+
         .dump = service_dump,
 
         .start = service_start,
         .stop = service_stop,
         .reload = service_reload,
 
+        .can_reload = service_can_reload,
+
         .active_state = service_active_state,
 
-        .free_hook = service_free_hook
+        .sigchld_event = service_sigchld_event,
+        .timer_event = service_timer_event,
 };
index d7f2c70..41dabff 100644 (file)
--- a/service.h
+++ b/service.h
@@ -6,8 +6,6 @@
 typedef struct Service Service;
 
 #include "name.h"
-#include "socket.h"
-#include "timer.h"
 
 typedef enum ServiceState {
         SERVICE_DEAD,
@@ -15,31 +13,34 @@ typedef enum ServiceState {
         SERVICE_START,
         SERVICE_START_POST,
         SERVICE_RUNNING,
-        SERVICE_RELOAD_PRE,
         SERVICE_RELOAD,
-        SERVICE_RELOAD_POST,
-        SERVICE_STOP_PRE,
-        SERVICE_STOP,
-        SERVICE_SIGTERM,
-        SERVICE_SIGKILL,
+        SERVICE_STOP,              /* No STOP_PRE state, instead just register multiple STOP executables */
+        SERVICE_STOP_SIGTERM,
+        SERVICE_STOP_SIGKILL,
         SERVICE_STOP_POST,
+        SERVICE_FINAL_SIGTERM,     /* In case the STOP_POST executable hangs, we shoot that down, too */
+        SERVICE_FINAL_SIGKILL,
         SERVICE_MAINTAINANCE,
+        SERVICE_AUTO_RESTART,
         _SERVICE_STATE_MAX,
 } ServiceState;
 
-typedef enum ServiceMode {
+typedef enum ServiceRestart {
         SERVICE_ONCE,
-        SERVICE_RESTART
-} ServiceMode;
+        SERVICE_RESTART_ON_SUCCESS,
+        SERVICE_RESTART_ALWAYS
+} ServiceRestart;
+
+typedef enum ServiceType {
+        SERVICE_FORKING,
+        SERVICE_SIMPLE
+} ServiceType;
 
 typedef enum ServiceExecCommand {
         SERVICE_EXEC_START_PRE,
         SERVICE_EXEC_START,
         SERVICE_EXEC_START_POST,
-        SERVICE_EXEC_RELOAD_PRE,
         SERVICE_EXEC_RELOAD,
-        SERVICE_EXEC_RELOAD_POST,
-        SERVICE_EXEC_STOP_PRE,
         SERVICE_EXEC_STOP,
         SERVICE_EXEC_STOP_POST,
         _SERVICE_EXEC_MAX
@@ -48,16 +49,28 @@ typedef enum ServiceExecCommand {
 struct Service {
         Meta meta;
 
-        ServiceState state;
-        ServiceMode mode;
+        ServiceType type;
+        ServiceRestart restart;
+
+        /* If set we'll read the main daemon PID from this file */
+        char *pid_file;
+
+        usec_t restart_usec;
+        usec_t timeout_usec;
 
         ExecCommand* exec_command[_SERVICE_EXEC_MAX];
         ExecContext exec_context;
 
-        pid_t service_pid, control_pid;
+        ServiceState state;
+
+        ExecStatus main_exec_status;
+
+        ExecCommand *control_command;
+        pid_t main_pid, control_pid;
+        bool main_pid_known:1;
 
-        Socket *socket;
-        Timer *timer;
+        bool failure:1; /* if we shut down, remember why */
+        int timer_id;
 };
 
 const NameVTable service_vtable;
diff --git a/set.c b/set.c
index 5e23c20..89fd81f 100644 (file)
--- a/set.c
+++ b/set.c
@@ -22,6 +22,10 @@ void set_free(Set* s) {
         hashmap_free(MAKE_HASHMAP(s));
 }
 
+int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func) {
+        return hashmap_ensure_allocated((Hashmap**) s, hash_func, compare_func);
+}
+
 int set_put(Set *s, void *value) {
         return hashmap_put(MAKE_HASHMAP(s), value, value);
 }
@@ -46,12 +50,16 @@ bool set_isempty(Set *s) {
         return hashmap_isempty(MAKE_HASHMAP(s));
 }
 
-void *set_iterate(Set *s, void **state) {
-        return hashmap_iterate(MAKE_HASHMAP(s), state, NULL);
+void *set_iterate(Set *s, Iterator *i) {
+        return hashmap_iterate(MAKE_HASHMAP(s), i, NULL);
+}
+
+void *set_iterate_backwards(Set *s, Iterator *i) {
+        return hashmap_iterate_backwards(MAKE_HASHMAP(s), i, NULL);
 }
 
-void *set_iterate_backwards(Set *s, void **state) {
-        return hashmap_iterate_backwards(MAKE_HASHMAP(s), state, NULL);
+void *set_iterate_skip(Set *s, void *value, Iterator *i) {
+        return hashmap_iterate_skip(MAKE_HASHMAP(s), value, i);
 }
 
 void *set_steal_first(Set *s) {
diff --git a/set.h b/set.h
index d8ef7aa..a16cbed 100644 (file)
--- a/set.h
+++ b/set.h
@@ -14,8 +14,9 @@
 typedef struct Set Set;
 
 Set *set_new(hash_func_t hash_func, compare_func_t compare_func);
-Set* set_copy(Set *s);
 void set_free(Set* s);
+Set* set_copy(Set *s);
+int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func);
 
 int set_put(Set *s, void *value);
 int set_replace(Set *s, void *value);
@@ -27,18 +28,19 @@ int set_merge(Set *s, Set *other);
 unsigned set_size(Set *s);
 bool set_isempty(Set *s);
 
-void *set_iterate(Set *s, void **state);
-void *set_iterate_backwards(Set *s, void **state);
+void *set_iterate(Set *s, Iterator *i);
+void *set_iterate_backwards(Set *s, Iterator *i);
+void *set_iterate_skip(Set *s, void *value, Iterator *i);
 
 void set_clear(Set *s);
 void *set_steal_first(Set *s);
 void* set_first(Set *s);
 void* set_last(Set *s);
 
-#define SET_FOREACH(e, s, state) \
-        for ((state) = NULL, (e) = set_iterate((s), &(state)); (e); (e) = set_iterate((s), &(state)))
+#define SET_FOREACH(e, s, i) \
+        for ((i) = ITERATOR_FIRST, (e) = set_iterate((s), &(i)); (e); (e) = set_iterate((s), &(i)))
 
-#define SET_FOREACH_BACKWARDS(e, s, state) \
-        for ((state) = NULL, (e) = set_iterate_backwards((s), &(state)); (e); (e) = set_iterate_backwards((s), &(state)))
+#define SET_FOREACH_BACKWARDS(e, s, i) \
+        for ((i) = ITERATOR_LAST, (e) = set_iterate_backwards((s), &(i)); (e); (e) = set_iterate_backwards((s), &(i)))
 
 #endif
index fc7bb5f..8fd819e 100644 (file)
@@ -3,11 +3,7 @@
 #include "name.h"
 #include "snapshot.h"
 
-static NameActiveState snapshot_active_state(Name *n) {
-        return SNAPSHOT(n)->state == SNAPSHOT_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
-}
-
-static void snapshot_free_hook(Name *n) {
+static void snapshot_done(Name *n) {
         Snapshot *s = SNAPSHOT(n);
 
         assert(s);
@@ -15,17 +11,14 @@ static void snapshot_free_hook(Name *n) {
         /* Nothing here for now */
 }
 
+static NameActiveState snapshot_active_state(Name *n) {
+        return SNAPSHOT(n)->state == SNAPSHOT_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
+}
+
 const NameVTable snapshot_vtable = {
         .suffix = ".snapshot",
 
-        .load = NULL,
-        .dump = NULL,
-
-        .start = NULL,
-        .stop = NULL,
-        .reload = NULL,
-
-        .active_state = snapshot_active_state,
+        .done = snapshot_done,
 
-        .free_hook = snapshot_free_hook
+        .active_state = snapshot_active_state
 };
index ac0ea12..a8a1914 100644 (file)
--- a/socket.c
+++ b/socket.c
@@ -6,6 +6,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <sys/poll.h>
+#include <signal.h>
 
 #include "name.h"
 #include "socket.h"
@@ -18,17 +19,75 @@ static const NameActiveState state_table[_SOCKET_STATE_MAX] = {
         [SOCKET_LISTENING] = NAME_ACTIVE,
         [SOCKET_RUNNING] = NAME_ACTIVE,
         [SOCKET_STOP_PRE] = NAME_DEACTIVATING,
+        [SOCKET_STOP_PRE_SIGTERM] = NAME_DEACTIVATING,
+        [SOCKET_STOP_PRE_SIGKILL] = NAME_DEACTIVATING,
         [SOCKET_STOP_POST] = NAME_DEACTIVATING,
+        [SOCKET_STOP_POST_SIGTERM] = NAME_DEACTIVATING,
+        [SOCKET_STOP_POST_SIGKILL] = NAME_DEACTIVATING,
         [SOCKET_MAINTAINANCE] = NAME_INACTIVE,
 };
 
-static int socket_load(Name *n) {
+static int socket_init(Name *n) {
         Socket *s = SOCKET(n);
+        char *t;
+        int r;
+
+        /* First, reset everything to the defaults, in case this is a
+         * reload */
 
-        exec_context_defaults(&s->exec_context);
+        s->bind_ipv6_only = false;
         s->backlog = SOMAXCONN;
+        s->timeout_usec = DEFAULT_TIMEOUT_USEC;
+        exec_context_init(&s->exec_context);
+
+        if ((r = name_load_fragment_and_dropin(n)) < 0)
+                return r;
+
+        if (!(t = name_change_suffix(name_id(n), ".service")))
+                return -ENOMEM;
+
+        r = manager_load_name(n->meta.manager, t, (Name**) &s->service);
+        free(t);
 
-        return name_load_fragment_and_dropin(n);
+        if (r < 0)
+                return r;
+
+        if ((r = set_ensure_allocated(n->meta.dependencies + NAME_BEFORE, trivial_hash_func, trivial_compare_func)) < 0)
+                return r;
+
+        if ((r = set_put(n->meta.dependencies[NAME_BEFORE], s->service)) < 0)
+                return r;
+
+        return 0;
+}
+
+static void socket_done(Name *n) {
+        Socket *s = SOCKET(n);
+        SocketPort *p;
+
+        assert(s);
+
+        while ((p = s->ports)) {
+                LIST_REMOVE(SocketPort, port, s->ports, p);
+
+                if (p->fd >= 0)
+                        close_nointr(p->fd);
+                free(p->path);
+                free(p);
+        }
+
+        exec_context_done(&s->exec_context);
+        exec_command_free_array(s->exec_command, _SOCKET_EXEC_MAX);
+        s->control_command = NULL;
+
+        if (s->control_pid > 0) {
+                name_unwatch_pid(n, s->control_pid);
+                s->control_pid = 0;
+        }
+
+        s->service = NULL;
+
+        name_unwatch_timer(n, &s->timer_id);
 }
 
 static const char* listen_lookup(int type) {
@@ -40,7 +99,7 @@ static const char* listen_lookup(int type) {
         else if (type == SOCK_SEQPACKET)
                 return "ListenSequentialPacket";
 
-        assert_not_reached("Unkown socket type");
+        assert_not_reached("Unknown socket type");
         return NULL;
 }
 
@@ -53,7 +112,11 @@ static void socket_dump(Name *n, FILE *f, const char *prefix) {
                 [SOCKET_LISTENING] = "listening",
                 [SOCKET_RUNNING] = "running",
                 [SOCKET_STOP_PRE] = "stop-pre",
+                [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm",
+                [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill",
                 [SOCKET_STOP_POST] = "stop-post",
+                [SOCKET_STOP_POST_SIGTERM] = "stop-post-sigterm",
+                [SOCKET_STOP_POST_SIGKILL] = "stop-post-sigkill",
                 [SOCKET_MAINTAINANCE] = "maintainance"
         };
 
@@ -78,7 +141,7 @@ static void socket_dump(Name *n, FILE *f, const char *prefix) {
                 prefix, yes_no(s->bind_ipv6_only),
                 prefix, s->backlog);
 
-        LIST_FOREACH(p, s->ports) {
+        LIST_FOREACH(port, p, s->ports) {
 
                 if (p->type == SOCKET_SOCKET) {
                         const char *t;
@@ -101,27 +164,17 @@ static void socket_dump(Name *n, FILE *f, const char *prefix) {
         for (c = 0; c < _SOCKET_EXEC_MAX; c++) {
                 ExecCommand *i;
 
-                LIST_FOREACH(i, s->exec_command[c])
+                LIST_FOREACH(command, i, s->exec_command[c])
                         fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path);
         }
 }
 
-static void socket_set_state(Socket *s, SocketState state) {
-        SocketState old_state;
-        assert(s);
-
-        old_state = s->state;
-        s->state = state;
-
-        name_notify(NAME(s), state_table[old_state], state_table[s->state]);
-}
-
-static void close_fds(Socket *s) {
+static void socket_close_fds(Socket *s) {
         SocketPort *p;
 
         assert(s);
 
-        LIST_FOREACH(p, s->ports) {
+        LIST_FOREACH(port, p, s->ports) {
                 if (p->fd < 0)
                         continue;
 
@@ -132,30 +185,16 @@ static void close_fds(Socket *s) {
         }
 }
 
-static int socket_start(Name *n) {
-        Socket *s = SOCKET(n);
+static int socket_open_fds(Socket *s) {
         SocketPort *p;
         int r;
 
         assert(s);
 
-        if (s->state == SOCKET_START_PRE ||
-            s->state == SOCKET_START_POST)
-                return 0;
-
-        if (s->state == SOCKET_LISTENING ||
-            s->state == SOCKET_RUNNING)
-                return -EALREADY;
-
-        if (s->state == SOCKET_STOP_PRE ||
-            s->state == SOCKET_STOP_POST)
-                return -EAGAIN;
-
-        assert(s->state == SOCKET_DEAD || s->state == SOCKET_MAINTAINANCE);
-
-        LIST_FOREACH(p, s->ports) {
+        LIST_FOREACH(port, p, s->ports) {
 
-                assert(p->fd < 0);
+                if (p->fd >= 0)
+                        continue;
 
                 if (p->type == SOCKET_SOCKET) {
 
@@ -188,46 +227,352 @@ static int socket_start(Name *n) {
                                 goto rollback;
                         }
                 }
+        }
+
+        return 0;
+
+rollback:
+        socket_close_fds(s);
+        return r;
+}
+
+static void socket_unwatch_fds(Socket *s) {
+        SocketPort *p;
 
-                if ((r = name_watch_fd(n, p->fd, POLLIN)) < 0)
-                        goto rollback;
+        assert(s);
+
+        LIST_FOREACH(port, p, s->ports) {
+                if (p->fd < 0)
+                        continue;
+
+                name_unwatch_fd(NAME(s), p->fd);
         }
+}
+
+static int socket_watch_fds(Socket *s) {
+        SocketPort *p;
+        int r;
+
+        assert(s);
 
-        socket_set_state(s, SOCKET_LISTENING);
+        LIST_FOREACH(port, p, s->ports) {
+                if (p->fd < 0)
+                        continue;
+
+                if ((r = name_watch_fd(NAME(s), p->fd, POLLIN)) < 0)
+                        goto fail;
+        }
 
         return 0;
 
-rollback:
-        close_fds(s);
+fail:
+        socket_unwatch_fds(s);
+        return r;
+}
+
+static void socket_set_state(Socket *s, SocketState state) {
+        SocketState old_state;
+        assert(s);
+
+        old_state = s->state;
+        s->state = state;
+
+        if (state != SOCKET_START_PRE &&
+            state != SOCKET_START_POST &&
+            state != SOCKET_STOP_PRE &&
+            state != SOCKET_STOP_PRE_SIGTERM &&
+            state != SOCKET_STOP_PRE_SIGKILL &&
+            state != SOCKET_STOP_POST &&
+            state != SOCKET_STOP_POST_SIGTERM &&
+            state != SOCKET_STOP_POST_SIGKILL)
+                name_unwatch_timer(NAME(s), &s->timer_id);
+
+        if (state != SOCKET_START_PRE &&
+            state != SOCKET_START_POST &&
+            state != SOCKET_STOP_PRE &&
+            state != SOCKET_STOP_PRE_SIGTERM &&
+            state != SOCKET_STOP_PRE_SIGKILL &&
+            state != SOCKET_STOP_POST &&
+            state != SOCKET_STOP_POST_SIGTERM &&
+            state != SOCKET_STOP_POST_SIGKILL)
+                if (s->control_pid >= 0) {
+                        name_unwatch_pid(NAME(s), s->control_pid);
+                        s->control_pid = 0;
+                }
+
+        if (state != SOCKET_START_PRE &&
+            state != SOCKET_START_POST &&
+            state != SOCKET_STOP_PRE &&
+            state != SOCKET_STOP_POST)
+                s->control_command = NULL;
+
+        if (state != SOCKET_START_POST &&
+            state != SOCKET_LISTENING &&
+            state != SOCKET_RUNNING &&
+            state != SOCKET_STOP_PRE &&
+            state != SOCKET_STOP_PRE_SIGTERM &&
+            state != SOCKET_STOP_PRE_SIGKILL)
+                socket_close_fds(s);
+
+        if (state != SOCKET_LISTENING)
+                socket_unwatch_fds(s);
+
+        name_notify(NAME(s), state_table[old_state], state_table[s->state]);
+}
+
+static int socket_spawn(Socket *s, ExecCommand *c, bool timeout, pid_t *_pid) {
+        pid_t pid;
+        int r;
+
+        assert(s);
+        assert(c);
+        assert(_pid);
+
+        if (timeout) {
+                if ((r = name_watch_timer(NAME(s), s->timeout_usec, &s->timer_id)) < 0)
+                        goto fail;
+        } else
+                name_unwatch_timer(NAME(s), &s->timer_id);
+
+        if ((r = exec_spawn(c, &s->exec_context, NULL, 0, &pid)) < 0)
+                goto fail;
+
+        if ((r = name_watch_pid(NAME(s), pid)) < 0)
+                /* FIXME: we need to do something here */
+                goto fail;
 
-        socket_set_state(s, SOCKET_MAINTAINANCE);
+        *_pid = pid;
+
+        return 0;
+
+fail:
+        if (timeout)
+                name_unwatch_timer(NAME(s), &s->timer_id);
 
         return r;
 }
 
-static int socket_stop(Name *n) {
+static void socket_enter_dead(Socket *s, bool success) {
+        assert(s);
+
+        if (!success)
+                s->failure = true;
+
+        socket_set_state(s, s->failure ? SOCKET_MAINTAINANCE : SOCKET_DEAD);
+}
+
+static void socket_enter_stop_post(Socket *s, bool success) {
+        int r;
+        assert(s);
+
+        if (!success)
+                s->failure = true;
+
+        if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST])) {
+
+                if ((r = socket_spawn(s, s->control_command, true, &s->control_pid)) < 0)
+                        goto fail;
+
+                socket_set_state(s, SOCKET_STOP_POST);
+        } else
+                socket_enter_dead(s, true);
+
+        return;
+
+fail:
+        log_warning("%s failed to run stop-post executable: %s", name_id(NAME(s)), strerror(-r));
+        socket_enter_dead(s, false);
+}
+
+static void socket_enter_signal(Socket *s, SocketState state, bool success) {
+        int r;
+
+        assert(s);
+
+        if (!success)
+                s->failure = true;
+
+        if (s->control_pid > 0) {
+                int sig;
+
+                sig = (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_POST_SIGTERM) ? SIGTERM : SIGKILL;
+
+                if (kill(s->control_pid, sig) < 0 && errno != ESRCH) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                socket_set_state(s, state);
+        } else
+                socket_enter_dead(s, true);
+
+        return;
+
+fail:
+        log_warning("%s failed to kill processes: %s", name_id(NAME(s)), strerror(-r));
+
+        if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL)
+                socket_enter_stop_post(s, false);
+        else
+                socket_enter_dead(s, false);
+}
+
+static void socket_enter_stop_pre(Socket *s, bool success) {
+        int r;
+        assert(s);
+
+        if (!success)
+                s->failure = true;
+
+        if ((s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE])) {
+
+                if ((r = socket_spawn(s, s->control_command, true, &s->control_pid)) < 0)
+                        goto fail;
+
+                socket_set_state(s, SOCKET_STOP_PRE);
+        } else
+                socket_enter_stop_post(s, true);
+
+        return;
+
+fail:
+        log_warning("%s failed to run stop-pre executable: %s", name_id(NAME(s)), strerror(-r));
+        socket_enter_stop_post(s, false);
+}
+
+static void socket_enter_start_post(Socket *s) {
+        int r;
+        assert(s);
+
+        if ((r = socket_open_fds(s)) < 0 ||
+            (r = socket_watch_fds(s)) < 0) {
+                log_warning("%s failed to listen on sockets: %s", name_id(NAME(s)), strerror(-r));
+                goto fail;
+        }
+
+        if ((s->control_command = s->exec_command[SOCKET_EXEC_START_POST])) {
+
+                if ((r = socket_spawn(s, s->control_command, true, &s->control_pid)) < 0) {
+                        log_warning("%s failed to run start-post executable: %s", name_id(NAME(s)), strerror(-r));
+                        goto fail;
+                }
+
+                socket_set_state(s, SOCKET_START_POST);
+        } else
+                socket_set_state(s, SOCKET_LISTENING);
+
+        return;
+
+fail:
+        socket_enter_stop_pre(s, false);
+}
+
+static void socket_enter_start_pre(Socket *s) {
+        int r;
+        assert(s);
+
+        if ((s->control_command = s->exec_command[SOCKET_EXEC_START_PRE])) {
+
+                if ((r = socket_spawn(s, s->control_command, true, &s->control_pid)) < 0)
+                        goto fail;
+
+                socket_set_state(s, SOCKET_START_PRE);
+        } else
+                socket_enter_start_post(s);
+
+        return;
+
+fail:
+        log_warning("%s failed to run start-pre exectuable: %s", name_id(NAME(s)), strerror(-r));
+        socket_enter_dead(s, false);
+}
+
+static void socket_enter_running(Socket *s) {
+        int r;
+
+        assert(s);
+
+        if ((r = manager_add_job(NAME(s)->meta.manager, JOB_START, NAME(s->service), JOB_REPLACE, true, NULL)) < 0)
+                goto fail;
+
+        socket_set_state(s, SOCKET_RUNNING);
+        return;
+
+fail:
+        log_warning("%s failed to queue socket startup job: %s", name_id(NAME(s)), strerror(-r));
+        socket_enter_dead(s, false);
+}
+
+static void socket_run_next(Socket *s, bool success) {
+        int r;
+
+        assert(s);
+        assert(s->control_command);
+        assert(s->control_command->command_next);
+
+        if (!success)
+                s->failure = true;
+
+        s->control_command = s->control_command->command_next;
+
+        if ((r = socket_spawn(s, s->control_command, true, &s->control_pid)) < 0)
+                goto fail;
+
+        return;
+
+fail:
+        if (s->state == SOCKET_STOP_PRE)
+                socket_enter_stop_post(s, false);
+        else if (s->state == SOCKET_STOP_POST)
+                socket_enter_dead(s, false);
+        else
+                socket_enter_stop_pre(s, false);
+}
+
+static int socket_start(Name *n) {
         Socket *s = SOCKET(n);
 
         assert(s);
 
+        /* We cannot fulfill this request right now, try again later
+         * please! */
+        if (s->state == SOCKET_STOP_PRE ||
+            s->state == SOCKET_STOP_PRE_SIGKILL ||
+            s->state == SOCKET_STOP_PRE_SIGTERM ||
+            s->state == SOCKET_STOP_POST ||
+            s->state == SOCKET_STOP_POST_SIGTERM ||
+            s->state == SOCKET_STOP_POST_SIGKILL)
+                return -EAGAIN;
+
         if (s->state == SOCKET_START_PRE ||
             s->state == SOCKET_START_POST)
-                return -EAGAIN;
+                return 0;
 
-        if (s->state == SOCKET_DEAD ||
-            s->state == SOCKET_MAINTAINANCE)
-                return -EALREADY;
+        /* Cannot run this without the service being around */
+        if (s->service->meta.load_state != NAME_LOADED)
+                return -ENOENT;
 
-        if (s->state == SOCKET_STOP_PRE ||
-            s->state == SOCKET_STOP_POST)
-                return 0;
+        assert(s->state == SOCKET_DEAD || s->state == SOCKET_MAINTAINANCE);
 
-        assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING);
+        s->failure = false;
+        socket_enter_start_pre(s);
+        return 0;
+}
 
-        close_fds(s);
+static int socket_stop(Name *n) {
+        Socket *s = SOCKET(n);
+
+        assert(s);
+
+        /* We cannot fulfill this request right now, try again later
+         * please! */
+        if (s->state == SOCKET_START_PRE ||
+            s->state == SOCKET_START_POST)
+                return -EAGAIN;
 
-        socket_set_state(s, SOCKET_DEAD);
+        assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING);
 
+        socket_enter_stop_pre(s, true);
         return 0;
 }
 
@@ -240,58 +585,140 @@ static NameActiveState socket_active_state(Name *n) {
 static void socket_fd_event(Name *n, int fd, uint32_t events) {
         Socket *s = SOCKET(n);
 
-        assert(n);
-
-        if (events != POLLIN)
-                goto fail;
+        assert(s);
 
-        log_info("POLLIN on %s", name_id(n));
+        log_info("Incoming traffic on %s", name_id(n));
 
-        return;
+        if (events != POLLIN)
+                socket_enter_stop_pre(s, false);
 
-fail:
-        close_fds(s);
-        socket_set_state(s, SOCKET_MAINTAINANCE);
+        socket_enter_running(s);
 }
 
-static void socket_free_hook(Name *n) {
-        SocketExecCommand c;
+static void socket_sigchld_event(Name *n, pid_t pid, int code, int status) {
         Socket *s = SOCKET(n);
-        SocketPort *p;
+        bool success;
 
         assert(s);
+        assert(pid >= 0);
 
-        while ((p = s->ports)) {
-                LIST_REMOVE(SocketPort, s->ports, p);
+        success = code == CLD_EXITED || status == 0;
+        s->failure = s->failure || !success;
 
-                if (p->fd >= 0)
-                        close_nointr(p->fd);
-                free(p->path);
-                free(p);
-        }
+        assert(s->control_pid == pid);
+        assert(s->control_command);
+
+        exec_status_fill(&s->control_command->exec_status, pid, code, status);
+        s->control_pid = 0;
+
+        log_debug("%s: control process exited, code=%s status=%i", name_id(n), sigchld_code(code), status);
+
+        if (s->control_command->command_next &&
+            (success || (s->state == SOCKET_EXEC_STOP_PRE || s->state == SOCKET_EXEC_STOP_POST)))
+                socket_run_next(s, success);
+        else {
+                /* No further commands for this step, so let's figure
+                 * out what to do next */
 
-        exec_context_free(&s->exec_context);
+                switch (s->state) {
+
+                case SOCKET_START_PRE:
+                        if (success)
+                                socket_enter_start_pre(s);
+                        else
+                                socket_enter_stop_pre(s, false);
+                        break;
+
+                case SOCKET_START_POST:
+                        if (success)
+                                socket_set_state(s, SOCKET_LISTENING);
+                        else
+                                socket_enter_stop_pre(s, false);
+                        break;
+
+                case SOCKET_STOP_PRE:
+                case SOCKET_STOP_PRE_SIGTERM:
+                case SOCKET_STOP_PRE_SIGKILL:
+                        socket_enter_stop_post(s, success);
+                        break;
+
+                case SOCKET_STOP_POST:
+                case SOCKET_STOP_POST_SIGTERM:
+                case SOCKET_STOP_POST_SIGKILL:
+                        socket_enter_dead(s, success);
+                        break;
+
+                default:
+                        assert_not_reached("Uh, control process died at wrong time.");
+                }
+        }
+}
 
-        for (c = 0; c < _SOCKET_EXEC_MAX; c++)
-                exec_command_free_list(s->exec_command[c]);
+static void socket_timer_event(Name *n, int id, uint64_t elapsed) {
+        Socket *s = SOCKET(n);
 
-        if (s->service)
-                s->service->socket = NULL;
+        assert(s);
+        assert(elapsed == 1);
+
+        assert(s->timer_id == id);
+
+        switch (s->state) {
+
+        case SOCKET_START_PRE:
+        case SOCKET_START_POST:
+                log_warning("%s operation timed out. Stopping.", name_id(n));
+                socket_enter_stop_pre(s, false);
+                break;
+
+        case SOCKET_STOP_PRE:
+                log_warning("%s stopping timed out. Terminating.", name_id(n));
+                socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, false);
+                break;
+
+        case SOCKET_STOP_PRE_SIGTERM:
+                log_warning("%s stopping timed out. Killing.", name_id(n));
+                socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, false);
+                break;
+
+        case SOCKET_STOP_PRE_SIGKILL:
+                log_warning("%s still around after SIGKILL. Ignoring.", name_id(n));
+                socket_enter_stop_post(s, false);
+                break;
+
+        case SOCKET_STOP_POST:
+                log_warning("%s stopping timed out (2). Terminating.", name_id(n));
+                socket_enter_signal(s, SOCKET_STOP_POST_SIGTERM, false);
+                break;
+
+        case SOCKET_STOP_POST_SIGTERM:
+                log_warning("%s stopping timed out (2). Killing.", name_id(n));
+                socket_enter_signal(s, SOCKET_STOP_POST_SIGKILL, false);
+                break;
+
+        case SOCKET_STOP_POST_SIGKILL:
+                log_warning("%s still around after SIGKILL (2). Entering maintainance mode.", name_id(n));
+                socket_enter_dead(s, false);
+                break;
+
+        default:
+                assert_not_reached("Timeout at wrong time.");
+        }
 }
 
 const NameVTable socket_vtable = {
         .suffix = ".socket",
 
-        .load = socket_load,
+        .init = socket_init,
+        .done = socket_done,
+
         .dump = socket_dump,
 
         .start = socket_start,
         .stop = socket_stop,
-        .reload = NULL,
 
         .active_state = socket_active_state,
 
         .fd_event = socket_fd_event,
-
-        .free_hook = socket_free_hook
+        .sigchld_event = socket_sigchld_event,
+        .timer_event = socket_timer_event
 };
index db5cd21..a8821f6 100644 (file)
--- a/socket.h
+++ b/socket.h
@@ -15,7 +15,11 @@ typedef enum SocketState {
         SOCKET_LISTENING,
         SOCKET_RUNNING,
         SOCKET_STOP_PRE,
+        SOCKET_STOP_PRE_SIGTERM,
+        SOCKET_STOP_PRE_SIGKILL,
         SOCKET_STOP_POST,
+        SOCKET_STOP_POST_SIGTERM,
+        SOCKET_STOP_POST_SIGKILL,
         SOCKET_MAINTAINANCE,
         _SOCKET_STATE_MAX
 } SocketState;
@@ -43,26 +47,32 @@ struct SocketPort {
 
         int fd;
 
-        LIST_FIELDS(SocketPort);
+        LIST_FIELDS(SocketPort, port);
 };
 
 struct Socket {
         Meta meta;
 
-        SocketState state;
-
         LIST_HEAD(SocketPort, ports);
 
         /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */
         bool bind_ipv6_only;
         unsigned backlog;
 
+        usec_t timeout_usec;
+
         ExecCommand* exec_command[_SOCKET_EXEC_MAX];
         ExecContext exec_context;
 
+        Service *service;
+
+        SocketState state;
+
+        ExecCommand* control_command;
         pid_t control_pid;
 
-        Service *service;
+        bool failure;
+        int timer_id;
 };
 
 extern const NameVTable socket_vtable;
diff --git a/strv.c b/strv.c
index ecad6d5..faa878c 100644 (file)
--- a/strv.c
+++ b/strv.c
@@ -115,3 +115,33 @@ fail:
         free(a);
         return NULL;
 }
+
+char **strv_merge(char **a, char **b) {
+        char **r, **k;
+
+        if (!a)
+                return strv_copy(b);
+
+        if (!b)
+                return strv_copy(a);
+
+        if (!(r = new(char*, strv_length(a)+strv_length(b)+1)))
+                return NULL;
+
+        for (k = r; *a; k++, a++)
+                if (!(*k = strdup(*a)))
+                        goto fail;
+        for (; *b; k++, b++)
+                if (!(*k = strdup(*b)))
+                        goto fail;
+
+        *k = NULL;
+        return r;
+
+fail:
+        for (k--; k >= r; k--)
+                free(*k);
+
+        return NULL;
+
+}
diff --git a/strv.h b/strv.h
index 43a5c59..9504ccc 100644 (file)
--- a/strv.h
+++ b/strv.h
@@ -10,6 +10,8 @@ void strv_free(char **l);
 char **strv_copy(char **l);
 unsigned strv_length(char **l);
 
+char **strv_merge(char **a, char **b);
+
 char **strv_new(const char *x, ...) __sentinel;
 
 #define STRV_FOREACH(s, l)                      \
diff --git a/timer.c b/timer.c
index b1571ce..0c89136 100644 (file)
--- a/timer.c
+++ b/timer.c
@@ -3,6 +3,12 @@
 #include "name.h"
 #include "timer.h"
 
+static void timer_done(Name *n) {
+        Timer *t = TIMER(n);
+
+        assert(t);
+}
+
 static NameActiveState timer_active_state(Name *n) {
 
         static const NameActiveState table[_TIMER_STATE_MAX] = {
@@ -14,26 +20,11 @@ static NameActiveState timer_active_state(Name *n) {
         return table[TIMER(n)->state];
 }
 
-static void timer_free_hook(Name *n) {
-        Timer *t = TIMER(n);
-
-        assert(t);
-
-        if (t->service)
-                t->service->timer = NULL;
-}
-
 const NameVTable timer_vtable = {
         .suffix = ".timer",
 
-        .load = name_load_fragment_and_dropin,
-        .dump = NULL,
-
-        .start = NULL,
-        .stop = NULL,
-        .reload = NULL,
-
-        .active_state = timer_active_state,
+        .init = name_load_fragment_and_dropin,
+        .done = timer_done,
 
-        .free_hook = timer_free_hook
+        .active_state = timer_active_state
 };
diff --git a/util.c b/util.c
index f045583..2e310f9 100644 (file)
--- a/util.c
+++ b/util.c
@@ -5,6 +5,8 @@
 #include <unistd.h>
 #include <errno.h>
 #include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
 
 #include "macro.h"
 #include "util.h"
@@ -108,7 +110,7 @@ int parse_boolean(const char *v) {
 
 int safe_atou(const char *s, unsigned *ret_u) {
         char *x = NULL;
-        unsigned l;
+        unsigned long l;
 
         assert(s);
         assert(ret_u);
@@ -119,7 +121,7 @@ int safe_atou(const char *s, unsigned *ret_u) {
         if (!x || *x || errno)
                 return errno ? -errno : -EINVAL;
 
-        if ((unsigned) l != l)
+        if ((unsigned long) (unsigned) l != l)
                 return -ERANGE;
 
         *ret_u = (unsigned) l;
@@ -128,7 +130,7 @@ int safe_atou(const char *s, unsigned *ret_u) {
 
 int safe_atoi(const char *s, int *ret_i) {
         char *x = NULL;
-        int l;
+        long l;
 
         assert(s);
         assert(ret_i);
@@ -139,10 +141,78 @@ int safe_atoi(const char *s, int *ret_i) {
         if (!x || *x || errno)
                 return errno ? -errno : -EINVAL;
 
-        if ((int) l != l)
+        if ((long) (int) l != l)
                 return -ERANGE;
 
-        *ret_i = (unsigned) l;
+        *ret_i = (int) l;
+        return 0;
+}
+
+int safe_atolu(const char *s, long unsigned *ret_lu) {
+        char *x = NULL;
+        unsigned long l;
+
+        assert(s);
+        assert(ret_lu);
+
+        errno = 0;
+        l = strtoul(s, &x, 0);
+
+        if (!x || *x || errno)
+                return errno ? -errno : -EINVAL;
+
+        *ret_lu = l;
+        return 0;
+}
+
+int safe_atoli(const char *s, long int *ret_li) {
+        char *x = NULL;
+        long l;
+
+        assert(s);
+        assert(ret_li);
+
+        errno = 0;
+        l = strtol(s, &x, 0);
+
+        if (!x || *x || errno)
+                return errno ? -errno : -EINVAL;
+
+        *ret_li = l;
+        return 0;
+}
+
+int safe_atollu(const char *s, long long unsigned *ret_llu) {
+        char *x = NULL;
+        unsigned long long l;
+
+        assert(s);
+        assert(ret_llu);
+
+        errno = 0;
+        l = strtoull(s, &x, 0);
+
+        if (!x || *x || errno)
+                return errno ? -errno : -EINVAL;
+
+        *ret_llu = l;
+        return 0;
+}
+
+int safe_atolli(const char *s, long long int *ret_lli) {
+        char *x = NULL;
+        long long l;
+
+        assert(s);
+        assert(ret_lli);
+
+        errno = 0;
+        l = strtoll(s, &x, 0);
+
+        if (!x || *x || errno)
+                return errno ? -errno : -EINVAL;
+
+        *ret_lli = l;
         return 0;
 }
 
@@ -164,3 +234,151 @@ char *split_spaces(const char *c, size_t *l, char **state) {
 
         return (char*) current;
 }
+
+/* Split a string into words, but consider strings enclosed in '' and
+ * "" as words even if they include spaces. */
+char *split_quoted(const char *c, size_t *l, char **state) {
+        char *current;
+
+        current = *state ? *state : (char*) c;
+
+        if (!*current || *c == 0)
+                return NULL;
+
+        current += strspn(current, WHITESPACE);
+
+        if (*current == '\'') {
+                current ++;
+                *l = strcspn(current, "'");
+                *state = current+*l;
+
+                if (**state == '\'')
+                        (*state)++;
+        } else if (*current == '\"') {
+                current ++;
+                *l = strcspn(current+1, "\"");
+                *state = current+*l;
+
+                if (**state == '\"')
+                        (*state)++;
+        } else {
+                *l = strcspn(current, WHITESPACE);
+                *state = current+*l;
+        }
+
+        return (char*) current;
+}
+
+const char *sigchld_code(int code) {
+
+        if (code == CLD_EXITED)
+                return "exited";
+        else if (code == CLD_KILLED)
+                return "killed";
+        else if (code == CLD_DUMPED)
+                return "dumped";
+        else if (code == CLD_TRAPPED)
+                return "trapped";
+        else if (code == CLD_STOPPED)
+                return "stopped";
+        else if (code == CLD_CONTINUED)
+                return "continued";
+
+        return "unknown";
+}
+
+int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
+        int r;
+        FILE *f;
+        char fn[132], line[256], *p;
+        long long unsigned ppid;
+
+        assert(pid >= 0);
+        assert(_ppid);
+
+        assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%llu/stat", (unsigned long long) pid) < (int) (sizeof(fn)-1));
+        fn[sizeof(fn)-1] = 0;
+
+        if (!(f = fopen(fn, "r")))
+                return -errno;
+
+        if (!(fgets(line, sizeof(line), f))) {
+                r = -errno;
+                fclose(f);
+                return r;
+        }
+
+        fclose(f);
+
+        /* Let's skip the pid and comm fields. The latter is enclosed
+         * in () but does not escape any () in its value, so let's
+         * skip over it manually */
+
+        if (!(p = strrchr(line, ')')))
+                return -EIO;
+
+        p++;
+
+        if (sscanf(p, " "
+                   "%*c "  /* state */
+                   "%llu ", /* ppid */
+                   &ppid) != 1)
+                return -EIO;
+
+        if ((long long unsigned) (pid_t) ppid != ppid)
+                return -ERANGE;
+
+        *_ppid = (pid_t) ppid;
+
+        return 0;
+}
+
+int write_one_line_file(const char *fn, const char *line) {
+        FILE *f;
+        int r;
+
+        assert(fn);
+        assert(line);
+
+        if (!(f = fopen(fn, "we")))
+                return -errno;
+
+        if (fputs(line, f) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        r = 0;
+finish:
+        fclose(f);
+        return r;
+}
+
+int read_one_line_file(const char *fn, char **line) {
+        FILE *f;
+        int r;
+        char t[64], *c;
+
+        assert(fn);
+        assert(line);
+
+        if (!(f = fopen(fn, "re")))
+                return -errno;
+
+        if (!(fgets(t, sizeof(t), f))) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (!(c = strdup(t))) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        *line = c;
+        r = 0;
+
+finish:
+        fclose(f);
+        return r;
+}
diff --git a/util.h b/util.h
index 6731414..86fd7bb 100644 (file)
--- a/util.h
+++ b/util.h
 
 typedef uint64_t usec_t;
 
-#define USEC_PER_SEC 1000000ULL
+#define MSEC_PER_SEC  1000ULL
+#define USEC_PER_SEC  1000000ULL
+#define USEC_PER_MSEC 1000ULL
+#define NSEC_PER_SEC  1000000000ULL
+#define NSEC_PER_MSEC 1000000ULL
 #define NSEC_PER_USEC 1000ULL
 
 usec_t now(clockid_t clock);
@@ -60,9 +64,26 @@ int parse_boolean(const char *v);
 int safe_atou(const char *s, unsigned *ret_u);
 int safe_atoi(const char *s, int *ret_i);
 
+int safe_atolu(const char *s, unsigned long *ret_u);
+int safe_atoli(const char *s, long int *ret_i);
+
+int safe_atollu(const char *s, unsigned long long *ret_u);
+int safe_atolli(const char *s, long long int *ret_i);
+
 char *split_spaces(const char *c, size_t *l, char **state);
+char *split_quoted(const char *c, size_t *l, char **state);
 
-#define FOREACH_WORD(word, length, s, state)    \
+#define FOREACH_WORD(word, length, s, state)                            \
         for ((state) = NULL, (word) = split_spaces((s), &(l), &(state)); (word); (word) = split_spaces((s), &(l), &(state)))
 
+#define FOREACH_WORD_QUOTED(word, length, s, state)                     \
+        for ((state) = NULL, (word) = split_quoted((s), &(l), &(state)); (word); (word) = split_quoted((s), &(l), &(state)))
+
+const char *sigchld_code(int code);
+
+pid_t get_parent_of_pid(pid_t pid, pid_t *ppid);
+
+int write_one_line_file(const char *fn, const char *line);
+int read_one_line_file(const char *fn, char **line);
+
 #endif