#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)
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] = {
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);
}
}
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
};
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,
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)))
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;
fail:
for (; k > 0; k--)
free(n[k-1]);
+ free(n);
return -ENOMEM;
}
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
#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] = {
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
};
/*-*- 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);
}
}
+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);
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);
}
#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 */
};
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
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
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);
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;
return e->value;
at_end:
- *state = (void *) -1;
+ *i = ITERATOR_LAST;
if (key)
*key = NULL;
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;
return e->value;
at_beginning:
- *state = (void *) -1;
+ *i = ITERATOR_FIRST;
if (key)
*key = NULL;
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)
* 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);
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);
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
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);
* 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;
}
/* 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 ||
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) {
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;
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 ||
} 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 ||
}
/* 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;
+}
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 {
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;
/* Used for graph algs as a "I have been here" marker */
Job* marker;
unsigned generation;
+
};
Job* job_new(Manager *m, JobType type, Name *name);
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);
/* 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
#include <assert.h>
#include <errno.h>
#include <string.h>
+#include <linux/oom.h>
#include "name.h"
#include "strv.h"
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;
}
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,
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] = {
[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);
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;
}
#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
printf("- By jobs:\n");
manager_dump_jobs(m, stdout, "\t");
- manager_run_jobs(m);
-
manager_loop(m);
retval = 0;
/* 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 */
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;
}
/* 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 */
/* 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;
}
static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned generation) {
- void *state;
+ Iterator i;
Name *n;
int r;
/* 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? */
static int transaction_verify_order(Manager *m, unsigned *generation) {
Job *j;
int r;
- void *state;
+ Iterator i;
assert(m);
assert(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;
/* 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;
}
static int transaction_is_destructive(Manager *m, JobMode mode) {
- void *state;
+ Iterator i;
Job *j;
assert(m);
/* 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);
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)
}
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);
rollback:
- HASHMAP_FOREACH(j, m->transaction_jobs, state) {
+ HASHMAP_FOREACH(j, m->transaction_jobs, i) {
if (j->linked)
continue;
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) {
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;
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;
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. */
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;
}
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;
* 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;
}
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);
}
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 (;;) {
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;
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;
}
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)
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;
}
}
#include <stdio.h>
typedef struct Manager Manager;
+typedef enum ManagerEventType ManagerEventType;
#include "name.h"
#include "job.h"
#include "list.h"
#include "set.h"
+enum ManagerEventType {
+ MANAGER_SIGNAL,
+ MANAGER_FD,
+ MANAGER_TIMER
+};
+
struct Manager {
uint32_t current_job_id;
/* 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 */
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
#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);
/* 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
};
#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);
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] = {
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
};
#include <errno.h>
#include <string.h>
#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <sys/poll.h>
#include "set.h"
#include "name.h"
return NULL;
}
- /* Not much initialization happening here at this time */
n->meta.manager = m;
n->meta.type = _NAME_TYPE_INVALID;
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);
/* 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;
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);
/* 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++)
/* 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 */
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);
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;
}
/* FIXME: Does not rollback on failure! */
static int augment(Name *n) {
int r;
- void* state;
+ Iterator i;
Name *other;
assert(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;
const char* name_id(Name *n) {
assert(n);
+ if (n->meta.id)
+ return n->meta.id;
+
return set_first(n->meta.names);
}
[NAME_AFTER] = "After",
};
- void *state;
char *t;
NameDependency d;
+ Iterator i;
assert(n);
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));
}
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)
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)
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);
}
assert(n);
- if (!NAME_VTABLE(n)->reload)
+ if (!name_can_reload(n))
return -EBADR;
state = name_active_state(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);
}
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)
/* 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);
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) {
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");
+ }
+}
#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,
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];
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"
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];
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);
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);
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
/*-*- 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);
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);
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] = {
[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;
"%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) {
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) {
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,
};
typedef struct Service Service;
#include "name.h"
-#include "socket.h"
-#include "timer.h"
typedef enum ServiceState {
SERVICE_DEAD,
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
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;
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);
}
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) {
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);
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
#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);
/* 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
};
#include <errno.h>
#include <fcntl.h>
#include <sys/poll.h>
+#include <signal.h>
#include "name.h"
#include "socket.h"
[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) {
else if (type == SOCK_SEQPACKET)
return "ListenSequentialPacket";
- assert_not_reached("Unkown socket type");
+ assert_not_reached("Unknown socket type");
return NULL;
}
[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"
};
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;
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;
}
}
-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) {
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;
}
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
};
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;
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;
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;
+
+}
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) \
#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] = {
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
};
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
#include "macro.h"
#include "util.h"
int safe_atou(const char *s, unsigned *ret_u) {
char *x = NULL;
- unsigned l;
+ unsigned long l;
assert(s);
assert(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;
int safe_atoi(const char *s, int *ret_i) {
char *x = NULL;
- int l;
+ long l;
assert(s);
assert(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;
}
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;
+}
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);
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