diff options
-rw-r--r-- | automount.c | 32 | ||||
-rw-r--r-- | conf-parser.c | 35 | ||||
-rw-r--r-- | conf-parser.h | 1 | ||||
-rw-r--r-- | device.c | 25 | ||||
-rw-r--r-- | execute.c | 240 | ||||
-rw-r--r-- | execute.h | 48 | ||||
-rw-r--r-- | hashmap.c | 61 | ||||
-rw-r--r-- | hashmap.h | 23 | ||||
-rw-r--r-- | job.c | 71 | ||||
-rw-r--r-- | job.h | 13 | ||||
-rw-r--r-- | list.h | 82 | ||||
-rw-r--r-- | load-fragment.c | 346 | ||||
-rw-r--r-- | macro.h | 2 | ||||
-rw-r--r-- | main.c | 2 | ||||
-rw-r--r-- | manager.c | 234 | ||||
-rw-r--r-- | manager.h | 13 | ||||
-rw-r--r-- | milestone.c | 22 | ||||
-rw-r--r-- | mount.c | 26 | ||||
-rw-r--r-- | name.c | 284 | ||||
-rw-r--r-- | name.h | 36 | ||||
-rw-r--r-- | service.c | 806 | ||||
-rw-r--r-- | service.h | 51 | ||||
-rw-r--r-- | set.c | 16 | ||||
-rw-r--r-- | set.h | 16 | ||||
-rw-r--r-- | snapshot.c | 21 | ||||
-rw-r--r-- | socket.c | 589 | ||||
-rw-r--r-- | socket.h | 18 | ||||
-rw-r--r-- | strv.c | 30 | ||||
-rw-r--r-- | strv.h | 2 | ||||
-rw-r--r-- | timer.c | 27 | ||||
-rw-r--r-- | util.c | 228 | ||||
-rw-r--r-- | util.h | 25 |
32 files changed, 2835 insertions, 590 deletions
diff --git a/automount.c b/automount.c index 84691e6e6e..b64bacbf4a 100644 --- a/automount.c +++ b/automount.c @@ -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 }; diff --git a/conf-parser.c b/conf-parser.c index 2ea6911257..2618858331 100644 --- a/conf-parser.c +++ b/conf-parser.c @@ -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; } diff --git a/conf-parser.h b/conf-parser.h index 25b77eb494..a8bed39dc5 100644 --- a/conf-parser.h +++ b/conf-parser.h @@ -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 @@ -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 }; @@ -1,41 +1,238 @@ /*-*- 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); } @@ -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 @@ -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) @@ -11,6 +11,11 @@ * 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 @@ -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; +} @@ -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); @@ -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 diff --git a/load-fragment.c b/load-fragment.c index e85c1d4cce..5570ae5913 100644 --- a/load-fragment.c +++ b/load-fragment.c @@ -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; } @@ -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 @@ -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; @@ -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; } } @@ -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 diff --git a/milestone.c b/milestone.c index ad8080aefc..fd4a6897ba 100644 --- a/milestone.c +++ b/milestone.c @@ -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 }; @@ -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 }; @@ -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"); + } +} @@ -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 @@ -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, }; @@ -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; @@ -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) { @@ -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 diff --git a/snapshot.c b/snapshot.c index fc7bb5f478..8fd819e2a9 100644 --- a/snapshot.c +++ b/snapshot.c @@ -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 }; @@ -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 }; @@ -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; @@ -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; + +} @@ -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) \ @@ -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 }; @@ -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; +} @@ -11,7 +11,11 @@ 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 |