first attempt at proper service/socket logic

This commit is contained in:
Lennart Poettering 2010-01-26 04:18:44 +01:00
parent 9152c76506
commit 034c6ed7da
32 changed files with 2856 additions and 611 deletions

View File

@ -8,13 +8,13 @@
#include "load-fstab.h"
#include "load-dropin.h"
static int automount_load(Name *n) {
static int automount_init(Name *n) {
int r;
Automount *a = AUTOMOUNT(n);
assert(a);
exec_context_defaults(&a->exec_context);
exec_context_init(&a->exec_context);
/* Load a .automount file */
if ((r = name_load_fragment(n)) < 0 && errno != -ENOENT)
@ -31,6 +31,13 @@ static int automount_load(Name *n) {
return 0;
}
static void automount_done(Name *n) {
Automount *d = AUTOMOUNT(n);
assert(d);
free(d->path);
}
static void automount_dump(Name *n, FILE *f, const char *prefix) {
static const char* const state_table[_AUTOMOUNT_STATE_MAX] = {
@ -67,7 +74,7 @@ static void automount_dump(Name *n, FILE *f, const char *prefix) {
for (c = 0; c < _AUTOMOUNT_EXEC_MAX; c++) {
ExecCommand *i;
LIST_FOREACH(i, s->exec_command[c])
LIST_FOREACH(command, i, s->exec_command[c])
fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path);
}
}
@ -88,24 +95,13 @@ static NameActiveState automount_active_state(Name *n) {
return table[AUTOMOUNT(n)->state];
}
static void automount_free_hook(Name *n) {
Automount *d = AUTOMOUNT(n);
assert(d);
free(d->path);
}
const NameVTable automount_vtable = {
.suffix = ".mount",
.load = automount_load,
.init = automount_init,
.done = automount_done,
.dump = automount_dump,
.start = NULL,
.stop = NULL,
.reload = NULL,
.active_state = automount_active_state,
.free_hook = automount_free_hook
.active_state = automount_active_state
};

View File

@ -337,6 +337,36 @@ int config_parse_string(
return 0;
}
int config_parse_path(
const char *filename,
unsigned line,
const char *section,
const char *lvalue,
const char *rvalue,
void *data,
void *userdata) {
char **s = data;
char *n;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (*rvalue != '/') {
log_error("[%s:%u] Not an absolute path: %s", filename, line, rvalue);
return -EINVAL;
}
if (!(n = strdup(rvalue)))
return -ENOMEM;
free(*s);
*s = n;
return 0;
}
int config_parse_strv(
const char *filename,
@ -360,7 +390,7 @@ int config_parse_strv(
assert(data);
k = strv_length(*sv);
FOREACH_WORD(w, &l, rvalue, state)
FOREACH_WORD_QUOTED(w, l, rvalue, state)
k++;
if (!(n = new(char*, k+1)))
@ -368,7 +398,7 @@ int config_parse_strv(
for (k = 0; (*sv)[k]; k++)
n[k] = (*sv)[k];
FOREACH_WORD(w, &l, rvalue, state)
FOREACH_WORD_QUOTED(w, l, rvalue, state)
if (!(n[k++] = strndup(w, l)))
goto fail;
@ -381,6 +411,7 @@ int config_parse_strv(
fail:
for (; k > 0; k--)
free(n[k-1]);
free(n);
return -ENOMEM;
}

View File

@ -29,6 +29,7 @@ int config_parse_unsigned(const char *filename, unsigned line, const char *secti
int config_parse_size(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
int config_parse_bool(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
int config_parse_string(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
int config_parse_path(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
int config_parse_strv(const char *filename, unsigned line, const char *section, const char *lvalue, const char *rvalue, void *data, void *userdata);
#endif

View File

@ -4,6 +4,13 @@
#include "device.h"
#include "strv.h"
static void device_done(Name *n) {
Device *d = DEVICE(n);
assert(d);
strv_free(d->sysfs);
}
static void device_dump(Name *n, FILE *f, const char *prefix) {
static const char* const state_table[_DEVICE_STATE_MAX] = {
@ -24,24 +31,12 @@ static NameActiveState device_active_state(Name *n) {
return DEVICE(n)->state == DEVICE_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
}
static void device_free_hook(Name *n) {
Device *d = DEVICE(n);
assert(d);
strv_free(d->sysfs);
}
const NameVTable device_vtable = {
.suffix = ".device",
.load = name_load_fragment_and_dropin,
.init = name_load_fragment_and_dropin,
.done = device_done,
.dump = device_dump,
.start = NULL,
.stop = NULL,
.reload = NULL,
.active_state = device_active_state,
.free_hook = device_free_hook
.active_state = device_active_state
};

246
execute.c
View File

@ -1,41 +1,238 @@
/*-*- Mode: C; c-basic-offset: 8 -*-*/
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "execute.h"
#include "strv.h"
#include "macro.h"
#include "util.h"
int exec_spawn(const ExecCommand *command, const ExecContext *context, pid_t *ret) {
assert(command);
assert(context);
assert(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;
}
void exec_context_free(ExecContext *c) {
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_init(ExecContext *c) {
assert(c);
c->umask = 0002;
cap_clear(c->capabilities);
c->oom_adjust = 0;
c->nice = 0;
}
void exec_context_done(ExecContext *c) {
unsigned l;
assert(c);
strv_free(c->environment);
c->environment = NULL;
for (l = 0; l < ELEMENTSOF(c->rlimit); l++)
for (l = 0; l < ELEMENTSOF(c->rlimit); l++) {
free(c->rlimit[l]);
c->rlimit[l] = NULL;
}
free(c->directory);
c->directory = NULL;
free(c->chdir);
free(c->user);
c->user = NULL;
free(c->group);
free(c->supplementary_groups);
c->group = NULL;
strv_free(c->supplementary_groups);
c->supplementary_groups = NULL;
}
void exec_command_free_list(ExecCommand *c) {
ExecCommand *i;
while ((i = c)) {
LIST_REMOVE(ExecCommand, c, i);
LIST_REMOVE(ExecCommand, command, c, i);
free(i->path);
free(i->argv);
@ -43,6 +240,16 @@ void exec_command_free_list(ExecCommand *c) {
}
}
void exec_command_free_array(ExecCommand **c, unsigned n) {
unsigned i;
for (i = 0; i < n; i++) {
exec_command_free_list(c[i]);
c[i] = NULL;
}
}
void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
assert(c);
assert(f);
@ -52,17 +259,20 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
fprintf(f,
"%sUmask: %04o\n"
"%sDumpable: %s\n"
"%sDirectory: %s\n",
"%sDirectory: %s\n"
"%sNice: %i\n"
"%sOOMAdjust: %i\n",
prefix, c->umask,
prefix, yes_no(c->dumpable),
prefix, c->chdir ? c->chdir : "/");
prefix, c->directory ? c->directory : "/",
prefix, c->nice,
prefix, c->oom_adjust);
}
void exec_context_defaults(ExecContext *c) {
assert(c);
void exec_status_fill(ExecStatus *s, pid_t pid, int code, int status) {
assert(s);
c->umask = 0002;
cap_clear(c->capabilities);
c->dumpable = true;
s->pid = pid;
s->code = code;
s->status = status;
s->timestamp = now(CLOCK_REALTIME);
}

View File

@ -14,10 +14,11 @@ typedef struct ExecContext ExecContext;
#include <stdio.h>
#include "list.h"
#include "util.h"
struct ExecStatus {
pid_t pid;
time_t timestamp;
usec_t timestamp;
int code; /* as in siginfo_t::si_code */
int status; /* as in sigingo_t::si_status */
};
@ -25,20 +26,20 @@ struct ExecStatus {
struct ExecCommand {
char *path;
char **argv;
ExecStatus last_exec_status;
LIST_FIELDS(ExecCommand);
ExecStatus exec_status;
LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */
};
struct ExecContext {
char **environment;
mode_t umask;
struct rlimit *rlimit[RLIMIT_NLIMITS];
cap_t capabilities;
bool capabilities_set:1;
bool dumpable:1;
int oom_adjust;
int nice;
char *chdir;
char *directory;
cap_t capabilities;
bool capabilities_set:1;
/* since resolving these names might might involve socket
* connections and we don't want to deadlock ourselves these
@ -48,13 +49,40 @@ struct ExecContext {
char **supplementary_groups;
};
int exec_spawn(const ExecCommand *command, const ExecContext *context, pid_t *ret);
typedef enum ExitStatus {
/* EXIT_SUCCESS defined by libc */
/* EXIT_FAILURE defined by libc */
EXIT_INVALIDARGUMENT = 2,
EXIT_NOTIMPLEMENTED = 3,
EXIT_NOPERMISSION = 4,
EXIT_NOTINSTALLED = 5,
EXIT_NOTCONFIGURED = 6,
EXIT_NOTRUNNING = 7,
/* The LSB suggests that error codes >= 200 are "reserved". We
* use them here under the assumption that they hence are
* unused by init scripts.
*
* http://refspecs.freestandards.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */
EXIT_CHDIR = 200,
EXIT_NICE,
EXIT_FDS,
EXIT_EXEC,
EXIT_MEMORY,
EXIT_LIMITS,
EXIT_OOM_ADJUST
} ExitStatus;
int exec_spawn(const ExecCommand *command, const ExecContext *context, int *fds, unsigned n_fds, pid_t *ret);
void exec_context_free(ExecContext *c);
void exec_command_free_list(ExecCommand *c);
void exec_command_free_array(ExecCommand **c, unsigned n);
void exec_context_init(ExecContext *c);
void exec_context_done(ExecContext *c);
void exec_context_dump(ExecContext *c, FILE* f, const char *prefix);
void exec_context_defaults(ExecContext *c);
void exec_status_fill(ExecStatus *s, pid_t pid, int code, int status);
#endif

View File

@ -69,6 +69,18 @@ Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) {
return h;
}
int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func) {
assert(h);
if (*h)
return 0;
if (!(*h = hashmap_new(hash_func, compare_func)))
return -ENOMEM;
return 0;
}
static void remove_entry(Hashmap *h, struct hashmap_entry *e) {
assert(h);
assert(e);
@ -248,26 +260,26 @@ void* hashmap_remove_value(Hashmap *h, const void *key, void *value) {
return value;
}
void *hashmap_iterate(Hashmap *h, void **state, const void **key) {
void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) {
struct hashmap_entry *e;
assert(state);
assert(i);
if (!h)
goto at_end;
if (*state == (void*) -1)
if (*i == ITERATOR_LAST)
goto at_end;
if (!*state && !h->iterate_list_head)
if (*i == ITERATOR_FIRST && !h->iterate_list_head)
goto at_end;
e = *state ? *state : h->iterate_list_head;
e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry*) *i;
if (e->iterate_next)
*state = e->iterate_next;
*i = (Iterator) e->iterate_next;
else
*state = (void*) -1;
*i = ITERATOR_LAST;
if (key)
*key = e->key;
@ -275,7 +287,7 @@ void *hashmap_iterate(Hashmap *h, void **state, const void **key) {
return e->value;
at_end:
*state = (void *) -1;
*i = ITERATOR_LAST;
if (key)
*key = NULL;
@ -283,26 +295,26 @@ at_end:
return NULL;
}
void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key) {
void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key) {
struct hashmap_entry *e;
assert(state);
assert(i);
if (!h)
goto at_beginning;
if (*state == (void*) -1)
if (*i == ITERATOR_FIRST)
goto at_beginning;
if (!*state && !h->iterate_list_tail)
if (*i == ITERATOR_LAST && !h->iterate_list_tail)
goto at_beginning;
e = *state ? *state : h->iterate_list_tail;
e = *i == ITERATOR_LAST ? h->iterate_list_tail : (struct hashmap_entry*) *i;
if (e->iterate_previous)
*state = e->iterate_previous;
*i = (Iterator) e->iterate_previous;
else
*state = (void*) -1;
*i = ITERATOR_FIRST;
if (key)
*key = e->key;
@ -310,7 +322,7 @@ void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key) {
return e->value;
at_beginning:
*state = (void *) -1;
*i = ITERATOR_FIRST;
if (key)
*key = NULL;
@ -318,6 +330,23 @@ at_beginning:
return NULL;
}
void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i) {
unsigned hash;
struct hashmap_entry *e;
if (!h)
return NULL;
hash = h->hash_func(key) % NBUCKETS;
if (!(e = hash_scan(h, hash, key)))
return NULL;
*i = (Iterator) e;
return e->value;
}
void* hashmap_first(Hashmap *h) {
if (!h)

View File

@ -11,6 +11,11 @@
* instantiate an object for each Hashmap use. */
typedef struct Hashmap Hashmap;
typedef struct _IteratorStruct _IteratorStruct;
typedef _IteratorStruct* Iterator;
#define ITERATOR_FIRST ((Iterator) 0)
#define ITERATOR_LAST ((Iterator) -1)
typedef unsigned (*hash_func_t)(const void *p);
typedef int (*compare_func_t)(const void *a, const void *b);
@ -24,6 +29,7 @@ int trivial_compare_func(const void *a, const void *b);
Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func);
void hashmap_free(Hashmap *h);
Hashmap *hashmap_copy(Hashmap *h);
int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func);
int hashmap_put(Hashmap *h, const void *key, void *value);
int hashmap_replace(Hashmap *h, const void *key, void *value);
@ -36,21 +42,22 @@ int hashmap_merge(Hashmap *h, Hashmap *other);
unsigned hashmap_size(Hashmap *h);
bool hashmap_isempty(Hashmap *h);
void *hashmap_iterate(Hashmap *h, void **state, const void **key);
void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key);
void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key);
void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key);
void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i);
void hashmap_clear(Hashmap *h);
void *hashmap_steal_first(Hashmap *h);
void* hashmap_first(Hashmap *h);
void* hashmap_last(Hashmap *h);
#define HASHMAP_FOREACH(e, h, state) \
for ((state) = NULL, (e) = hashmap_iterate((h), &(state), NULL); (e); (e) = hashmap_iterate((h), &(state), NULL))
#define HASHMAP_FOREACH(e, h, i) \
for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); (e); (e) = hashmap_iterate((h), &(i), NULL))
#define HASHMAP_FOREACH_KEY(e, k, h, state) \
for ((state) = NULL, (e) = hashmap_iterate((h), &(state), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(state), (const void**) &(k)))
#define HASHMAP_FOREACH_KEY(e, k, h, i) \
for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(i), (const void**) &(k)))
#define HASHMAP_FOREACH_BACKWARDS(e, h, state) \
for ((state) = NULL, (e) = hashmap_iterate_backwards((h), &(state), NULL); (e); (e) = hashmap_iterate_backwards((h), &(state), NULL))
#define HASHMAP_FOREACH_BACKWARDS(e, h, i) \
for ((i) = ITERATE_LAST, (e) = hashmap_iterate_backwards((h), &(i), NULL); (e); (e) = hashmap_iterate_backwards((h), &(i), NULL))
#endif

71
job.c
View File

@ -276,33 +276,8 @@ bool job_type_is_conflicting(JobType a, JobType b) {
return (a == JOB_STOP) != (b == JOB_STOP);
}
bool job_type_is_applicable(JobType j, NameType n) {
assert(j >= 0 && j < _JOB_TYPE_MAX);
assert(n >= 0 && n < _NAME_TYPE_MAX);
switch (j) {
case JOB_VERIFY_ACTIVE:
case JOB_START:
return true;
case JOB_STOP:
case JOB_RESTART:
case JOB_TRY_RESTART:
return name_type_can_start(n);
case JOB_RELOAD:
return name_type_can_reload(n);
case JOB_RELOAD_OR_START:
return name_type_can_reload(n) && name_type_can_start(n);
default:
assert_not_reached("Invalid job type");
}
}
bool job_is_runnable(Job *j) {
void *state;
Iterator i;
Name *other;
assert(j);
@ -323,7 +298,7 @@ bool job_is_runnable(Job *j) {
* dependencies, regardless whether they are
* starting or stopping something. */
SET_FOREACH(other, j->name->meta.dependencies[NAME_AFTER], state)
SET_FOREACH(other, j->name->meta.dependencies[NAME_AFTER], i)
if (other->meta.job)
return false;
}
@ -331,7 +306,7 @@ bool job_is_runnable(Job *j) {
/* Also, if something else is being stopped and we should
* change state after it, then lets wait. */
SET_FOREACH(other, j->name->meta.dependencies[NAME_BEFORE], state)
SET_FOREACH(other, j->name->meta.dependencies[NAME_BEFORE], i)
if (other->meta.job &&
(other->meta.job->type == JOB_STOP ||
other->meta.job->type == JOB_RESTART ||
@ -356,12 +331,17 @@ int job_run_and_invalidate(Job *j) {
int r;
assert(j);
if (!job_is_runnable(j))
return -EAGAIN;
if (j->in_run_queue) {
LIST_REMOVE(Job, run_queue, j->manager->run_queue, j);
j->in_run_queue = false;
}
if (j->state != JOB_WAITING)
return 0;
if (!job_is_runnable(j))
return -EAGAIN;
j->state = JOB_RUNNING;
switch (j->type) {
@ -437,16 +417,18 @@ int job_run_and_invalidate(Job *j) {
int job_finish_and_invalidate(Job *j, bool success) {
Name *n;
void *state;
Name *other;
NameType t;
Iterator i;
assert(j);
/* Patch restart jobs so that they become normal start jobs */
if (success && (j->type == JOB_RESTART || j->type == JOB_TRY_RESTART)) {
j->state = JOB_RUNNING;
j->type = JOB_START;
return job_run_and_invalidate(j);
job_schedule_run(j);
return 0;
}
n = j->name;
@ -460,14 +442,14 @@ int job_finish_and_invalidate(Job *j, bool success) {
t == JOB_VERIFY_ACTIVE ||
t == JOB_RELOAD_OR_START) {
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], state)
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], i)
if (other->meta.job &&
(other->meta.type == JOB_START ||
other->meta.type == JOB_VERIFY_ACTIVE ||
other->meta.type == JOB_RELOAD_OR_START))
job_finish_and_invalidate(other->meta.job, false);
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRED_BY], state)
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRED_BY], i)
if (other->meta.job &&
!other->meta.job->forced &&
(other->meta.type == JOB_START ||
@ -477,7 +459,7 @@ int job_finish_and_invalidate(Job *j, bool success) {
} else if (t == JOB_STOP) {
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], state)
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], i)
if (other->meta.job &&
(t == JOB_START ||
t == JOB_VERIFY_ACTIVE ||
@ -487,12 +469,23 @@ int job_finish_and_invalidate(Job *j, bool success) {
}
/* Try to start the next jobs that can be started */
SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], state)
SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], i)
if (other->meta.job)
job_run_and_invalidate(other->meta.job);
SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], state)
job_schedule_run(other->meta.job);
SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], i)
if (other->meta.job)
job_run_and_invalidate(other->meta.job);
job_schedule_run(other->meta.job);
return 0;
}
void job_schedule_run(Job *j) {
assert(j);
assert(j->linked);
if (j->in_run_queue)
return;
LIST_PREPEND(Job, run_queue, j->manager->run_queue, j);
j->in_run_queue = true;
}

13
job.h
View File

@ -56,9 +56,8 @@ struct JobDependency {
bool matters;
/* Linked list for the subjects, resp objects */
JobDependency *subject_prev, *subject_next;
JobDependency *object_prev, *object_next;
LIST_FIELDS(JobDependency, subject);
LIST_FIELDS(JobDependency, object);
};
struct Job {
@ -71,11 +70,12 @@ struct Job {
JobState state;
bool linked:1;
bool in_run_queue:1;
bool matters_to_anchor:1;
bool forced:1;
/* These fields are used only while building a transaction */
Job *transaction_next, *transaction_prev;
LIST_FIELDS(Job, transaction);
LIST_FIELDS(Job, run_queue);
JobDependency *subject_list;
JobDependency *object_list;
@ -83,6 +83,7 @@ struct Job {
/* Used for graph algs as a "I have been here" marker */
Job* marker;
unsigned generation;
};
Job* job_new(Manager *m, JobType type, Name *name);
@ -102,8 +103,8 @@ int job_type_merge(JobType *a, JobType b);
bool job_type_is_mergeable(JobType a, JobType b);
bool job_type_is_superset(JobType a, JobType b);
bool job_type_is_conflicting(JobType a, JobType b);
bool job_type_is_applicable(JobType j, NameType n);
void job_schedule_run(Job *j);
int job_run_and_invalidate(Job *j);
int job_finish_and_invalidate(Job *j, bool success);

82
list.h
View File

@ -6,85 +6,95 @@
/* The head of the linked list. Use this in the structure that shall
* contain the head of the linked list */
#define LIST_HEAD(t,name) \
t *name
t *name
/* The pointers in the linked list's items. Use this in the item structure */
#define LIST_FIELDS(t) \
t *next, *prev
#define LIST_FIELDS(t,name) \
t *name##_next, *name##_prev
/* Initialize the list's head */
#define LIST_HEAD_INIT(t,item) \
#define LIST_HEAD_INIT(t,head) \
do { \
(item) = (t*) NULL; } \
(head) = NULL; } \
while(false)
/* Initialize a list item */
#define LIST_INIT(t,item) \
#define LIST_INIT(t,name,item) \
do { \
t *_item = (item); \
assert(_item); \
_item->prev = _item->next = NULL; \
_item->name##_prev = _item->name##_next = NULL; \
} while(false)
/* Prepend an item to the list */
#define LIST_PREPEND(t,head,item) \
#define LIST_PREPEND(t,name,head,item) \
do { \
t **_head = &(head), *_item = (item); \
assert(_item); \
if ((_item->next = *_head)) \
_item->next->prev = _item; \
_item->prev = NULL; \
if ((_item->name##_next = *_head)) \
_item->name##_next->name##_prev = _item; \
_item->name##_prev = NULL; \
*_head = _item; \
} while(false)
/* Remove an item from the list */
#define LIST_REMOVE(t,head,item) \
#define LIST_REMOVE(t,name,head,item) \
do { \
t **_head = &(head), *_item = (item); \
assert(_item); \
if (_item->next) \
_item->next->prev = _item->prev; \
if (_item->prev) \
_item->prev->next = _item->next; \
if (_item->name##_next) \
_item->name##_next->name##_prev = _item->name##_prev; \
if (_item->name##_prev) \
_item->name##_prev->name##_next = _item->name##_next; \
else { \
assert(*_head == _item); \
*_head = _item->next; \
*_head = _item->name##_next; \
} \
_item->next = _item->prev = NULL; \
_item->name##_next = _item->name##_prev = NULL; \
} while(false)
/* Find the head of the list */
#define LIST_FIND_HEAD(t,item,head) \
#define LIST_FIND_HEAD(t,name,item,head) \
do { \
t **_head = (head), *_item = (item); \
*_head = _item; \
assert(_head); \
while ((*_head)->prev) \
*_head = (*_head)->prev; \
t *_item = (item); \
assert(_item); \
while ((_item->name##_prev) \
_item = _item->name##_prev; \
(head) = _item; \
} while (false)
/* Find the head of the list */
#define LIST_FIND_TAIL(t,name,item,tail) \
do { \
t *_item = (item); \
assert(_item); \
while (_item->name##_next) \
_item = _item->name##_next; \
(tail) = _item; \
} while (false)
/* Insert an item after another one (a = where, b = what) */
#define LIST_INSERT_AFTER(t,head,a,b) \
#define LIST_INSERT_AFTER(t,name,head,a,b) \
do { \
t **_head = &(head), *_a = (a), *_b = (b); \
assert(_b); \
if (!_a) { \
if ((_b->next = *_head)) \
_b->next->prev = _b; \
_b->prev = NULL; \
if ((_b->name##_next = *_head)) \
_b->name##_next->name##_prev = _b; \
_b->name##_prev = NULL; \
*_head = _b; \
} else { \
if ((_b->next = _a->next)) \
_b->next->prev = _b; \
_b->prev = _a; \
_a->next = _b; \
if ((_b->name##_next = _a->name##_next)) \
_b->name##_next->name##_prev = _b; \
_b->name##_prev = _a; \
_a->name##_next = _b; \
} \
} while(false)
#define LIST_FOREACH(i,head) \
for (i = (head); i; i = i->next)
#define LIST_FOREACH(name,i,head) \
for ((i) = (head); (i); (i) = (i)->name##_next)
#define LIST_FOREACH_SAFE(i,n,head) \
for (i = (head); i && ((n = i->next), 1); i = n)
#define LIST_FOREACH_SAFE(name,i,n,head) \
for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n))
#endif

View File

@ -3,6 +3,7 @@
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <linux/oom.h>
#include "name.h"
#include "strv.h"
@ -44,9 +45,8 @@ static int config_parse_deps(
if (r < 0)
return r;
if (!*set)
if (!(*set = set_new(trivial_hash_func, trivial_compare_func)))
return -ENOMEM;
if ((r = set_ensure_allocated(set, trivial_hash_func, trivial_compare_func)) < 0)
return r;
if ((r = set_put(*set, other)) < 0)
return r;
@ -177,12 +177,12 @@ static int config_parse_listen(
}
p->fd = -1;
LIST_PREPEND(SocketPort, s->ports, p);
LIST_PREPEND(SocketPort, port, s->ports, p);
return 0;
}
static int config_parse_bind(
static int config_parse_socket_bind(
const char *filename,
unsigned line,
const char *section,
@ -211,6 +211,256 @@ static int config_parse_bind(
return 0;
}
static int config_parse_nice(
const char *filename,
unsigned line,
const char *section,
const char *lvalue,
const char *rvalue,
void *data,
void *userdata) {
int *i = data, priority, r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if ((r = safe_atoi(rvalue, &priority)) < 0) {
log_error("[%s:%u] Failed to parse nice priority: %s", filename, line, rvalue);
return r;
}
if (priority < PRIO_MIN || priority >= PRIO_MAX) {
log_error("[%s:%u] Nice priority out of range: %s", filename, line, rvalue);
return -ERANGE;
}
*i = priority;
return 0;
}
static int config_parse_oom_adjust(
const char *filename,
unsigned line,
const char *section,
const char *lvalue,
const char *rvalue,
void *data,
void *userdata) {
int *i = data, oa, r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if ((r = safe_atoi(rvalue, &oa)) < 0) {
log_error("[%s:%u] Failed to parse OOM adjust value: %s", filename, line, rvalue);
return r;
}
if (oa < OOM_DISABLE || oa > OOM_ADJUST_MAX) {
log_error("[%s:%u] OOM adjust value out of range: %s", filename, line, rvalue);
return -ERANGE;
}
*i = oa;
return 0;
}
static int config_parse_umask(
const char *filename,
unsigned line,
const char *section,
const char *lvalue,
const char *rvalue,
void *data,
void *userdata) {
mode_t *m = data;
long l;
char *x = NULL;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
errno = 0;
l = strtol(rvalue, &x, 8);
if (!x || *x || errno) {
log_error("[%s:%u] Failed to parse umask value: %s", filename, line, rvalue);
return errno ? -errno : -EINVAL;
}
if (l < 0000 || l > 0777) {
log_error("[%s:%u] umask value out of range: %s", filename, line, rvalue);
return -ERANGE;
}
*m = (mode_t) l;
return 0;
}
static int config_parse_exec(
const char *filename,
unsigned line,
const char *section,
const char *lvalue,
const char *rvalue,
void *data,
void *userdata) {
ExecCommand **e = data, *ee, *nce = NULL;
char **n;
char *w;
unsigned k;
size_t l;
char *state;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
k = 0;
FOREACH_WORD_QUOTED(w, l, rvalue, state)
k++;
if (!(n = new(char*, k+1)))
return -ENOMEM;
FOREACH_WORD_QUOTED(w, l, rvalue, state)
if (!(n[k++] = strndup(w, l)))
goto fail;
n[k] = NULL;
if (!n[0] || n[0][0] != '/') {
log_error("[%s:%u] Invalid executable path in command line: %s", filename, line, rvalue);
strv_free(n);
return -EINVAL;
}
if (!(nce = new0(ExecCommand, 1)))
goto fail;
nce->argv = n;
if (!(nce->path = strdup(n[0])))
goto fail;
if (*e) {
/* It's kinda important that we keep the order here */
LIST_FIND_TAIL(ExecCommand, command, *e, ee);
LIST_INSERT_AFTER(ExecCommand, command, *e, ee, nce);
} else
*e = nce;
return 0;
fail:
for (; k > 0; k--)
free(n[k-1]);
free(n);
free(nce);
return -ENOMEM;
}
static int config_parse_usec(
const char *filename,
unsigned line,
const char *section,
const char *lvalue,
const char *rvalue,
void *data,
void *userdata) {
usec_t *usec = data;
unsigned long long u;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if ((r = safe_atollu(rvalue, &u)) < 0) {
log_error("[%s:%u] Failed to parse time value: %s", filename, line, rvalue);
return r;
}
/* We actually assume the user configures seconds. Later on we
* might choose to support suffixes for time values, to
* configure bigger or smaller units */
*usec = u * USEC_PER_SEC;
return 0;
}
static int config_parse_service_type(
const char *filename,
unsigned line,
const char *section,
const char *lvalue,
const char *rvalue,
void *data,
void *userdata) {
Service *s = data;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (streq(rvalue, "forking"))
s->type = SERVICE_FORKING;
else if (streq(rvalue, "simple"))
s->type = SERVICE_SIMPLE;
else {
log_error("[%s:%u] Failed to parse service type: %s", filename, line, rvalue);
return -EBADMSG;
}
return 0;
}
static int config_parse_service_restart(
const char *filename,
unsigned line,
const char *section,
const char *lvalue,
const char *rvalue,
void *data,
void *userdata) {
Service *s = data;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (streq(rvalue, "once"))
s->restart = SERVICE_ONCE;
else if (streq(rvalue, "on-success"))
s->type = SERVICE_RESTART_ON_SUCCESS;
else if (streq(rvalue, "always"))
s->type = SERVICE_RESTART_ALWAYS;
else {
log_error("[%s:%u] Failed to parse service type: %s", filename, line, rvalue);
return -EBADMSG;
}
return 0;
}
int name_load_fragment(Name *n) {
static const char* const section_table[_NAME_TYPE_MAX] = {
@ -224,32 +474,64 @@ int name_load_fragment(Name *n) {
[NAME_SNAPSHOT] = "Snapshot"
};
#define EXEC_CONTEXT_CONFIG_ITEMS(context, section) \
{ "Directory", config_parse_path, &(context).directory, section }, \
{ "User", config_parse_string, &(context).user, section }, \
{ "Group", config_parse_string, &(context).group, section }, \
{ "SupplementaryGroups", config_parse_strv, &(context).supplementary_groups, section }, \
{ "Nice", config_parse_nice, &(context).nice, section }, \
{ "OOMAdjust", config_parse_oom_adjust, &(context).oom_adjust, section }, \
{ "UMask", config_parse_umask, &(context).umask, section }, \
{ "Environment", config_parse_strv, &(context).environment, section }
const ConfigItem items[] = {
{ "Names", config_parse_names, &n->meta.names, "Meta" },
{ "Description", config_parse_string, &n->meta.description, "Meta" },
{ "Requires", config_parse_deps, n->meta.dependencies+NAME_REQUIRES, "Meta" },
{ "SoftRequires", config_parse_deps, n->meta.dependencies+NAME_SOFT_REQUIRES, "Meta" },
{ "Wants", config_parse_deps, n->meta.dependencies+NAME_WANTS, "Meta" },
{ "Requisite", config_parse_deps, n->meta.dependencies+NAME_REQUISITE, "Meta" },
{ "SoftRequisite", config_parse_deps, n->meta.dependencies+NAME_SOFT_REQUISITE, "Meta" },
{ "Conflicts", config_parse_deps, n->meta.dependencies+NAME_CONFLICTS, "Meta" },
{ "Before", config_parse_deps, n->meta.dependencies+NAME_BEFORE, "Meta" },
{ "After", config_parse_deps, n->meta.dependencies+NAME_AFTER, "Meta" },
{ "ListenStream", config_parse_listen, &n->socket, "Socket" },
{ "ListenDatagram", config_parse_listen, &n->socket, "Socket" },
{ "ListenSequentialPacket", config_parse_listen, &n->socket, "Socket" },
{ "ListenFIFO", config_parse_listen, &n->socket, "Socket" },
{ "BindIPv6Only", config_parse_bind, &n->socket, "Socket" },
{ "Backlog", config_parse_unsigned, &n->socket.backlog, "Socket" },
{ "Names", config_parse_names, &n->meta.names, "Meta" },
{ "Description", config_parse_string, &n->meta.description, "Meta" },
{ "Requires", config_parse_deps, n->meta.dependencies+NAME_REQUIRES, "Meta" },
{ "SoftRequires", config_parse_deps, n->meta.dependencies+NAME_SOFT_REQUIRES, "Meta" },
{ "Wants", config_parse_deps, n->meta.dependencies+NAME_WANTS, "Meta" },
{ "Requisite", config_parse_deps, n->meta.dependencies+NAME_REQUISITE, "Meta" },
{ "SoftRequisite", config_parse_deps, n->meta.dependencies+NAME_SOFT_REQUISITE, "Meta" },
{ "Conflicts", config_parse_deps, n->meta.dependencies+NAME_CONFLICTS, "Meta" },
{ "Before", config_parse_deps, n->meta.dependencies+NAME_BEFORE, "Meta" },
{ "After", config_parse_deps, n->meta.dependencies+NAME_AFTER, "Meta" },
{ "PIDFile", config_parse_path, &n->service.pid_file, "Service" },
{ "ExecStartPre", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_START_PRE], "Service" },
{ "ExecStart", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_START], "Service" },
{ "ExecStartPost", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_START_POST], "Service" },
{ "ExecReload", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_RELOAD], "Service" },
{ "ExecStop", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_STOP], "Service" },
{ "ExecStopPost", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_STOP_POST], "Service" },
{ "RestartSec", config_parse_usec, &n->service.restart_usec, "Service" },
{ "TimeoutSec", config_parse_usec, &n->service.timeout_usec, "Service" },
{ "Type", config_parse_service_type, &n->service, "Service" },
{ "Restart", config_parse_service_restart, &n->service, "Service" },
EXEC_CONTEXT_CONFIG_ITEMS(n->service.exec_context, "Service"),
{ "ListenStream", config_parse_listen, &n->socket, "Socket" },
{ "ListenDatagram", config_parse_listen, &n->socket, "Socket" },
{ "ListenSequentialPacket", config_parse_listen, &n->socket, "Socket" },
{ "ListenFIFO", config_parse_listen, &n->socket, "Socket" },
{ "BindIPv6Only", config_parse_socket_bind, &n->socket, "Socket" },
{ "Backlog", config_parse_unsigned, &n->socket.backlog, "Socket" },
{ "ExecStartPre", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_START_PRE], "Socket" },
{ "ExecStartPost", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_START_POST], "Socket" },
{ "ExecStopPre", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_STOP_PRE], "Socket" },
{ "ExecStopPost", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_STOP_POST], "Socket" },
EXEC_CONTEXT_CONFIG_ITEMS(n->socket.exec_context, "Socket"),
EXEC_CONTEXT_CONFIG_ITEMS(n->automount.exec_context, "Automount"),
{ NULL, NULL, NULL, NULL }
};
const
#undef EXEC_CONTEXT_CONFIG_ITEMS
char *t;
int r;
void *state;
const char *sections[3];
Iterator i;
assert(n);
assert(n->meta.load_state == NAME_STUB);
@ -258,12 +540,18 @@ int name_load_fragment(Name *n) {
sections[1] = section_table[n->meta.type];
sections[2] = NULL;
SET_FOREACH(t, n->meta.names, state)
if ((r = config_parse(t, sections, items, n)) < 0)
goto fail;
SET_FOREACH(t, n->meta.names, i) {
r = 0;
/* Try to find a name we can load this with */
if ((r = config_parse(t, sections, items, n)) == -ENOENT)
continue;
fail:
return r;
/* Yay, we succeeded! Now let's call this our identifier */
if (r == 0)
n->meta.id = t;
return r;
}
return -ENOENT;
}

View File

@ -76,4 +76,6 @@ static inline size_t ALIGN(size_t l) {
#define memzero(x,l) (memset((x), 0, (l)))
#define zero(x) (memzero(&(x), sizeof(x)))
#define char_array_0(x) x[sizeof(x)-1] = 0;
#endif

2
main.c
View File

@ -42,8 +42,6 @@ int main(int argc, char *argv[]) {
printf("- By jobs:\n");
manager_dump_jobs(m, stdout, "\t");
manager_run_jobs(m);
manager_loop(m);
retval = 0;

234
manager.c
View File

@ -217,8 +217,8 @@ static int delete_one_unmergeable_job(Manager *m, Job *j) {
/* We rely here on the fact that if a merged with b does not
* merge with c, either a or b merge with c neither */
for (; j; j = j->transaction_next)
for (k = j->transaction_next; k; k = k->transaction_next) {
LIST_FOREACH(transaction, j, j)
LIST_FOREACH(transaction, k, j->transaction_next) {
Job *d;
/* Is this one mergeable? Then skip it */
@ -245,19 +245,19 @@ static int delete_one_unmergeable_job(Manager *m, Job *j) {
static int transaction_merge_jobs(Manager *m) {
Job *j;
void *state;
Iterator i;
int r;
assert(m);
/* First step, check whether any of the jobs for one specific
* task conflict. If so, try to drop one of them. */
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
JobType t;
Job *k;
t = j->type;
for (k = j->transaction_next; k; k = k->transaction_next) {
LIST_FOREACH(transaction, k, j->transaction_next) {
if ((r = job_type_merge(&t, k->type)) >= 0)
continue;
@ -277,12 +277,12 @@ static int transaction_merge_jobs(Manager *m) {
}
/* Second step, merge the jobs. */
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
JobType t = j->type;
Job *k;
/* Merge all transactions */
for (k = j->transaction_next; k; k = k->transaction_next)
LIST_FOREACH(transaction, k, j->transaction_next)
assert_se(job_type_merge(&t, k->type) == 0);
/* If an active job is mergeable, merge it too */
@ -311,7 +311,7 @@ static bool name_matters_to_anchor(Name *n, Job *j) {
/* Checks whether at least one of the jobs for this name
* matters to the anchor. */
for (; j; j = j->transaction_next)
LIST_FOREACH(transaction, j, j)
if (j->matters_to_anchor)
return true;
@ -319,7 +319,7 @@ static bool name_matters_to_anchor(Name *n, Job *j) {
}
static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned generation) {
void *state;
Iterator i;
Name *n;
int r;
@ -368,7 +368,7 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned
/* We assume that the the dependencies are bidirectional, and
* hence can ignore NAME_AFTER */
SET_FOREACH(n, j->name->meta.dependencies[NAME_BEFORE], state) {
SET_FOREACH(n, j->name->meta.dependencies[NAME_BEFORE], i) {
Job *o;
/* Is there a job for this name? */
@ -390,7 +390,7 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned
static int transaction_verify_order(Manager *m, unsigned *generation) {
Job *j;
int r;
void *state;
Iterator i;
assert(m);
assert(generation);
@ -398,7 +398,7 @@ static int transaction_verify_order(Manager *m, unsigned *generation) {
/* Check if the ordering graph is cyclic. If it is, try to fix
* that up by dropping one of the jobs. */
HASHMAP_FOREACH(j, m->transaction_jobs, state)
HASHMAP_FOREACH(j, m->transaction_jobs, i)
if ((r = transaction_verify_order_one(m, j, NULL, (*generation)++)) < 0)
return r;
@ -413,12 +413,12 @@ static void transaction_collect_garbage(Manager *m) {
/* Drop jobs that are not required by any other job */
do {
void *state;
Iterator i;
Job *j;
again = false;
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
if (j->object_list)
continue;
@ -432,7 +432,7 @@ static void transaction_collect_garbage(Manager *m) {
}
static int transaction_is_destructive(Manager *m, JobMode mode) {
void *state;
Iterator i;
Job *j;
assert(m);
@ -440,7 +440,7 @@ static int transaction_is_destructive(Manager *m, JobMode mode) {
/* Checks whether applying this transaction means that
* existing jobs would be replaced */
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
/* Assume merged */
assert(!j->transaction_prev);
@ -464,12 +464,12 @@ static void transaction_minimize_impact(Manager *m) {
do {
Job *j;
void *state;
Iterator i;
again = false;
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
for (; j; j = j->transaction_next) {
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
LIST_FOREACH(transaction, j, j) {
/* If it matters, we shouldn't drop it */
if (j->matters_to_anchor)
@ -497,13 +497,13 @@ static void transaction_minimize_impact(Manager *m) {
}
static int transaction_apply(Manager *m, JobMode mode) {
void *state;
Iterator i;
Job *j;
int r;
/* Moves the transaction jobs to the set of active jobs */
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
/* Assume merged */
assert(!j->transaction_prev);
assert(!j->transaction_next);
@ -543,7 +543,7 @@ static int transaction_apply(Manager *m, JobMode mode) {
rollback:
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
if (j->linked)
continue;
@ -637,7 +637,7 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool f
f = hashmap_get(m->transaction_jobs, name);
for (j = f; j; j = j->transaction_next) {
LIST_FOREACH(transaction, j, f) {
assert(j->name == name);
if (j->type == type) {
@ -652,21 +652,18 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool f
else if (!(j = job_new(m, type, name)))
return NULL;
if ((r = hashmap_replace(m->transaction_jobs, name, j)) < 0) {
job_free(j);
return NULL;
}
j->transaction_next = f;
if (f)
f->transaction_prev = j;
j->generation = 0;
j->marker = NULL;
j->matters_to_anchor = false;
j->forced = force;
LIST_PREPEND(Job, transaction, f, j);
if ((r = hashmap_replace(m->transaction_jobs, name, f)) < 0) {
job_free(j);
return NULL;
}
if (is_new)
*is_new = true;
@ -708,7 +705,7 @@ void manager_transaction_unlink_job(Manager *m, Job *j) {
static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *name, Job *by, bool matters, bool force, Job **_ret) {
Job *ret;
void *state;
Iterator i;
Name *dep;
int r;
bool is_new;
@ -720,7 +717,7 @@ static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *
if (name->meta.load_state != NAME_LOADED)
return -EINVAL;
if (!job_type_is_applicable(type, name->meta.type))
if (!name_job_is_applicable(name, type))
return -EBADR;
/* First add the job. */
@ -734,28 +731,28 @@ static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *
if (is_new) {
/* Finally, recursively add in all dependencies. */
if (type == JOB_START || type == JOB_RELOAD_OR_START) {
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRES], state)
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRES], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
goto fail;
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUIRES], state)
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUIRES], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !force, force, NULL)) < 0 && r != -EBADR)
goto fail;
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_WANTS], state)
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_WANTS], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, force, NULL)) < 0 && r != -EBADR)
goto fail;
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUISITE], state)
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUISITE], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
goto fail;
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUISITE], state)
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUISITE], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !force, force, NULL)) < 0 && r != -EBADR)
goto fail;
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_CONFLICTS], state)
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_CONFLICTS], i)
if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
goto fail;
} else if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRED_BY], state)
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRED_BY], i)
if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
goto fail;
}
@ -805,14 +802,14 @@ Name *manager_get_name(Manager *m, const char *name) {
return hashmap_get(m->names, name);
}
static int dispatch_load_queue(Manager *m) {
static void dispatch_load_queue(Manager *m) {
Meta *meta;
assert(m);
/* Make sure we are not run recursively */
if (m->dispatching_load_queue)
return 0;
return;
m->dispatching_load_queue = true;
@ -820,50 +817,36 @@ static int dispatch_load_queue(Manager *m) {
* tries to load its data until the queue is empty */
while ((meta = m->load_queue)) {
assert(meta->linked);
assert(meta->in_load_queue);
name_load(NAME(meta));
LIST_REMOVE(Meta, m->load_queue, meta);
}
m->dispatching_load_queue = false;
return 0;
}
int manager_load_name(Manager *m, const char *name, Name **_ret) {
Name *ret;
NameType t;
int r;
char *n;
assert(m);
assert(name);
assert(_ret);
if (!name_is_valid(name))
return -EINVAL;
/* This will load the service information files, but not actually
* start any services or anything */
if ((ret = manager_get_name(m, name)))
goto finish;
if ((t = name_type_from_string(name)) == _NAME_TYPE_INVALID)
return -EINVAL;
if ((ret = manager_get_name(m, name))) {
*_ret = ret;
return 0;
}
if (!(ret = name_new(m)))
return -ENOMEM;
ret->meta.type = t;
if (!(n = strdup(name))) {
if ((r = name_add_name(ret, name)) < 0) {
name_free(ret);
return -ENOMEM;
}
if ((r = set_put(ret->meta.names, n)) < 0) {
name_free(ret);
free(n);
return r;
}
@ -872,38 +855,36 @@ int manager_load_name(Manager *m, const char *name, Name **_ret) {
return r;
}
/* At this point the new entry is created and linked. However,
/* At this point the new entry is created and linked. However
* not loaded. Now load this entry and all its dependencies
* recursively */
dispatch_load_queue(m);
finish:
*_ret = ret;
return 0;
}
void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) {
void *state;
Iterator i;
Job *j;
assert(s);
assert(f);
HASHMAP_FOREACH(j, s->jobs, state)
HASHMAP_FOREACH(j, s->jobs, i)
job_dump(j, f, prefix);
}
void manager_dump_names(Manager *s, FILE *f, const char *prefix) {
void *state;
Iterator i;
Name *n;
const char *t;
assert(s);
assert(f);
HASHMAP_FOREACH_KEY(n, t, s->names, state)
HASHMAP_FOREACH_KEY(n, t, s->names, i)
if (name_id(n) == t)
name_dump(n, f, prefix);
}
@ -919,19 +900,25 @@ void manager_clear_jobs(Manager *m) {
job_free(j);
}
void manager_run_jobs(Manager *m) {
void manager_dispatch_run_queue(Manager *m) {
Job *j;
void *state;
int r;
HASHMAP_FOREACH(j, m->jobs, state) {
r = job_run_and_invalidate(j);
if (m->dispatching_run_queue)
return;
/* FIXME... the list of jobs might have changed */
m->dispatching_run_queue = true;
while ((j = m->run_queue)) {
assert(j->linked);
assert(j->in_run_queue);
job_run_and_invalidate(j);
}
m->dispatching_run_queue = false;
}
int manager_dispatch_sigchld(Manager *m) {
static int manager_dispatch_sigchld(Manager *m) {
assert(m);
for (;;) {
@ -945,6 +932,9 @@ int manager_dispatch_sigchld(Manager *m) {
if (si.si_pid == 0)
break;
if (si.si_code != CLD_EXITED && si.si_code != CLD_KILLED && si.si_code != CLD_DUMPED)
continue;
if (!(n = hashmap_remove(m->watch_pids, UINT32_TO_PTR(si.si_pid))))
continue;
@ -954,7 +944,7 @@ int manager_dispatch_sigchld(Manager *m) {
return 0;
}
int manager_process_signal_fd(Manager *m) {
static int manager_process_signal_fd(Manager *m) {
ssize_t n;
struct signalfd_siginfo sfsi;
bool sigchld = false;
@ -978,20 +968,77 @@ int manager_process_signal_fd(Manager *m) {
}
if (sigchld)
manager_dispatch_sigchld(m);
return manager_dispatch_sigchld(m);
return 0;
}
static int process_event(Manager *m, struct epoll_event *ev) {
int r;
assert(m);
assert(ev);
switch (ev->data.u32) {
case MANAGER_SIGNAL:
assert(ev->data.fd == m->signal_fd);
/* An incoming signal? */
if (ev->events != POLLIN)
return -EINVAL;
if ((r = manager_process_signal_fd(m)) < 0)
return -r;
break;
case MANAGER_FD: {
Name *n;
/* Some fd event, to be dispatched to the names */
assert_se(n = ev->data.ptr);
NAME_VTABLE(n)->fd_event(n, ev->data.fd, ev->events);
break;
}
case MANAGER_TIMER: {
Name *n;
uint64_t u;
ssize_t k;
/* Some timer event, to be dispatched to the names */
if ((k = read(ev->data.fd, &u, sizeof(u))) != sizeof(u)) {
if (k < 0 && (errno == EINTR || errno == EAGAIN))
break;
return k < 0 ? -errno : -EIO;
}
assert_se(n = ev->data.ptr);
NAME_VTABLE(n)->timer_event(n, ev->data.fd, u);
break;
}
default:
assert_not_reached("Unknown epoll event type.");
}
return 0;
}
int manager_loop(Manager *m) {
int r;
struct epoll_event events[32];
assert(m);
for (;;) {
struct epoll_event events[32];
int n, i;
manager_dispatch_run_queue(m);
if ((n = epoll_wait(m->epoll_fd, events, ELEMENTSOF(events), -1)) < 0) {
if (errno == -EINTR)
@ -1000,23 +1047,8 @@ int manager_loop(Manager *m) {
return -errno;
}
for (i = 0; i < n; i++) {
if (events[i].data.fd == m->signal_fd) {
/* An incoming signal? */
if (events[i].events != POLLIN)
return -EINVAL;
if ((r = manager_process_signal_fd(m)) < 0)
return -r;
} else {
Name *n;
/* Some other fd event, to be dispatched to the names */
assert_se(n = events[i].data.ptr);
NAME_VTABLE(n)->fd_event(n, events[i].data.fd, events[i].events);
}
}
for (i = 0; i < n; i++)
if ((r = process_event(m, events + i)) < 0)
return r;
}
}

View File

@ -8,6 +8,7 @@
#include <stdio.h>
typedef struct Manager Manager;
typedef enum ManagerEventType ManagerEventType;
#include "name.h"
#include "job.h"
@ -15,6 +16,12 @@ typedef struct Manager Manager;
#include "list.h"
#include "set.h"
enum ManagerEventType {
MANAGER_SIGNAL,
MANAGER_FD,
MANAGER_TIMER
};
struct Manager {
uint32_t current_job_id;
@ -29,11 +36,15 @@ struct Manager {
/* Names that need to be loaded */
LIST_HEAD(Meta, load_queue); /* this is actually more a stack than a queue, but uh. */
/* Jobs that need to be run */
LIST_HEAD(Job, run_queue); /* more a stack than a queue, too */
/* Jobs to be added */
Hashmap *transaction_jobs; /* Name object => Job object list 1:1 */
JobDependency *transaction_anchor;
bool dispatching_load_queue:1;
bool dispatching_run_queue:1;
Hashmap *watch_pids; /* pid => Name object n:1 */
@ -57,7 +68,7 @@ void manager_transaction_unlink_job(Manager *m, Job *j);
void manager_clear_jobs(Manager *m);
void manager_run_jobs(Manager *m);
void manager_dispatch_run_queue(Manager *m);
int manager_loop(Manager *m);
#endif

View File

@ -4,11 +4,7 @@
#include "milestone.h"
#include "load-fragment.h"
static NameActiveState milestone_active_state(Name *n) {
return MILESTONE(n)->state == MILESTONE_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
}
static void milestone_free_hook(Name *n) {
static void milestone_done(Name *n) {
Milestone *m = MILESTONE(n);
assert(m);
@ -16,17 +12,15 @@ static void milestone_free_hook(Name *n) {
/* Nothing here for now */
}
static NameActiveState milestone_active_state(Name *n) {
return MILESTONE(n)->state == MILESTONE_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
}
const NameVTable milestone_vtable = {
.suffix = ".milestone",
.load = name_load_fragment,
.dump = NULL,
.init = name_load_fragment,
.done = milestone_done,
.start = NULL,
.stop = NULL,
.reload = NULL,
.active_state = milestone_active_state,
.free_hook = milestone_free_hook
.active_state = milestone_active_state
};

26
mount.c
View File

@ -8,7 +8,7 @@
#include "load-fstab.h"
#include "load-dropin.h"
static int mount_load(Name *n) {
static int mount_init(Name *n) {
int r;
Mount *m = MOUNT(n);
@ -29,6 +29,13 @@ static int mount_load(Name *n) {
return r;
}
static void mount_done(Name *n) {
Mount *d = MOUNT(n);
assert(d);
free(d->path);
}
static void mount_dump(Name *n, FILE *f, const char *prefix) {
static const char* const state_table[_MOUNT_STATE_MAX] = {
@ -63,24 +70,13 @@ static NameActiveState mount_active_state(Name *n) {
return table[MOUNT(n)->state];
}
static void mount_free_hook(Name *n) {
Mount *d = MOUNT(n);
assert(d);
free(d->path);
}
const NameVTable mount_vtable = {
.suffix = ".mount",
.load = mount_load,
.init = mount_init,
.done = mount_done,
.dump = mount_dump,
.start = NULL,
.stop = NULL,
.reload = NULL,
.active_state = mount_active_state,
.free_hook = mount_free_hook
};

284
name.c
View File

@ -4,6 +4,8 @@
#include <errno.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <sys/poll.h>
#include "set.h"
#include "name.h"
@ -77,7 +79,6 @@ Name *name_new(Manager *m) {
return NULL;
}
/* Not much initialization happening here at this time */
n->meta.manager = m;
n->meta.type = _NAME_TYPE_INVALID;
@ -86,10 +87,40 @@ Name *name_new(Manager *m) {
return n;
}
int name_add_name(Name *n, const char *text) {
NameType t;
char *s;
int r;
assert(n);
assert(text);
if ((t = name_type_from_string(text)) == _NAME_TYPE_INVALID)
return -EINVAL;
if (n->meta.type != _NAME_TYPE_INVALID && t != n->meta.type)
return -EINVAL;
if (!(s = strdup(text)))
return -ENOMEM;
if ((r = set_put(n->meta.names, s)) < 0) {
free(s);
return r;
}
n->meta.type = t;
if (!n->meta.id)
n->meta.id = s;
return 0;
}
/* FIXME: Does not rollback on failure! */
int name_link_names(Name *n, bool replace) {
char *t;
void *state;
Iterator i;
int r;
assert(n);
@ -99,7 +130,7 @@ int name_link_names(Name *n, bool replace) {
/* Link all names that aren't linked yet. */
SET_FOREACH(t, n->meta.names, state)
SET_FOREACH(t, n->meta.names, i)
if (replace) {
if ((r = hashmap_replace(n->meta.manager->names, t, n)) < 0)
return r;
@ -125,24 +156,26 @@ int name_link(Name *n) {
if ((r = name_link_names(n, false)) < 0) {
char *t;
void *state;
Iterator i;
/* Rollback the registered names */
SET_FOREACH(t, n->meta.names, state)
SET_FOREACH(t, n->meta.names, i)
hashmap_remove_value(n->meta.manager->names, t, n);
n->meta.linked = false;
return r;
}
if (n->meta.load_state == NAME_STUB)
LIST_PREPEND(Meta, n->meta.manager->load_queue, &n->meta);
if (n->meta.load_state == NAME_STUB) {
LIST_PREPEND(Meta, load_queue, n->meta.manager->load_queue, &n->meta);
n->meta.in_load_queue = true;
}
return 0;
}
static void bidi_set_free(Name *name, Set *s) {
void *state;
Iterator i;
Name *other;
assert(name);
@ -150,7 +183,7 @@ static void bidi_set_free(Name *name, Set *s) {
/* Frees the set and makes sure we are dropped from the
* inverse pointers */
SET_FOREACH(other, s, state) {
SET_FOREACH(other, s, i) {
NameDependency d;
for (d = 0; d < _NAME_DEPENDENCY_MAX; d++)
@ -169,13 +202,13 @@ void name_free(Name *name) {
/* Detach from next 'bigger' objects */
if (name->meta.linked) {
char *t;
void *state;
Iterator i;
SET_FOREACH(t, name->meta.names, state)
SET_FOREACH(t, name->meta.names, i)
hashmap_remove_value(name->meta.manager->names, t, name);
if (name->meta.load_state == NAME_STUB)
LIST_REMOVE(Meta, name->meta.manager->load_queue, &name->meta);
if (name->meta.in_load_queue)
LIST_REMOVE(Meta, load_queue, name->meta.manager->load_queue, &name->meta);
}
/* Free data and next 'smaller' objects */
@ -185,8 +218,8 @@ void name_free(Name *name) {
for (d = 0; d < _NAME_DEPENDENCY_MAX; d++)
bidi_set_free(name, name->meta.dependencies[d]);
if (NAME_VTABLE(name)->free_hook)
NAME_VTABLE(name)->free_hook(name);
if (NAME_VTABLE(name)->done)
NAME_VTABLE(name)->done(name);
free(name->meta.description);
@ -212,13 +245,11 @@ static int ensure_in_set(Set **s, void *data) {
assert(s);
assert(data);
if (!*s)
if (!(*s = set_new(trivial_hash_func, trivial_compare_func)))
return -ENOMEM;
if ((r = set_ensure_allocated(s, trivial_hash_func, trivial_compare_func)) < 0)
return r;
if ((r = set_put(*s, data)) < 0)
if (r != -EEXIST)
return r;
return r;
return 0;
}
@ -279,7 +310,7 @@ int name_merge(Name *name, Name *other) {
/* FIXME: Does not rollback on failure! */
static int augment(Name *n) {
int r;
void* state;
Iterator i;
Name *other;
assert(n);
@ -287,29 +318,29 @@ static int augment(Name *n) {
/* Adds in the missing links to make all dependencies
* bidirectional. */
SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], state)
SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], i)
if ((r = ensure_in_set(&other->meta.dependencies[NAME_AFTER], n)) < 0)
return r;
SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], state)
SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], i)
if ((r = ensure_in_set(&other->meta.dependencies[NAME_BEFORE], n)) < 0)
return r;
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], state)
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], i)
if ((r = ensure_in_set(&other->meta.dependencies[NAME_CONFLICTS], n)) < 0)
return r;
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], state)
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], i)
if ((r = ensure_in_set(&other->meta.dependencies[NAME_REQUIRED_BY], n)) < 0)
return r;
SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], state)
SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], i)
if ((r = ensure_in_set(&other->meta.dependencies[NAME_REQUIRED_BY], n)) < 0)
return r;
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], state)
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], i)
if ((r = ensure_in_set(&other->meta.dependencies[NAME_SOFT_REQUIRED_BY], n)) < 0)
return r;
SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], state)
SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], i)
if ((r = ensure_in_set(&other->meta.dependencies[NAME_WANTED_BY], n)) < 0)
return r;
@ -331,6 +362,9 @@ int name_sanitize(Name *n) {
const char* name_id(Name *n) {
assert(n);
if (n->meta.id)
return n->meta.id;
return set_first(n->meta.names);
}
@ -372,9 +406,9 @@ void name_dump(Name *n, FILE *f, const char *prefix) {
[NAME_AFTER] = "After",
};
void *state;
char *t;
NameDependency d;
Iterator i;
assert(n);
@ -391,17 +425,16 @@ void name_dump(Name *n, FILE *f, const char *prefix) {
prefix, load_state_table[n->meta.load_state],
prefix, active_state_table[name_active_state(n)]);
SET_FOREACH(t, n->meta.names, state)
SET_FOREACH(t, n->meta.names, i)
fprintf(f, "%s\tName: %s\n", prefix, t);
for (d = 0; d < _NAME_DEPENDENCY_MAX; d++) {
void *state;
Name *other;
if (set_isempty(n->meta.dependencies[d]))
continue;
SET_FOREACH(other, n->meta.dependencies[d], state)
SET_FOREACH(other, n->meta.dependencies[d], i)
fprintf(f, "%s\t%s: %s\n", prefix, dependency_table[d], name_id(other));
}
@ -423,13 +456,13 @@ void name_dump(Name *n, FILE *f, const char *prefix) {
static int verify_type(Name *name) {
char *n;
void *state;
Iterator i;
assert(name);
/* Checks that all aliases of this name have the same and valid type */
SET_FOREACH(n, name->meta.names, state) {
SET_FOREACH(n, name->meta.names, i) {
NameType t;
if ((t = name_type_from_string(n)) == _NAME_TYPE_INVALID)
@ -472,14 +505,19 @@ int name_load(Name *name) {
assert(name);
if (name->meta.in_load_queue) {
LIST_REMOVE(Meta, load_queue, name->meta.manager->load_queue, &name->meta);
name->meta.in_load_queue = false;
}
if (name->meta.load_state != NAME_STUB)
return 0;
if ((r = verify_type(name)) < 0)
return r;
if (NAME_VTABLE(name)->load)
if ((r = NAME_VTABLE(name)->load(name)) < 0)
if (NAME_VTABLE(name)->init)
if ((r = NAME_VTABLE(name)->init(name)) < 0)
goto fail;
if ((r = name_sanitize(name)) < 0)
@ -513,8 +551,11 @@ int name_start(Name *n) {
if (NAME_IS_ACTIVE_OR_RELOADING(state))
return -EALREADY;
if (state == NAME_ACTIVATING)
return 0;
/* We don't suppress calls to ->start() here when we are
* already starting, to allow this request to be used as a
* "hurry up" call, for example when the name is in some "auto
* restart" state where it waits for a holdoff timer to elapse
* before it will start again. */
return NAME_VTABLE(n)->start(n);
}
@ -558,7 +599,7 @@ int name_reload(Name *n) {
assert(n);
if (!NAME_VTABLE(n)->reload)
if (!name_can_reload(n))
return -EBADR;
state = name_active_state(n);
@ -573,45 +614,58 @@ int name_reload(Name *n) {
bool name_type_can_reload(NameType t) {
assert(t >= 0 && t < _NAME_TYPE_MAX);
return !!name_vtable[t]->reload;
}
bool name_can_reload(Name *n) {
assert(n);
if (!name_type_can_reload(n->meta.type))
return false;
if (!NAME_VTABLE(n)->can_reload)
return true;
return NAME_VTABLE(n)->can_reload(n);
}
static void retroactively_start_dependencies(Name *n) {
void *state;
Iterator i;
Name *other;
assert(n);
assert(NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(n)));
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], state)
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], i)
if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
manager_add_job(n->meta.manager, JOB_START, other, JOB_REPLACE, true, NULL);
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], state)
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], i)
if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
manager_add_job(n->meta.manager, JOB_START, other, JOB_FAIL, false, NULL);
SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], state)
SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], i)
if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
manager_add_job(n->meta.manager, JOB_START, other, JOB_REPLACE, true, NULL);
SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], state)
SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], i)
if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
manager_add_job(n->meta.manager, JOB_START, other, JOB_FAIL, false, NULL);
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], state)
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], i)
if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
manager_add_job(n->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL);
}
static void retroactively_stop_dependencies(Name *n) {
void *state;
Iterator i;
Name *other;
assert(n);
assert(NAME_IS_INACTIVE_OR_DEACTIVATING(name_active_state(n)));
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], state)
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], i)
if (!NAME_IS_INACTIVE_OR_DEACTIVATING(name_active_state(other)))
manager_add_job(n->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL);
}
@ -626,6 +680,11 @@ void name_notify(Name *n, NameActiveState os, NameActiveState ns) {
if (os == ns)
return;
if (!NAME_IS_ACTIVE_OR_RELOADING(os) && NAME_IS_ACTIVE_OR_RELOADING(ns))
n->meta.active_enter_timestamp = now(CLOCK_REALTIME);
else if (NAME_IS_ACTIVE_OR_RELOADING(os) && !NAME_IS_ACTIVE_OR_RELOADING(ns))
n->meta.active_exit_timestamp = now(CLOCK_REALTIME);
if (n->meta.job) {
if (n->meta.job->state == JOB_WAITING)
@ -633,7 +692,7 @@ void name_notify(Name *n, NameActiveState os, NameActiveState ns) {
/* So we reached a different state for this
* job. Let's see if we can run it now if it
* failed previously due to EAGAIN. */
job_run_and_invalidate(n->meta.job);
job_schedule_run(n->meta.job);
else {
assert(n->meta.job->state == JOB_RUNNING);
@ -709,12 +768,17 @@ int name_watch_fd(Name *n, int fd, uint32_t events) {
zero(ev);
ev.data.fd = fd;
ev.data.ptr = n;
ev.data.u32 = MANAGER_FD;
ev.events = events;
if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
return -errno;
if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) >= 0)
return 0;
return 0;
if (errno == EEXIST)
if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_MOD, fd, &ev) >= 0)
return 0;
return -errno;
}
void name_unwatch_fd(Name *n, int fd) {
@ -737,3 +801,121 @@ void name_unwatch_pid(Name *n, pid_t pid) {
hashmap_remove(n->meta.manager->watch_pids, UINT32_TO_PTR(pid));
}
int name_watch_timer(Name *n, usec_t delay, int *id) {
struct epoll_event ev;
int fd;
struct itimerspec its;
int flags;
bool ours;
assert(n);
assert(id);
/* This will try to reuse the old timer if there is one */
if (*id >= 0) {
ours = false;
fd = *id;
} else {
ours = true;
if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0)
return -errno;
}
zero(its);
if (delay <= 0) {
/* Set absolute time in the past, but not 0, since we
* don't want to disarm the timer */
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 1;
flags = TFD_TIMER_ABSTIME;
} else {
timespec_store(&its.it_value, delay);
flags = 0;
}
/* This will also flush the elapse counter */
if (timerfd_settime(fd, flags, &its, NULL) < 0)
goto fail;
zero(ev);
ev.data.fd = fd;
ev.data.ptr = n;
ev.data.u32 = MANAGER_TIMER;
ev.events = POLLIN;
if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
goto fail;
*id = fd;
return 0;
fail:
if (ours)
assert_se(close_nointr(fd) == 0);
return -errno;
}
void name_unwatch_timer(Name *n, int *id) {
assert(n);
assert(id);
if (*id >= 0) {
assert_se(epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_DEL, *id, NULL) >= 0);
assert_se(close_nointr(*id) == 0);
*id = -1;
}
}
char *name_change_suffix(const char *t, const char *suffix) {
char *e, *n;
size_t a, b;
assert(t);
assert(name_is_valid(t));
assert(suffix);
assert_se(e = strrchr(t, '.'));
a = e - t;
b = strlen(suffix);
if (!(n = new(char, a + b + 1)))
return NULL;
memcpy(n, t, a);
memcpy(n+a, t, b+1);
return n;
}
bool name_job_is_applicable(Name *n, JobType j) {
assert(n);
assert(j >= 0 && j < _JOB_TYPE_MAX);
switch (j) {
case JOB_VERIFY_ACTIVE:
case JOB_START:
return true;
case JOB_STOP:
case JOB_RESTART:
case JOB_TRY_RESTART:
return name_can_start(n);
case JOB_RELOAD:
return name_can_reload(n);
case JOB_RELOAD_OR_START:
return name_can_reload(n) && name_can_start(n);
default:
assert_not_reached("Invalid job type");
}
}

36
name.h
View File

@ -21,8 +21,11 @@ typedef enum NameDependency NameDependency;
#include "list.h"
#include "socket-util.h"
#include "execute.h"
#include "util.h"
#define NAME_MAX 32
#define DEFAULT_TIMEOUT_USEC (20*USEC_PER_SEC)
#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
enum NameType {
NAME_SERVICE = 0,
@ -90,6 +93,8 @@ struct Meta {
NameType type;
NameLoadState load_state;
char *id; /* One name is special because we use it for identification. Points to an entry in the names set */
Set *names;
Set *dependencies[_NAME_DEPENDENCY_MAX];
@ -100,9 +105,13 @@ struct Meta {
Job *job;
bool linked:1;
bool in_load_queue:1;
usec_t active_enter_timestamp;
usec_t active_exit_timestamp;
/* Load queue */
LIST_FIELDS(Meta);
LIST_FIELDS(Meta, load_queue);
};
#include "service.h"
@ -129,21 +138,27 @@ union Name {
struct NameVTable {
const char *suffix;
int (*load)(Name *n);
int (*init)(Name *n);
void (*done)(Name *n);
void (*dump)(Name *n, FILE *f, const char *prefix);
int (*start)(Name *n);
int (*stop)(Name *n);
int (*reload)(Name *n);
bool (*can_reload)(Name *n);
/* Boils down the more complex internal state of this name to
* a simpler one that the engine can understand */
NameActiveState (*active_state)(Name *n);
void (*fd_event)(Name *n, int fd, uint32_t events);
void (*sigchld_event)(Name *n, pid_t pid, int code, int status);
void (*timer_event)(Name *n, int id, uint64_t n_elapsed);
void (*free_hook)(Name *n);
void (*retry)(Name *n);
};
extern const NameVTable * const name_vtable[_NAME_TYPE_MAX];
@ -171,10 +186,10 @@ DEFINE_CAST(MOUNT, Mount);
DEFINE_CAST(AUTOMOUNT, Automount);
DEFINE_CAST(SNAPSHOT, Snapshot);
NameActiveState name_active_state(Name *name);
bool name_type_can_start(NameType t);
bool name_type_can_reload(NameType t);
bool name_can_reload(Name *n);
#define name_can_start(n) name_type_can_start((n)->meta.type)
NameType name_type_from_string(const char *n);
bool name_is_valid(const char *n);
@ -190,6 +205,10 @@ int name_load(Name *name);
const char* name_id(Name *n);
const char *name_description(Name *n);
int name_add_name(Name *n, const char *text);
NameActiveState name_active_state(Name *name);
void name_dump(Name *n, FILE *f, const char *prefix);
int name_start(Name *n);
@ -204,4 +223,11 @@ void name_unwatch_fd(Name *n, int fd);
int name_watch_pid(Name *n, pid_t pid);
void name_unwatch_pid(Name *n, pid_t pid);
int name_watch_timer(Name *n, usec_t delay, int *id);
void name_unwatch_timer(Name *n, int *id);
char *name_change_suffix(const char *t, const char *suffix);
bool name_job_is_applicable(Name *n, JobType j);
#endif

824
service.c
View File

@ -1,11 +1,30 @@
/*-*- Mode: C; c-basic-offset: 8 -*-*/
#include <errno.h>
#include <signal.h>
#include "name.h"
#include "service.h"
#include "load-fragment.h"
#include "load-dropin.h"
#include "log.h"
static const NameActiveState state_table[_SERVICE_STATE_MAX] = {
[SERVICE_DEAD] = NAME_INACTIVE,
[SERVICE_START_PRE] = NAME_ACTIVATING,
[SERVICE_START] = NAME_ACTIVATING,
[SERVICE_START_POST] = NAME_ACTIVATING,
[SERVICE_RUNNING] = NAME_ACTIVE,
[SERVICE_RELOAD] = NAME_ACTIVE_RELOADING,
[SERVICE_STOP] = NAME_DEACTIVATING,
[SERVICE_STOP_SIGTERM] = NAME_DEACTIVATING,
[SERVICE_STOP_SIGKILL] = NAME_DEACTIVATING,
[SERVICE_STOP_POST] = NAME_DEACTIVATING,
[SERVICE_FINAL_SIGTERM] = NAME_DEACTIVATING,
[SERVICE_FINAL_SIGKILL] = NAME_DEACTIVATING,
[SERVICE_MAINTAINANCE] = NAME_INACTIVE,
[SERVICE_AUTO_RESTART] = NAME_ACTIVATING,
};
static int service_load_sysv(Service *s) {
assert(s);
@ -16,13 +35,26 @@ static int service_load_sysv(Service *s) {
return -ENOENT;
}
static int service_load(Name *n) {
static int service_init(Name *n) {
int r;
Service *s = SERVICE(n);
assert(s);
exec_context_defaults(&s->exec_context);
/* First, reset everything to the defaults, in case this is a
* reload */
s->type = 0;
s->restart = 0;
s->timeout_usec = DEFAULT_TIMEOUT_USEC;
s->restart_usec = DEFAULT_RESTART_USEC;
exec_context_init(&s->exec_context);
s->timer_id = -1;
s->state = SERVICE_DEAD;
/* Load a .service file */
r = name_load_fragment(n);
@ -41,6 +73,33 @@ static int service_load(Name *n) {
return 0;
}
static void service_done(Name *n) {
Service *s = SERVICE(n);
assert(s);
free(s->pid_file);
s->pid_file = NULL;
exec_context_done(&s->exec_context);
exec_command_free_array(s->exec_command, _SERVICE_EXEC_MAX);
s->control_command = NULL;
/* This will leak a process, but at least no memory or any of
* our resources */
if (s->main_pid > 0) {
name_unwatch_pid(n, s->main_pid);
s->main_pid = 0;
}
if (s->control_pid > 0) {
name_unwatch_pid(n, s->control_pid);
s->control_pid = 0;
}
name_unwatch_timer(n, &s->timer_id);
}
static void service_dump(Name *n, FILE *f, const char *prefix) {
static const char* const state_table[_SERVICE_STATE_MAX] = {
@ -49,27 +108,24 @@ static void service_dump(Name *n, FILE *f, const char *prefix) {
[SERVICE_START] = "start",
[SERVICE_START_POST] = "post",
[SERVICE_RUNNING] = "running",
[SERVICE_RELOAD_PRE] = "reload-pre",
[SERVICE_RELOAD] = "reload",
[SERVICE_RELOAD_POST] = "reload-post",
[SERVICE_STOP_PRE] = "stop-pre",
[SERVICE_STOP] = "stop",
[SERVICE_SIGTERM] = "sigterm",
[SERVICE_SIGKILL] = "sigkill",
[SERVICE_STOP_SIGTERM] = "stop-sigterm",
[SERVICE_STOP_SIGKILL] = "stop-sigkill",
[SERVICE_STOP_POST] = "stop-post",
[SERVICE_MAINTAINANCE] = "maintainance"
[SERVICE_FINAL_SIGTERM] = "final-sigterm",
[SERVICE_FINAL_SIGKILL] = "final-sigkill",
[SERVICE_MAINTAINANCE] = "maintainance",
[SERVICE_AUTO_RESTART] = "auto-restart",
};
static const char* const command_table[_SERVICE_EXEC_MAX] = {
[SERVICE_EXEC_START_PRE] = "StartPre",
[SERVICE_EXEC_START] = "Start",
[SERVICE_EXEC_START_POST] = "StartPost",
[SERVICE_EXEC_RELOAD_PRE] = "ReloadPre",
[SERVICE_EXEC_RELOAD] = "Reload",
[SERVICE_EXEC_RELOAD_POST] = "ReloadPost",
[SERVICE_EXEC_STOP_PRE] = "StopPre",
[SERVICE_EXEC_STOP] = "Stop",
[SERVICE_EXEC_STOP_POST] = "StopPost",
[SERVICE_EXEC_START_PRE] = "ExecStartPre",
[SERVICE_EXEC_START] = "ExecStart",
[SERVICE_EXEC_START_POST] = "ExecStartPost",
[SERVICE_EXEC_RELOAD] = "ExecReload",
[SERVICE_EXEC_STOP] = "ExecStop",
[SERVICE_EXEC_STOP_POST] = "ExecStopPost",
};
ServiceExecCommand c;
@ -81,39 +137,437 @@ static void service_dump(Name *n, FILE *f, const char *prefix) {
"%sService State: %s\n",
prefix, state_table[s->state]);
if (s->pid_file)
fprintf(f,
"%sPIDFile: %s\n",
prefix, s->pid_file);
exec_context_dump(&s->exec_context, f, prefix);
for (c = 0; c < _SERVICE_EXEC_MAX; c++) {
ExecCommand *i;
LIST_FOREACH(i, s->exec_command[c])
LIST_FOREACH(command, i, s->exec_command[c])
fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path);
}
}
static int service_set_state(Service *s, ServiceState state) {
static int service_load_pid_file(Service *s) {
char *k;
unsigned long p;
int r;
assert(s);
s->state = state;
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) {
Service *s = SERVICE(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;
return service_set_state(s, SERVICE_START_PRE);
assert(s->state == SERVICE_DEAD || s->state == SERVICE_MAINTAINANCE || s->state == SERVICE_AUTO_RESTART);
s->failure = false;
s->main_pid_known = false;
service_enter_start_pre(s);
return 0;
}
static int service_stop(Name *n) {
@ -121,62 +575,308 @@ static int service_stop(Name *n) {
assert(s);
if (s->state == SERVICE_START_PRE ||
s->state == SERVICE_START ||
s->state == SERVICE_START_POST ||
s->state == SERVICE_RELOAD)
return -EAGAIN;
if (s->state == SERVICE_AUTO_RESTART) {
service_set_state(s, SERVICE_DEAD);
return 0;
}
assert(s->state == SERVICE_RUNNING);
service_enter_stop(s, true);
return 0;
}
static int service_reload(Name *n) {
return 0;
}
static NameActiveState service_active_state(Name *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 table[SERVICE(n)->state];
}
static void service_free_hook(Name *n) {
Service *s = SERVICE(n);
unsigned c;
assert(s);
exec_context_free(&s->exec_context);
assert(s->state == SERVICE_RUNNING);
for (c = 0; c < _SERVICE_EXEC_MAX; c++)
exec_command_free_list(s->exec_command[c]);
service_enter_reload(s);
return 0;
}
if (s->socket)
s->socket->service = NULL;
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);
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 s->control_pid > 0;
}
static void service_sigchld_event(Name *n, pid_t pid, int code, int status) {
Service *s = SERVICE(n);
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);
/* If there is still a control process, wait for that first */
break;
default:
assert_not_reached("Uh, main process died at wrong time.");
}
} 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,
};

View File

@ -6,8 +6,6 @@
typedef struct Service Service;
#include "name.h"
#include "socket.h"
#include "timer.h"
typedef enum ServiceState {
SERVICE_DEAD,
@ -15,31 +13,34 @@ typedef enum ServiceState {
SERVICE_START,
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_RELOAD_PRE,
SERVICE_RELOAD,
SERVICE_RELOAD_POST,
SERVICE_STOP_PRE,
SERVICE_STOP,
SERVICE_SIGTERM,
SERVICE_SIGKILL,
SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */
SERVICE_STOP_SIGTERM,
SERVICE_STOP_SIGKILL,
SERVICE_STOP_POST,
SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */
SERVICE_FINAL_SIGKILL,
SERVICE_MAINTAINANCE,
SERVICE_AUTO_RESTART,
_SERVICE_STATE_MAX,
} ServiceState;
typedef enum ServiceMode {
typedef enum ServiceRestart {
SERVICE_ONCE,
SERVICE_RESTART
} ServiceMode;
SERVICE_RESTART_ON_SUCCESS,
SERVICE_RESTART_ALWAYS
} ServiceRestart;
typedef enum ServiceType {
SERVICE_FORKING,
SERVICE_SIMPLE
} ServiceType;
typedef enum ServiceExecCommand {
SERVICE_EXEC_START_PRE,
SERVICE_EXEC_START,
SERVICE_EXEC_START_POST,
SERVICE_EXEC_RELOAD_PRE,
SERVICE_EXEC_RELOAD,
SERVICE_EXEC_RELOAD_POST,
SERVICE_EXEC_STOP_PRE,
SERVICE_EXEC_STOP,
SERVICE_EXEC_STOP_POST,
_SERVICE_EXEC_MAX
@ -48,16 +49,28 @@ typedef enum ServiceExecCommand {
struct Service {
Meta meta;
ServiceState state;
ServiceMode mode;
ServiceType type;
ServiceRestart restart;
/* If set we'll read the main daemon PID from this file */
char *pid_file;
usec_t restart_usec;
usec_t timeout_usec;
ExecCommand* exec_command[_SERVICE_EXEC_MAX];
ExecContext exec_context;
pid_t service_pid, control_pid;
ServiceState state;
Socket *socket;
Timer *timer;
ExecStatus main_exec_status;
ExecCommand *control_command;
pid_t main_pid, control_pid;
bool main_pid_known:1;
bool failure:1; /* if we shut down, remember why */
int timer_id;
};
const NameVTable service_vtable;

16
set.c
View File

@ -22,6 +22,10 @@ void set_free(Set* s) {
hashmap_free(MAKE_HASHMAP(s));
}
int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func) {
return hashmap_ensure_allocated((Hashmap**) s, hash_func, compare_func);
}
int set_put(Set *s, void *value) {
return hashmap_put(MAKE_HASHMAP(s), value, value);
}
@ -46,12 +50,16 @@ bool set_isempty(Set *s) {
return hashmap_isempty(MAKE_HASHMAP(s));
}
void *set_iterate(Set *s, void **state) {
return hashmap_iterate(MAKE_HASHMAP(s), state, NULL);
void *set_iterate(Set *s, Iterator *i) {
return hashmap_iterate(MAKE_HASHMAP(s), i, NULL);
}
void *set_iterate_backwards(Set *s, void **state) {
return hashmap_iterate_backwards(MAKE_HASHMAP(s), state, NULL);
void *set_iterate_backwards(Set *s, Iterator *i) {
return hashmap_iterate_backwards(MAKE_HASHMAP(s), i, 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) {

16
set.h
View File

@ -14,8 +14,9 @@
typedef struct Set Set;
Set *set_new(hash_func_t hash_func, compare_func_t compare_func);
Set* set_copy(Set *s);
void set_free(Set* s);
Set* set_copy(Set *s);
int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func);
int set_put(Set *s, void *value);
int set_replace(Set *s, void *value);
@ -27,18 +28,19 @@ int set_merge(Set *s, Set *other);
unsigned set_size(Set *s);
bool set_isempty(Set *s);
void *set_iterate(Set *s, void **state);
void *set_iterate_backwards(Set *s, void **state);
void *set_iterate(Set *s, Iterator *i);
void *set_iterate_backwards(Set *s, Iterator *i);
void *set_iterate_skip(Set *s, void *value, Iterator *i);
void set_clear(Set *s);
void *set_steal_first(Set *s);
void* set_first(Set *s);
void* set_last(Set *s);
#define SET_FOREACH(e, s, state) \
for ((state) = NULL, (e) = set_iterate((s), &(state)); (e); (e) = set_iterate((s), &(state)))
#define SET_FOREACH(e, s, i) \
for ((i) = ITERATOR_FIRST, (e) = set_iterate((s), &(i)); (e); (e) = set_iterate((s), &(i)))
#define SET_FOREACH_BACKWARDS(e, s, state) \
for ((state) = NULL, (e) = set_iterate_backwards((s), &(state)); (e); (e) = set_iterate_backwards((s), &(state)))
#define SET_FOREACH_BACKWARDS(e, s, i) \
for ((i) = ITERATOR_LAST, (e) = set_iterate_backwards((s), &(i)); (e); (e) = set_iterate_backwards((s), &(i)))
#endif

View File

@ -3,11 +3,7 @@
#include "name.h"
#include "snapshot.h"
static NameActiveState snapshot_active_state(Name *n) {
return SNAPSHOT(n)->state == SNAPSHOT_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
}
static void snapshot_free_hook(Name *n) {
static void snapshot_done(Name *n) {
Snapshot *s = SNAPSHOT(n);
assert(s);
@ -15,17 +11,14 @@ static void snapshot_free_hook(Name *n) {
/* Nothing here for now */
}
static NameActiveState snapshot_active_state(Name *n) {
return SNAPSHOT(n)->state == SNAPSHOT_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
}
const NameVTable snapshot_vtable = {
.suffix = ".snapshot",
.load = NULL,
.dump = NULL,
.done = snapshot_done,
.start = NULL,
.stop = NULL,
.reload = NULL,
.active_state = snapshot_active_state,
.free_hook = snapshot_free_hook
.active_state = snapshot_active_state
};

605
socket.c
View File

@ -6,6 +6,7 @@
#include <errno.h>
#include <fcntl.h>
#include <sys/poll.h>
#include <signal.h>
#include "name.h"
#include "socket.h"
@ -18,17 +19,75 @@ static const NameActiveState state_table[_SOCKET_STATE_MAX] = {
[SOCKET_LISTENING] = NAME_ACTIVE,
[SOCKET_RUNNING] = NAME_ACTIVE,
[SOCKET_STOP_PRE] = NAME_DEACTIVATING,
[SOCKET_STOP_PRE_SIGTERM] = NAME_DEACTIVATING,
[SOCKET_STOP_PRE_SIGKILL] = NAME_DEACTIVATING,
[SOCKET_STOP_POST] = NAME_DEACTIVATING,
[SOCKET_STOP_POST_SIGTERM] = NAME_DEACTIVATING,
[SOCKET_STOP_POST_SIGKILL] = NAME_DEACTIVATING,
[SOCKET_MAINTAINANCE] = NAME_INACTIVE,
};
static int socket_load(Name *n) {
static int socket_init(Name *n) {
Socket *s = SOCKET(n);
char *t;
int r;
exec_context_defaults(&s->exec_context);
/* First, reset everything to the defaults, in case this is a
* reload */
s->bind_ipv6_only = false;
s->backlog = SOMAXCONN;
s->timeout_usec = DEFAULT_TIMEOUT_USEC;
exec_context_init(&s->exec_context);
return name_load_fragment_and_dropin(n);
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);
if (r < 0)
return r;
if ((r = set_ensure_allocated(n->meta.dependencies + NAME_BEFORE, trivial_hash_func, trivial_compare_func)) < 0)
return r;
if ((r = set_put(n->meta.dependencies[NAME_BEFORE], s->service)) < 0)
return r;
return 0;
}
static void socket_done(Name *n) {
Socket *s = SOCKET(n);
SocketPort *p;
assert(s);
while ((p = s->ports)) {
LIST_REMOVE(SocketPort, port, s->ports, p);
if (p->fd >= 0)
close_nointr(p->fd);
free(p->path);
free(p);
}
exec_context_done(&s->exec_context);
exec_command_free_array(s->exec_command, _SOCKET_EXEC_MAX);
s->control_command = NULL;
if (s->control_pid > 0) {
name_unwatch_pid(n, s->control_pid);
s->control_pid = 0;
}
s->service = NULL;
name_unwatch_timer(n, &s->timer_id);
}
static const char* listen_lookup(int type) {
@ -40,7 +99,7 @@ static const char* listen_lookup(int type) {
else if (type == SOCK_SEQPACKET)
return "ListenSequentialPacket";
assert_not_reached("Unkown socket type");
assert_not_reached("Unknown socket type");
return NULL;
}
@ -53,7 +112,11 @@ static void socket_dump(Name *n, FILE *f, const char *prefix) {
[SOCKET_LISTENING] = "listening",
[SOCKET_RUNNING] = "running",
[SOCKET_STOP_PRE] = "stop-pre",
[SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm",
[SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill",
[SOCKET_STOP_POST] = "stop-post",
[SOCKET_STOP_POST_SIGTERM] = "stop-post-sigterm",
[SOCKET_STOP_POST_SIGKILL] = "stop-post-sigkill",
[SOCKET_MAINTAINANCE] = "maintainance"
};
@ -78,7 +141,7 @@ static void socket_dump(Name *n, FILE *f, const char *prefix) {
prefix, yes_no(s->bind_ipv6_only),
prefix, s->backlog);
LIST_FOREACH(p, s->ports) {
LIST_FOREACH(port, p, s->ports) {
if (p->type == SOCKET_SOCKET) {
const char *t;
@ -101,27 +164,17 @@ static void socket_dump(Name *n, FILE *f, const char *prefix) {
for (c = 0; c < _SOCKET_EXEC_MAX; c++) {
ExecCommand *i;
LIST_FOREACH(i, s->exec_command[c])
LIST_FOREACH(command, i, s->exec_command[c])
fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path);
}
}
static void socket_set_state(Socket *s, SocketState state) {
SocketState old_state;
assert(s);
old_state = s->state;
s->state = state;
name_notify(NAME(s), state_table[old_state], state_table[s->state]);
}
static void close_fds(Socket *s) {
static void socket_close_fds(Socket *s) {
SocketPort *p;
assert(s);
LIST_FOREACH(p, s->ports) {
LIST_FOREACH(port, p, s->ports) {
if (p->fd < 0)
continue;
@ -132,30 +185,16 @@ static void close_fds(Socket *s) {
}
}
static int socket_start(Name *n) {
Socket *s = SOCKET(n);
static int socket_open_fds(Socket *s) {
SocketPort *p;
int r;
assert(s);
if (s->state == SOCKET_START_PRE ||
s->state == SOCKET_START_POST)
return 0;
LIST_FOREACH(port, p, s->ports) {
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) {
assert(p->fd < 0);
if (p->fd >= 0)
continue;
if (p->type == SOCKET_SOCKET) {
@ -188,46 +227,352 @@ static int socket_start(Name *n) {
goto rollback;
}
}
if ((r = name_watch_fd(n, p->fd, POLLIN)) < 0)
goto rollback;
}
socket_set_state(s, SOCKET_LISTENING);
return 0;
rollback:
close_fds(s);
socket_close_fds(s);
return r;
}
socket_set_state(s, SOCKET_MAINTAINANCE);
static void socket_unwatch_fds(Socket *s) {
SocketPort *p;
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);
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;
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;
*_pid = pid;
return 0;
fail:
if (timeout)
name_unwatch_timer(NAME(s), &s->timer_id);
return r;
}
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 0;
/* Cannot run this without the service being around */
if (s->service->meta.load_state != NAME_LOADED)
return -ENOENT;
assert(s->state == SOCKET_DEAD || s->state == SOCKET_MAINTAINANCE);
s->failure = false;
socket_enter_start_pre(s);
return 0;
}
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;
if (s->state == SOCKET_DEAD ||
s->state == SOCKET_MAINTAINANCE)
return -EALREADY;
if (s->state == SOCKET_STOP_PRE ||
s->state == SOCKET_STOP_POST)
return 0;
assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING);
close_fds(s);
socket_set_state(s, SOCKET_DEAD);
socket_enter_stop_pre(s, true);
return 0;
}
@ -240,58 +585,140 @@ static NameActiveState socket_active_state(Name *n) {
static void socket_fd_event(Name *n, int fd, uint32_t events) {
Socket *s = SOCKET(n);
assert(n);
if (events != POLLIN)
goto fail;
log_info("POLLIN on %s", name_id(n));
return;
fail:
close_fds(s);
socket_set_state(s, SOCKET_MAINTAINANCE);
}
static void socket_free_hook(Name *n) {
SocketExecCommand c;
Socket *s = SOCKET(n);
SocketPort *p;
assert(s);
while ((p = s->ports)) {
LIST_REMOVE(SocketPort, s->ports, p);
log_info("Incoming traffic on %s", name_id(n));
if (p->fd >= 0)
close_nointr(p->fd);
free(p->path);
free(p);
if (events != POLLIN)
socket_enter_stop_pre(s, false);
socket_enter_running(s);
}
static void socket_sigchld_event(Name *n, pid_t pid, int code, int status) {
Socket *s = SOCKET(n);
bool success;
assert(s);
assert(pid >= 0);
success = code == CLD_EXITED || status == 0;
s->failure = s->failure || !success;
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 */
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.");
}
}
}
exec_context_free(&s->exec_context);
static void socket_timer_event(Name *n, int id, uint64_t elapsed) {
Socket *s = SOCKET(n);
for (c = 0; c < _SOCKET_EXEC_MAX; c++)
exec_command_free_list(s->exec_command[c]);
assert(s);
assert(elapsed == 1);
if (s->service)
s->service->socket = NULL;
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
};

View File

@ -15,7 +15,11 @@ typedef enum SocketState {
SOCKET_LISTENING,
SOCKET_RUNNING,
SOCKET_STOP_PRE,
SOCKET_STOP_PRE_SIGTERM,
SOCKET_STOP_PRE_SIGKILL,
SOCKET_STOP_POST,
SOCKET_STOP_POST_SIGTERM,
SOCKET_STOP_POST_SIGKILL,
SOCKET_MAINTAINANCE,
_SOCKET_STATE_MAX
} SocketState;
@ -43,26 +47,32 @@ struct SocketPort {
int fd;
LIST_FIELDS(SocketPort);
LIST_FIELDS(SocketPort, port);
};
struct Socket {
Meta meta;
SocketState state;
LIST_HEAD(SocketPort, ports);
/* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */
bool bind_ipv6_only;
unsigned backlog;
usec_t timeout_usec;
ExecCommand* exec_command[_SOCKET_EXEC_MAX];
ExecContext exec_context;
Service *service;
SocketState state;
ExecCommand* control_command;
pid_t control_pid;
Service *service;
bool failure;
int timer_id;
};
extern const NameVTable socket_vtable;

30
strv.c
View File

@ -115,3 +115,33 @@ fail:
free(a);
return NULL;
}
char **strv_merge(char **a, char **b) {
char **r, **k;
if (!a)
return strv_copy(b);
if (!b)
return strv_copy(a);
if (!(r = new(char*, strv_length(a)+strv_length(b)+1)))
return NULL;
for (k = r; *a; k++, a++)
if (!(*k = strdup(*a)))
goto fail;
for (; *b; k++, b++)
if (!(*k = strdup(*b)))
goto fail;
*k = NULL;
return r;
fail:
for (k--; k >= r; k--)
free(*k);
return NULL;
}

2
strv.h
View File

@ -10,6 +10,8 @@ void strv_free(char **l);
char **strv_copy(char **l);
unsigned strv_length(char **l);
char **strv_merge(char **a, char **b);
char **strv_new(const char *x, ...) __sentinel;
#define STRV_FOREACH(s, l) \

27
timer.c
View File

@ -3,6 +3,12 @@
#include "name.h"
#include "timer.h"
static void timer_done(Name *n) {
Timer *t = TIMER(n);
assert(t);
}
static NameActiveState timer_active_state(Name *n) {
static const NameActiveState table[_TIMER_STATE_MAX] = {
@ -14,26 +20,11 @@ static NameActiveState timer_active_state(Name *n) {
return table[TIMER(n)->state];
}
static void timer_free_hook(Name *n) {
Timer *t = TIMER(n);
assert(t);
if (t->service)
t->service->timer = NULL;
}
const NameVTable timer_vtable = {
.suffix = ".timer",
.load = name_load_fragment_and_dropin,
.dump = NULL,
.init = name_load_fragment_and_dropin,
.done = timer_done,
.start = NULL,
.stop = NULL,
.reload = NULL,
.active_state = timer_active_state,
.free_hook = timer_free_hook
.active_state = timer_active_state
};

228
util.c
View File

@ -5,6 +5,8 @@
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include "macro.h"
#include "util.h"
@ -108,7 +110,7 @@ int parse_boolean(const char *v) {
int safe_atou(const char *s, unsigned *ret_u) {
char *x = NULL;
unsigned l;
unsigned long l;
assert(s);
assert(ret_u);
@ -119,7 +121,7 @@ int safe_atou(const char *s, unsigned *ret_u) {
if (!x || *x || errno)
return errno ? -errno : -EINVAL;
if ((unsigned) l != l)
if ((unsigned long) (unsigned) l != l)
return -ERANGE;
*ret_u = (unsigned) l;
@ -128,7 +130,7 @@ int safe_atou(const char *s, unsigned *ret_u) {
int safe_atoi(const char *s, int *ret_i) {
char *x = NULL;
int l;
long l;
assert(s);
assert(ret_i);
@ -139,10 +141,78 @@ int safe_atoi(const char *s, int *ret_i) {
if (!x || *x || errno)
return errno ? -errno : -EINVAL;
if ((int) l != l)
if ((long) (int) l != l)
return -ERANGE;
*ret_i = (unsigned) l;
*ret_i = (int) l;
return 0;
}
int safe_atolu(const char *s, long unsigned *ret_lu) {
char *x = NULL;
unsigned long l;
assert(s);
assert(ret_lu);
errno = 0;
l = strtoul(s, &x, 0);
if (!x || *x || errno)
return errno ? -errno : -EINVAL;
*ret_lu = l;
return 0;
}
int safe_atoli(const char *s, long int *ret_li) {
char *x = NULL;
long l;
assert(s);
assert(ret_li);
errno = 0;
l = strtol(s, &x, 0);
if (!x || *x || errno)
return errno ? -errno : -EINVAL;
*ret_li = l;
return 0;
}
int safe_atollu(const char *s, long long unsigned *ret_llu) {
char *x = NULL;
unsigned long long l;
assert(s);
assert(ret_llu);
errno = 0;
l = strtoull(s, &x, 0);
if (!x || *x || errno)
return errno ? -errno : -EINVAL;
*ret_llu = l;
return 0;
}
int safe_atolli(const char *s, long long int *ret_lli) {
char *x = NULL;
long long l;
assert(s);
assert(ret_lli);
errno = 0;
l = strtoll(s, &x, 0);
if (!x || *x || errno)
return errno ? -errno : -EINVAL;
*ret_lli = l;
return 0;
}
@ -164,3 +234,151 @@ char *split_spaces(const char *c, size_t *l, char **state) {
return (char*) current;
}
/* Split a string into words, but consider strings enclosed in '' and
* "" as words even if they include spaces. */
char *split_quoted(const char *c, size_t *l, char **state) {
char *current;
current = *state ? *state : (char*) c;
if (!*current || *c == 0)
return NULL;
current += strspn(current, WHITESPACE);
if (*current == '\'') {
current ++;
*l = strcspn(current, "'");
*state = current+*l;
if (**state == '\'')
(*state)++;
} else if (*current == '\"') {
current ++;
*l = strcspn(current+1, "\"");
*state = current+*l;
if (**state == '\"')
(*state)++;
} else {
*l = strcspn(current, WHITESPACE);
*state = current+*l;
}
return (char*) current;
}
const char *sigchld_code(int code) {
if (code == CLD_EXITED)
return "exited";
else if (code == CLD_KILLED)
return "killed";
else if (code == CLD_DUMPED)
return "dumped";
else if (code == CLD_TRAPPED)
return "trapped";
else if (code == CLD_STOPPED)
return "stopped";
else if (code == CLD_CONTINUED)
return "continued";
return "unknown";
}
int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
int r;
FILE *f;
char fn[132], line[256], *p;
long long unsigned ppid;
assert(pid >= 0);
assert(_ppid);
assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%llu/stat", (unsigned long long) pid) < (int) (sizeof(fn)-1));
fn[sizeof(fn)-1] = 0;
if (!(f = fopen(fn, "r")))
return -errno;
if (!(fgets(line, sizeof(line), f))) {
r = -errno;
fclose(f);
return r;
}
fclose(f);
/* Let's skip the pid and comm fields. The latter is enclosed
* in () but does not escape any () in its value, so let's
* skip over it manually */
if (!(p = strrchr(line, ')')))
return -EIO;
p++;
if (sscanf(p, " "
"%*c " /* state */
"%llu ", /* ppid */
&ppid) != 1)
return -EIO;
if ((long long unsigned) (pid_t) ppid != ppid)
return -ERANGE;
*_ppid = (pid_t) ppid;
return 0;
}
int write_one_line_file(const char *fn, const char *line) {
FILE *f;
int r;
assert(fn);
assert(line);
if (!(f = fopen(fn, "we")))
return -errno;
if (fputs(line, f) < 0) {
r = -errno;
goto finish;
}
r = 0;
finish:
fclose(f);
return r;
}
int read_one_line_file(const char *fn, char **line) {
FILE *f;
int r;
char t[64], *c;
assert(fn);
assert(line);
if (!(f = fopen(fn, "re")))
return -errno;
if (!(fgets(t, sizeof(t), f))) {
r = -errno;
goto finish;
}
if (!(c = strdup(t))) {
r = -ENOMEM;
goto finish;
}
*line = c;
r = 0;
finish:
fclose(f);
return r;
}

27
util.h
View File

@ -11,7 +11,11 @@
typedef uint64_t usec_t;
#define USEC_PER_SEC 1000000ULL
#define MSEC_PER_SEC 1000ULL
#define USEC_PER_SEC 1000000ULL
#define USEC_PER_MSEC 1000ULL
#define NSEC_PER_SEC 1000000000ULL
#define NSEC_PER_MSEC 1000000ULL
#define NSEC_PER_USEC 1000ULL
usec_t now(clockid_t clock);
@ -60,9 +64,26 @@ int parse_boolean(const char *v);
int safe_atou(const char *s, unsigned *ret_u);
int safe_atoi(const char *s, int *ret_i);
char *split_spaces(const char *c, size_t *l, char **state);
int safe_atolu(const char *s, unsigned long *ret_u);
int safe_atoli(const char *s, long int *ret_i);
#define FOREACH_WORD(word, length, s, state) \
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) \
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