mirror of
https://github.com/systemd/systemd.git
synced 2025-02-23 13:57:33 +03:00
first attempt at proper service/socket logic
This commit is contained in:
parent
9152c76506
commit
034c6ed7da
32
automount.c
32
automount.c
@ -8,13 +8,13 @@
|
||||
#include "load-fstab.h"
|
||||
#include "load-dropin.h"
|
||||
|
||||
static int automount_load(Name *n) {
|
||||
static int automount_init(Name *n) {
|
||||
int r;
|
||||
Automount *a = AUTOMOUNT(n);
|
||||
|
||||
assert(a);
|
||||
|
||||
exec_context_defaults(&a->exec_context);
|
||||
exec_context_init(&a->exec_context);
|
||||
|
||||
/* Load a .automount file */
|
||||
if ((r = name_load_fragment(n)) < 0 && errno != -ENOENT)
|
||||
@ -31,6 +31,13 @@ static int automount_load(Name *n) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void automount_done(Name *n) {
|
||||
Automount *d = AUTOMOUNT(n);
|
||||
|
||||
assert(d);
|
||||
free(d->path);
|
||||
}
|
||||
|
||||
static void automount_dump(Name *n, FILE *f, const char *prefix) {
|
||||
|
||||
static const char* const state_table[_AUTOMOUNT_STATE_MAX] = {
|
||||
@ -67,7 +74,7 @@ static void automount_dump(Name *n, FILE *f, const char *prefix) {
|
||||
for (c = 0; c < _AUTOMOUNT_EXEC_MAX; c++) {
|
||||
ExecCommand *i;
|
||||
|
||||
LIST_FOREACH(i, s->exec_command[c])
|
||||
LIST_FOREACH(command, i, s->exec_command[c])
|
||||
fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path);
|
||||
}
|
||||
}
|
||||
@ -88,24 +95,13 @@ static NameActiveState automount_active_state(Name *n) {
|
||||
return table[AUTOMOUNT(n)->state];
|
||||
}
|
||||
|
||||
static void automount_free_hook(Name *n) {
|
||||
Automount *d = AUTOMOUNT(n);
|
||||
|
||||
assert(d);
|
||||
free(d->path);
|
||||
}
|
||||
|
||||
const NameVTable automount_vtable = {
|
||||
.suffix = ".mount",
|
||||
|
||||
.load = automount_load,
|
||||
.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
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
25
device.c
25
device.c
@ -4,6 +4,13 @@
|
||||
#include "device.h"
|
||||
#include "strv.h"
|
||||
|
||||
static void device_done(Name *n) {
|
||||
Device *d = DEVICE(n);
|
||||
|
||||
assert(d);
|
||||
strv_free(d->sysfs);
|
||||
}
|
||||
|
||||
static void device_dump(Name *n, FILE *f, const char *prefix) {
|
||||
|
||||
static const char* const state_table[_DEVICE_STATE_MAX] = {
|
||||
@ -24,24 +31,12 @@ static NameActiveState device_active_state(Name *n) {
|
||||
return DEVICE(n)->state == DEVICE_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
|
||||
}
|
||||
|
||||
static void device_free_hook(Name *n) {
|
||||
Device *d = DEVICE(n);
|
||||
|
||||
assert(d);
|
||||
strv_free(d->sysfs);
|
||||
}
|
||||
|
||||
const NameVTable device_vtable = {
|
||||
.suffix = ".device",
|
||||
|
||||
.load = name_load_fragment_and_dropin,
|
||||
.init = name_load_fragment_and_dropin,
|
||||
.done = device_done,
|
||||
.dump = device_dump,
|
||||
|
||||
.start = NULL,
|
||||
.stop = NULL,
|
||||
.reload = NULL,
|
||||
|
||||
.active_state = device_active_state,
|
||||
|
||||
.free_hook = device_free_hook
|
||||
.active_state = device_active_state
|
||||
};
|
||||
|
246
execute.c
246
execute.c
@ -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);
|
||||
}
|
||||
|
48
execute.h
48
execute.h
@ -14,10 +14,11 @@ typedef struct ExecContext ExecContext;
|
||||
#include <stdio.h>
|
||||
|
||||
#include "list.h"
|
||||
#include "util.h"
|
||||
|
||||
struct ExecStatus {
|
||||
pid_t pid;
|
||||
time_t timestamp;
|
||||
usec_t timestamp;
|
||||
int code; /* as in siginfo_t::si_code */
|
||||
int status; /* as in sigingo_t::si_status */
|
||||
};
|
||||
@ -25,20 +26,20 @@ struct ExecStatus {
|
||||
struct ExecCommand {
|
||||
char *path;
|
||||
char **argv;
|
||||
ExecStatus last_exec_status;
|
||||
LIST_FIELDS(ExecCommand);
|
||||
ExecStatus exec_status;
|
||||
LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */
|
||||
};
|
||||
|
||||
struct ExecContext {
|
||||
char **environment;
|
||||
mode_t umask;
|
||||
struct rlimit *rlimit[RLIMIT_NLIMITS];
|
||||
cap_t capabilities;
|
||||
bool capabilities_set:1;
|
||||
bool dumpable:1;
|
||||
int oom_adjust;
|
||||
int nice;
|
||||
char *chdir;
|
||||
char *directory;
|
||||
|
||||
cap_t capabilities;
|
||||
bool capabilities_set:1;
|
||||
|
||||
/* since resolving these names might might involve socket
|
||||
* connections and we don't want to deadlock ourselves these
|
||||
@ -48,13 +49,40 @@ struct ExecContext {
|
||||
char **supplementary_groups;
|
||||
};
|
||||
|
||||
int exec_spawn(const ExecCommand *command, const ExecContext *context, pid_t *ret);
|
||||
typedef enum ExitStatus {
|
||||
/* EXIT_SUCCESS defined by libc */
|
||||
/* EXIT_FAILURE defined by libc */
|
||||
EXIT_INVALIDARGUMENT = 2,
|
||||
EXIT_NOTIMPLEMENTED = 3,
|
||||
EXIT_NOPERMISSION = 4,
|
||||
EXIT_NOTINSTALLED = 5,
|
||||
EXIT_NOTCONFIGURED = 6,
|
||||
EXIT_NOTRUNNING = 7,
|
||||
|
||||
/* The LSB suggests that error codes >= 200 are "reserved". We
|
||||
* use them here under the assumption that they hence are
|
||||
* unused by init scripts.
|
||||
*
|
||||
* http://refspecs.freestandards.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */
|
||||
|
||||
EXIT_CHDIR = 200,
|
||||
EXIT_NICE,
|
||||
EXIT_FDS,
|
||||
EXIT_EXEC,
|
||||
EXIT_MEMORY,
|
||||
EXIT_LIMITS,
|
||||
EXIT_OOM_ADJUST
|
||||
} ExitStatus;
|
||||
|
||||
int exec_spawn(const ExecCommand *command, const ExecContext *context, int *fds, unsigned n_fds, pid_t *ret);
|
||||
|
||||
void exec_context_free(ExecContext *c);
|
||||
void exec_command_free_list(ExecCommand *c);
|
||||
void exec_command_free_array(ExecCommand **c, unsigned n);
|
||||
|
||||
void exec_context_init(ExecContext *c);
|
||||
void exec_context_done(ExecContext *c);
|
||||
void exec_context_dump(ExecContext *c, FILE* f, const char *prefix);
|
||||
|
||||
void exec_context_defaults(ExecContext *c);
|
||||
void exec_status_fill(ExecStatus *s, pid_t pid, int code, int status);
|
||||
|
||||
#endif
|
||||
|
61
hashmap.c
61
hashmap.c
@ -69,6 +69,18 @@ Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func) {
|
||||
return h;
|
||||
}
|
||||
|
||||
int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func) {
|
||||
assert(h);
|
||||
|
||||
if (*h)
|
||||
return 0;
|
||||
|
||||
if (!(*h = hashmap_new(hash_func, compare_func)))
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void remove_entry(Hashmap *h, struct hashmap_entry *e) {
|
||||
assert(h);
|
||||
assert(e);
|
||||
@ -248,26 +260,26 @@ void* hashmap_remove_value(Hashmap *h, const void *key, void *value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
void *hashmap_iterate(Hashmap *h, void **state, const void **key) {
|
||||
void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key) {
|
||||
struct hashmap_entry *e;
|
||||
|
||||
assert(state);
|
||||
assert(i);
|
||||
|
||||
if (!h)
|
||||
goto at_end;
|
||||
|
||||
if (*state == (void*) -1)
|
||||
if (*i == ITERATOR_LAST)
|
||||
goto at_end;
|
||||
|
||||
if (!*state && !h->iterate_list_head)
|
||||
if (*i == ITERATOR_FIRST && !h->iterate_list_head)
|
||||
goto at_end;
|
||||
|
||||
e = *state ? *state : h->iterate_list_head;
|
||||
e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry*) *i;
|
||||
|
||||
if (e->iterate_next)
|
||||
*state = e->iterate_next;
|
||||
*i = (Iterator) e->iterate_next;
|
||||
else
|
||||
*state = (void*) -1;
|
||||
*i = ITERATOR_LAST;
|
||||
|
||||
if (key)
|
||||
*key = e->key;
|
||||
@ -275,7 +287,7 @@ void *hashmap_iterate(Hashmap *h, void **state, const void **key) {
|
||||
return e->value;
|
||||
|
||||
at_end:
|
||||
*state = (void *) -1;
|
||||
*i = ITERATOR_LAST;
|
||||
|
||||
if (key)
|
||||
*key = NULL;
|
||||
@ -283,26 +295,26 @@ at_end:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key) {
|
||||
void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key) {
|
||||
struct hashmap_entry *e;
|
||||
|
||||
assert(state);
|
||||
assert(i);
|
||||
|
||||
if (!h)
|
||||
goto at_beginning;
|
||||
|
||||
if (*state == (void*) -1)
|
||||
if (*i == ITERATOR_FIRST)
|
||||
goto at_beginning;
|
||||
|
||||
if (!*state && !h->iterate_list_tail)
|
||||
if (*i == ITERATOR_LAST && !h->iterate_list_tail)
|
||||
goto at_beginning;
|
||||
|
||||
e = *state ? *state : h->iterate_list_tail;
|
||||
e = *i == ITERATOR_LAST ? h->iterate_list_tail : (struct hashmap_entry*) *i;
|
||||
|
||||
if (e->iterate_previous)
|
||||
*state = e->iterate_previous;
|
||||
*i = (Iterator) e->iterate_previous;
|
||||
else
|
||||
*state = (void*) -1;
|
||||
*i = ITERATOR_FIRST;
|
||||
|
||||
if (key)
|
||||
*key = e->key;
|
||||
@ -310,7 +322,7 @@ void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key) {
|
||||
return e->value;
|
||||
|
||||
at_beginning:
|
||||
*state = (void *) -1;
|
||||
*i = ITERATOR_FIRST;
|
||||
|
||||
if (key)
|
||||
*key = NULL;
|
||||
@ -318,6 +330,23 @@ at_beginning:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i) {
|
||||
unsigned hash;
|
||||
struct hashmap_entry *e;
|
||||
|
||||
if (!h)
|
||||
return NULL;
|
||||
|
||||
hash = h->hash_func(key) % NBUCKETS;
|
||||
|
||||
if (!(e = hash_scan(h, hash, key)))
|
||||
return NULL;
|
||||
|
||||
*i = (Iterator) e;
|
||||
|
||||
return e->value;
|
||||
}
|
||||
|
||||
void* hashmap_first(Hashmap *h) {
|
||||
|
||||
if (!h)
|
||||
|
23
hashmap.h
23
hashmap.h
@ -11,6 +11,11 @@
|
||||
* instantiate an object for each Hashmap use. */
|
||||
|
||||
typedef struct Hashmap Hashmap;
|
||||
typedef struct _IteratorStruct _IteratorStruct;
|
||||
typedef _IteratorStruct* Iterator;
|
||||
|
||||
#define ITERATOR_FIRST ((Iterator) 0)
|
||||
#define ITERATOR_LAST ((Iterator) -1)
|
||||
|
||||
typedef unsigned (*hash_func_t)(const void *p);
|
||||
typedef int (*compare_func_t)(const void *a, const void *b);
|
||||
@ -24,6 +29,7 @@ int trivial_compare_func(const void *a, const void *b);
|
||||
Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func);
|
||||
void hashmap_free(Hashmap *h);
|
||||
Hashmap *hashmap_copy(Hashmap *h);
|
||||
int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func);
|
||||
|
||||
int hashmap_put(Hashmap *h, const void *key, void *value);
|
||||
int hashmap_replace(Hashmap *h, const void *key, void *value);
|
||||
@ -36,21 +42,22 @@ int hashmap_merge(Hashmap *h, Hashmap *other);
|
||||
unsigned hashmap_size(Hashmap *h);
|
||||
bool hashmap_isempty(Hashmap *h);
|
||||
|
||||
void *hashmap_iterate(Hashmap *h, void **state, const void **key);
|
||||
void *hashmap_iterate_backwards(Hashmap *h, void **state, const void **key);
|
||||
void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key);
|
||||
void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key);
|
||||
void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i);
|
||||
|
||||
void hashmap_clear(Hashmap *h);
|
||||
void *hashmap_steal_first(Hashmap *h);
|
||||
void* hashmap_first(Hashmap *h);
|
||||
void* hashmap_last(Hashmap *h);
|
||||
|
||||
#define HASHMAP_FOREACH(e, h, state) \
|
||||
for ((state) = NULL, (e) = hashmap_iterate((h), &(state), NULL); (e); (e) = hashmap_iterate((h), &(state), NULL))
|
||||
#define HASHMAP_FOREACH(e, h, i) \
|
||||
for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); (e); (e) = hashmap_iterate((h), &(i), NULL))
|
||||
|
||||
#define HASHMAP_FOREACH_KEY(e, k, h, state) \
|
||||
for ((state) = NULL, (e) = hashmap_iterate((h), &(state), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(state), (const void**) &(k)))
|
||||
#define HASHMAP_FOREACH_KEY(e, k, h, i) \
|
||||
for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(i), (const void**) &(k)))
|
||||
|
||||
#define HASHMAP_FOREACH_BACKWARDS(e, h, state) \
|
||||
for ((state) = NULL, (e) = hashmap_iterate_backwards((h), &(state), NULL); (e); (e) = hashmap_iterate_backwards((h), &(state), NULL))
|
||||
#define HASHMAP_FOREACH_BACKWARDS(e, h, i) \
|
||||
for ((i) = ITERATE_LAST, (e) = hashmap_iterate_backwards((h), &(i), NULL); (e); (e) = hashmap_iterate_backwards((h), &(i), NULL))
|
||||
|
||||
#endif
|
||||
|
71
job.c
71
job.c
@ -276,33 +276,8 @@ bool job_type_is_conflicting(JobType a, JobType b) {
|
||||
return (a == JOB_STOP) != (b == JOB_STOP);
|
||||
}
|
||||
|
||||
bool job_type_is_applicable(JobType j, NameType n) {
|
||||
assert(j >= 0 && j < _JOB_TYPE_MAX);
|
||||
assert(n >= 0 && n < _NAME_TYPE_MAX);
|
||||
|
||||
switch (j) {
|
||||
case JOB_VERIFY_ACTIVE:
|
||||
case JOB_START:
|
||||
return true;
|
||||
|
||||
case JOB_STOP:
|
||||
case JOB_RESTART:
|
||||
case JOB_TRY_RESTART:
|
||||
return name_type_can_start(n);
|
||||
|
||||
case JOB_RELOAD:
|
||||
return name_type_can_reload(n);
|
||||
|
||||
case JOB_RELOAD_OR_START:
|
||||
return name_type_can_reload(n) && name_type_can_start(n);
|
||||
|
||||
default:
|
||||
assert_not_reached("Invalid job type");
|
||||
}
|
||||
}
|
||||
|
||||
bool job_is_runnable(Job *j) {
|
||||
void *state;
|
||||
Iterator i;
|
||||
Name *other;
|
||||
|
||||
assert(j);
|
||||
@ -323,7 +298,7 @@ bool job_is_runnable(Job *j) {
|
||||
* dependencies, regardless whether they are
|
||||
* starting or stopping something. */
|
||||
|
||||
SET_FOREACH(other, j->name->meta.dependencies[NAME_AFTER], state)
|
||||
SET_FOREACH(other, j->name->meta.dependencies[NAME_AFTER], i)
|
||||
if (other->meta.job)
|
||||
return false;
|
||||
}
|
||||
@ -331,7 +306,7 @@ bool job_is_runnable(Job *j) {
|
||||
/* Also, if something else is being stopped and we should
|
||||
* change state after it, then lets wait. */
|
||||
|
||||
SET_FOREACH(other, j->name->meta.dependencies[NAME_BEFORE], state)
|
||||
SET_FOREACH(other, j->name->meta.dependencies[NAME_BEFORE], i)
|
||||
if (other->meta.job &&
|
||||
(other->meta.job->type == JOB_STOP ||
|
||||
other->meta.job->type == JOB_RESTART ||
|
||||
@ -356,12 +331,17 @@ int job_run_and_invalidate(Job *j) {
|
||||
int r;
|
||||
assert(j);
|
||||
|
||||
if (!job_is_runnable(j))
|
||||
return -EAGAIN;
|
||||
if (j->in_run_queue) {
|
||||
LIST_REMOVE(Job, run_queue, j->manager->run_queue, j);
|
||||
j->in_run_queue = false;
|
||||
}
|
||||
|
||||
if (j->state != JOB_WAITING)
|
||||
return 0;
|
||||
|
||||
if (!job_is_runnable(j))
|
||||
return -EAGAIN;
|
||||
|
||||
j->state = JOB_RUNNING;
|
||||
|
||||
switch (j->type) {
|
||||
@ -437,16 +417,18 @@ int job_run_and_invalidate(Job *j) {
|
||||
|
||||
int job_finish_and_invalidate(Job *j, bool success) {
|
||||
Name *n;
|
||||
void *state;
|
||||
Name *other;
|
||||
NameType t;
|
||||
Iterator i;
|
||||
|
||||
assert(j);
|
||||
|
||||
/* Patch restart jobs so that they become normal start jobs */
|
||||
if (success && (j->type == JOB_RESTART || j->type == JOB_TRY_RESTART)) {
|
||||
j->state = JOB_RUNNING;
|
||||
j->type = JOB_START;
|
||||
return job_run_and_invalidate(j);
|
||||
job_schedule_run(j);
|
||||
return 0;
|
||||
}
|
||||
|
||||
n = j->name;
|
||||
@ -460,14 +442,14 @@ int job_finish_and_invalidate(Job *j, bool success) {
|
||||
t == JOB_VERIFY_ACTIVE ||
|
||||
t == JOB_RELOAD_OR_START) {
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], i)
|
||||
if (other->meta.job &&
|
||||
(other->meta.type == JOB_START ||
|
||||
other->meta.type == JOB_VERIFY_ACTIVE ||
|
||||
other->meta.type == JOB_RELOAD_OR_START))
|
||||
job_finish_and_invalidate(other->meta.job, false);
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRED_BY], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRED_BY], i)
|
||||
if (other->meta.job &&
|
||||
!other->meta.job->forced &&
|
||||
(other->meta.type == JOB_START ||
|
||||
@ -477,7 +459,7 @@ int job_finish_and_invalidate(Job *j, bool success) {
|
||||
|
||||
} else if (t == JOB_STOP) {
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], i)
|
||||
if (other->meta.job &&
|
||||
(t == JOB_START ||
|
||||
t == JOB_VERIFY_ACTIVE ||
|
||||
@ -487,12 +469,23 @@ int job_finish_and_invalidate(Job *j, bool success) {
|
||||
}
|
||||
|
||||
/* Try to start the next jobs that can be started */
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], i)
|
||||
if (other->meta.job)
|
||||
job_run_and_invalidate(other->meta.job);
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], state)
|
||||
job_schedule_run(other->meta.job);
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], i)
|
||||
if (other->meta.job)
|
||||
job_run_and_invalidate(other->meta.job);
|
||||
job_schedule_run(other->meta.job);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void job_schedule_run(Job *j) {
|
||||
assert(j);
|
||||
assert(j->linked);
|
||||
|
||||
if (j->in_run_queue)
|
||||
return;
|
||||
|
||||
LIST_PREPEND(Job, run_queue, j->manager->run_queue, j);
|
||||
j->in_run_queue = true;
|
||||
}
|
||||
|
13
job.h
13
job.h
@ -56,9 +56,8 @@ struct JobDependency {
|
||||
|
||||
bool matters;
|
||||
|
||||
/* Linked list for the subjects, resp objects */
|
||||
JobDependency *subject_prev, *subject_next;
|
||||
JobDependency *object_prev, *object_next;
|
||||
LIST_FIELDS(JobDependency, subject);
|
||||
LIST_FIELDS(JobDependency, object);
|
||||
};
|
||||
|
||||
struct Job {
|
||||
@ -71,11 +70,12 @@ struct Job {
|
||||
JobState state;
|
||||
|
||||
bool linked:1;
|
||||
bool in_run_queue:1;
|
||||
bool matters_to_anchor:1;
|
||||
bool forced:1;
|
||||
|
||||
/* These fields are used only while building a transaction */
|
||||
Job *transaction_next, *transaction_prev;
|
||||
LIST_FIELDS(Job, transaction);
|
||||
LIST_FIELDS(Job, run_queue);
|
||||
|
||||
JobDependency *subject_list;
|
||||
JobDependency *object_list;
|
||||
@ -83,6 +83,7 @@ struct Job {
|
||||
/* Used for graph algs as a "I have been here" marker */
|
||||
Job* marker;
|
||||
unsigned generation;
|
||||
|
||||
};
|
||||
|
||||
Job* job_new(Manager *m, JobType type, Name *name);
|
||||
@ -102,8 +103,8 @@ int job_type_merge(JobType *a, JobType b);
|
||||
bool job_type_is_mergeable(JobType a, JobType b);
|
||||
bool job_type_is_superset(JobType a, JobType b);
|
||||
bool job_type_is_conflicting(JobType a, JobType b);
|
||||
bool job_type_is_applicable(JobType j, NameType n);
|
||||
|
||||
void job_schedule_run(Job *j);
|
||||
int job_run_and_invalidate(Job *j);
|
||||
int job_finish_and_invalidate(Job *j, bool success);
|
||||
|
||||
|
82
list.h
82
list.h
@ -6,85 +6,95 @@
|
||||
/* The head of the linked list. Use this in the structure that shall
|
||||
* contain the head of the linked list */
|
||||
#define LIST_HEAD(t,name) \
|
||||
t *name
|
||||
t *name
|
||||
|
||||
/* The pointers in the linked list's items. Use this in the item structure */
|
||||
#define LIST_FIELDS(t) \
|
||||
t *next, *prev
|
||||
#define LIST_FIELDS(t,name) \
|
||||
t *name##_next, *name##_prev
|
||||
|
||||
/* Initialize the list's head */
|
||||
#define LIST_HEAD_INIT(t,item) \
|
||||
#define LIST_HEAD_INIT(t,head) \
|
||||
do { \
|
||||
(item) = (t*) NULL; } \
|
||||
(head) = NULL; } \
|
||||
while(false)
|
||||
|
||||
/* Initialize a list item */
|
||||
#define LIST_INIT(t,item) \
|
||||
#define LIST_INIT(t,name,item) \
|
||||
do { \
|
||||
t *_item = (item); \
|
||||
assert(_item); \
|
||||
_item->prev = _item->next = NULL; \
|
||||
_item->name##_prev = _item->name##_next = NULL; \
|
||||
} while(false)
|
||||
|
||||
/* Prepend an item to the list */
|
||||
#define LIST_PREPEND(t,head,item) \
|
||||
#define LIST_PREPEND(t,name,head,item) \
|
||||
do { \
|
||||
t **_head = &(head), *_item = (item); \
|
||||
assert(_item); \
|
||||
if ((_item->next = *_head)) \
|
||||
_item->next->prev = _item; \
|
||||
_item->prev = NULL; \
|
||||
if ((_item->name##_next = *_head)) \
|
||||
_item->name##_next->name##_prev = _item; \
|
||||
_item->name##_prev = NULL; \
|
||||
*_head = _item; \
|
||||
} while(false)
|
||||
|
||||
/* Remove an item from the list */
|
||||
#define LIST_REMOVE(t,head,item) \
|
||||
#define LIST_REMOVE(t,name,head,item) \
|
||||
do { \
|
||||
t **_head = &(head), *_item = (item); \
|
||||
assert(_item); \
|
||||
if (_item->next) \
|
||||
_item->next->prev = _item->prev; \
|
||||
if (_item->prev) \
|
||||
_item->prev->next = _item->next; \
|
||||
if (_item->name##_next) \
|
||||
_item->name##_next->name##_prev = _item->name##_prev; \
|
||||
if (_item->name##_prev) \
|
||||
_item->name##_prev->name##_next = _item->name##_next; \
|
||||
else { \
|
||||
assert(*_head == _item); \
|
||||
*_head = _item->next; \
|
||||
*_head = _item->name##_next; \
|
||||
} \
|
||||
_item->next = _item->prev = NULL; \
|
||||
_item->name##_next = _item->name##_prev = NULL; \
|
||||
} while(false)
|
||||
|
||||
/* Find the head of the list */
|
||||
#define LIST_FIND_HEAD(t,item,head) \
|
||||
#define LIST_FIND_HEAD(t,name,item,head) \
|
||||
do { \
|
||||
t **_head = (head), *_item = (item); \
|
||||
*_head = _item; \
|
||||
assert(_head); \
|
||||
while ((*_head)->prev) \
|
||||
*_head = (*_head)->prev; \
|
||||
t *_item = (item); \
|
||||
assert(_item); \
|
||||
while ((_item->name##_prev) \
|
||||
_item = _item->name##_prev; \
|
||||
(head) = _item; \
|
||||
} while (false)
|
||||
|
||||
/* Find the head of the list */
|
||||
#define LIST_FIND_TAIL(t,name,item,tail) \
|
||||
do { \
|
||||
t *_item = (item); \
|
||||
assert(_item); \
|
||||
while (_item->name##_next) \
|
||||
_item = _item->name##_next; \
|
||||
(tail) = _item; \
|
||||
} while (false)
|
||||
|
||||
/* Insert an item after another one (a = where, b = what) */
|
||||
#define LIST_INSERT_AFTER(t,head,a,b) \
|
||||
#define LIST_INSERT_AFTER(t,name,head,a,b) \
|
||||
do { \
|
||||
t **_head = &(head), *_a = (a), *_b = (b); \
|
||||
assert(_b); \
|
||||
if (!_a) { \
|
||||
if ((_b->next = *_head)) \
|
||||
_b->next->prev = _b; \
|
||||
_b->prev = NULL; \
|
||||
if ((_b->name##_next = *_head)) \
|
||||
_b->name##_next->name##_prev = _b; \
|
||||
_b->name##_prev = NULL; \
|
||||
*_head = _b; \
|
||||
} else { \
|
||||
if ((_b->next = _a->next)) \
|
||||
_b->next->prev = _b; \
|
||||
_b->prev = _a; \
|
||||
_a->next = _b; \
|
||||
if ((_b->name##_next = _a->name##_next)) \
|
||||
_b->name##_next->name##_prev = _b; \
|
||||
_b->name##_prev = _a; \
|
||||
_a->name##_next = _b; \
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
#define LIST_FOREACH(i,head) \
|
||||
for (i = (head); i; i = i->next)
|
||||
#define LIST_FOREACH(name,i,head) \
|
||||
for ((i) = (head); (i); (i) = (i)->name##_next)
|
||||
|
||||
#define LIST_FOREACH_SAFE(i,n,head) \
|
||||
for (i = (head); i && ((n = i->next), 1); i = n)
|
||||
#define LIST_FOREACH_SAFE(name,i,n,head) \
|
||||
for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n))
|
||||
|
||||
#endif
|
||||
|
346
load-fragment.c
346
load-fragment.c
@ -3,6 +3,7 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <linux/oom.h>
|
||||
|
||||
#include "name.h"
|
||||
#include "strv.h"
|
||||
@ -44,9 +45,8 @@ static int config_parse_deps(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!*set)
|
||||
if (!(*set = set_new(trivial_hash_func, trivial_compare_func)))
|
||||
return -ENOMEM;
|
||||
if ((r = set_ensure_allocated(set, trivial_hash_func, trivial_compare_func)) < 0)
|
||||
return r;
|
||||
|
||||
if ((r = set_put(*set, other)) < 0)
|
||||
return r;
|
||||
@ -177,12 +177,12 @@ static int config_parse_listen(
|
||||
}
|
||||
|
||||
p->fd = -1;
|
||||
LIST_PREPEND(SocketPort, s->ports, p);
|
||||
LIST_PREPEND(SocketPort, port, s->ports, p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_parse_bind(
|
||||
static int config_parse_socket_bind(
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
@ -211,6 +211,256 @@ static int config_parse_bind(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_parse_nice(
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
const char *lvalue,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
int *i = data, priority, r;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
if ((r = safe_atoi(rvalue, &priority)) < 0) {
|
||||
log_error("[%s:%u] Failed to parse nice priority: %s", filename, line, rvalue);
|
||||
return r;
|
||||
}
|
||||
|
||||
if (priority < PRIO_MIN || priority >= PRIO_MAX) {
|
||||
log_error("[%s:%u] Nice priority out of range: %s", filename, line, rvalue);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
*i = priority;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_parse_oom_adjust(
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
const char *lvalue,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
int *i = data, oa, r;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
if ((r = safe_atoi(rvalue, &oa)) < 0) {
|
||||
log_error("[%s:%u] Failed to parse OOM adjust value: %s", filename, line, rvalue);
|
||||
return r;
|
||||
}
|
||||
|
||||
if (oa < OOM_DISABLE || oa > OOM_ADJUST_MAX) {
|
||||
log_error("[%s:%u] OOM adjust value out of range: %s", filename, line, rvalue);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
*i = oa;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_parse_umask(
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
const char *lvalue,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
mode_t *m = data;
|
||||
long l;
|
||||
char *x = NULL;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
errno = 0;
|
||||
l = strtol(rvalue, &x, 8);
|
||||
if (!x || *x || errno) {
|
||||
log_error("[%s:%u] Failed to parse umask value: %s", filename, line, rvalue);
|
||||
return errno ? -errno : -EINVAL;
|
||||
}
|
||||
|
||||
if (l < 0000 || l > 0777) {
|
||||
log_error("[%s:%u] umask value out of range: %s", filename, line, rvalue);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
*m = (mode_t) l;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_parse_exec(
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
const char *lvalue,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
ExecCommand **e = data, *ee, *nce = NULL;
|
||||
char **n;
|
||||
char *w;
|
||||
unsigned k;
|
||||
size_t l;
|
||||
char *state;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
k = 0;
|
||||
FOREACH_WORD_QUOTED(w, l, rvalue, state)
|
||||
k++;
|
||||
|
||||
if (!(n = new(char*, k+1)))
|
||||
return -ENOMEM;
|
||||
|
||||
FOREACH_WORD_QUOTED(w, l, rvalue, state)
|
||||
if (!(n[k++] = strndup(w, l)))
|
||||
goto fail;
|
||||
|
||||
n[k] = NULL;
|
||||
|
||||
if (!n[0] || n[0][0] != '/') {
|
||||
log_error("[%s:%u] Invalid executable path in command line: %s", filename, line, rvalue);
|
||||
strv_free(n);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!(nce = new0(ExecCommand, 1)))
|
||||
goto fail;
|
||||
|
||||
nce->argv = n;
|
||||
if (!(nce->path = strdup(n[0])))
|
||||
goto fail;
|
||||
|
||||
if (*e) {
|
||||
/* It's kinda important that we keep the order here */
|
||||
LIST_FIND_TAIL(ExecCommand, command, *e, ee);
|
||||
LIST_INSERT_AFTER(ExecCommand, command, *e, ee, nce);
|
||||
} else
|
||||
*e = nce;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
for (; k > 0; k--)
|
||||
free(n[k-1]);
|
||||
free(n);
|
||||
|
||||
free(nce);
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static int config_parse_usec(
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
const char *lvalue,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
usec_t *usec = data;
|
||||
unsigned long long u;
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
if ((r = safe_atollu(rvalue, &u)) < 0) {
|
||||
log_error("[%s:%u] Failed to parse time value: %s", filename, line, rvalue);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* We actually assume the user configures seconds. Later on we
|
||||
* might choose to support suffixes for time values, to
|
||||
* configure bigger or smaller units */
|
||||
|
||||
*usec = u * USEC_PER_SEC;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_parse_service_type(
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
const char *lvalue,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
Service *s = data;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
if (streq(rvalue, "forking"))
|
||||
s->type = SERVICE_FORKING;
|
||||
else if (streq(rvalue, "simple"))
|
||||
s->type = SERVICE_SIMPLE;
|
||||
else {
|
||||
log_error("[%s:%u] Failed to parse service type: %s", filename, line, rvalue);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_parse_service_restart(
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
const char *lvalue,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
Service *s = data;
|
||||
|
||||
assert(filename);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
assert(data);
|
||||
|
||||
if (streq(rvalue, "once"))
|
||||
s->restart = SERVICE_ONCE;
|
||||
else if (streq(rvalue, "on-success"))
|
||||
s->type = SERVICE_RESTART_ON_SUCCESS;
|
||||
else if (streq(rvalue, "always"))
|
||||
s->type = SERVICE_RESTART_ALWAYS;
|
||||
else {
|
||||
log_error("[%s:%u] Failed to parse service type: %s", filename, line, rvalue);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int name_load_fragment(Name *n) {
|
||||
|
||||
static const char* const section_table[_NAME_TYPE_MAX] = {
|
||||
@ -224,32 +474,64 @@ int name_load_fragment(Name *n) {
|
||||
[NAME_SNAPSHOT] = "Snapshot"
|
||||
};
|
||||
|
||||
#define EXEC_CONTEXT_CONFIG_ITEMS(context, section) \
|
||||
{ "Directory", config_parse_path, &(context).directory, section }, \
|
||||
{ "User", config_parse_string, &(context).user, section }, \
|
||||
{ "Group", config_parse_string, &(context).group, section }, \
|
||||
{ "SupplementaryGroups", config_parse_strv, &(context).supplementary_groups, section }, \
|
||||
{ "Nice", config_parse_nice, &(context).nice, section }, \
|
||||
{ "OOMAdjust", config_parse_oom_adjust, &(context).oom_adjust, section }, \
|
||||
{ "UMask", config_parse_umask, &(context).umask, section }, \
|
||||
{ "Environment", config_parse_strv, &(context).environment, section }
|
||||
|
||||
const ConfigItem items[] = {
|
||||
{ "Names", config_parse_names, &n->meta.names, "Meta" },
|
||||
{ "Description", config_parse_string, &n->meta.description, "Meta" },
|
||||
{ "Requires", config_parse_deps, n->meta.dependencies+NAME_REQUIRES, "Meta" },
|
||||
{ "SoftRequires", config_parse_deps, n->meta.dependencies+NAME_SOFT_REQUIRES, "Meta" },
|
||||
{ "Wants", config_parse_deps, n->meta.dependencies+NAME_WANTS, "Meta" },
|
||||
{ "Requisite", config_parse_deps, n->meta.dependencies+NAME_REQUISITE, "Meta" },
|
||||
{ "SoftRequisite", config_parse_deps, n->meta.dependencies+NAME_SOFT_REQUISITE, "Meta" },
|
||||
{ "Conflicts", config_parse_deps, n->meta.dependencies+NAME_CONFLICTS, "Meta" },
|
||||
{ "Before", config_parse_deps, n->meta.dependencies+NAME_BEFORE, "Meta" },
|
||||
{ "After", config_parse_deps, n->meta.dependencies+NAME_AFTER, "Meta" },
|
||||
{ "ListenStream", config_parse_listen, &n->socket, "Socket" },
|
||||
{ "ListenDatagram", config_parse_listen, &n->socket, "Socket" },
|
||||
{ "ListenSequentialPacket", config_parse_listen, &n->socket, "Socket" },
|
||||
{ "ListenFIFO", config_parse_listen, &n->socket, "Socket" },
|
||||
{ "BindIPv6Only", config_parse_bind, &n->socket, "Socket" },
|
||||
{ "Backlog", config_parse_unsigned, &n->socket.backlog, "Socket" },
|
||||
{ "Names", config_parse_names, &n->meta.names, "Meta" },
|
||||
{ "Description", config_parse_string, &n->meta.description, "Meta" },
|
||||
{ "Requires", config_parse_deps, n->meta.dependencies+NAME_REQUIRES, "Meta" },
|
||||
{ "SoftRequires", config_parse_deps, n->meta.dependencies+NAME_SOFT_REQUIRES, "Meta" },
|
||||
{ "Wants", config_parse_deps, n->meta.dependencies+NAME_WANTS, "Meta" },
|
||||
{ "Requisite", config_parse_deps, n->meta.dependencies+NAME_REQUISITE, "Meta" },
|
||||
{ "SoftRequisite", config_parse_deps, n->meta.dependencies+NAME_SOFT_REQUISITE, "Meta" },
|
||||
{ "Conflicts", config_parse_deps, n->meta.dependencies+NAME_CONFLICTS, "Meta" },
|
||||
{ "Before", config_parse_deps, n->meta.dependencies+NAME_BEFORE, "Meta" },
|
||||
{ "After", config_parse_deps, n->meta.dependencies+NAME_AFTER, "Meta" },
|
||||
|
||||
{ "PIDFile", config_parse_path, &n->service.pid_file, "Service" },
|
||||
{ "ExecStartPre", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_START_PRE], "Service" },
|
||||
{ "ExecStart", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_START], "Service" },
|
||||
{ "ExecStartPost", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_START_POST], "Service" },
|
||||
{ "ExecReload", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_RELOAD], "Service" },
|
||||
{ "ExecStop", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_STOP], "Service" },
|
||||
{ "ExecStopPost", config_parse_exec, &n->service.exec_command[SERVICE_EXEC_STOP_POST], "Service" },
|
||||
{ "RestartSec", config_parse_usec, &n->service.restart_usec, "Service" },
|
||||
{ "TimeoutSec", config_parse_usec, &n->service.timeout_usec, "Service" },
|
||||
{ "Type", config_parse_service_type, &n->service, "Service" },
|
||||
{ "Restart", config_parse_service_restart, &n->service, "Service" },
|
||||
EXEC_CONTEXT_CONFIG_ITEMS(n->service.exec_context, "Service"),
|
||||
|
||||
{ "ListenStream", config_parse_listen, &n->socket, "Socket" },
|
||||
{ "ListenDatagram", config_parse_listen, &n->socket, "Socket" },
|
||||
{ "ListenSequentialPacket", config_parse_listen, &n->socket, "Socket" },
|
||||
{ "ListenFIFO", config_parse_listen, &n->socket, "Socket" },
|
||||
{ "BindIPv6Only", config_parse_socket_bind, &n->socket, "Socket" },
|
||||
{ "Backlog", config_parse_unsigned, &n->socket.backlog, "Socket" },
|
||||
{ "ExecStartPre", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_START_PRE], "Socket" },
|
||||
{ "ExecStartPost", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_START_POST], "Socket" },
|
||||
{ "ExecStopPre", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_STOP_PRE], "Socket" },
|
||||
{ "ExecStopPost", config_parse_exec, &n->service.exec_command[SOCKET_EXEC_STOP_POST], "Socket" },
|
||||
EXEC_CONTEXT_CONFIG_ITEMS(n->socket.exec_context, "Socket"),
|
||||
|
||||
EXEC_CONTEXT_CONFIG_ITEMS(n->automount.exec_context, "Automount"),
|
||||
|
||||
{ NULL, NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
const
|
||||
#undef EXEC_CONTEXT_CONFIG_ITEMS
|
||||
|
||||
char *t;
|
||||
int r;
|
||||
void *state;
|
||||
const char *sections[3];
|
||||
Iterator i;
|
||||
|
||||
assert(n);
|
||||
assert(n->meta.load_state == NAME_STUB);
|
||||
@ -258,12 +540,18 @@ int name_load_fragment(Name *n) {
|
||||
sections[1] = section_table[n->meta.type];
|
||||
sections[2] = NULL;
|
||||
|
||||
SET_FOREACH(t, n->meta.names, state)
|
||||
if ((r = config_parse(t, sections, items, n)) < 0)
|
||||
goto fail;
|
||||
SET_FOREACH(t, n->meta.names, i) {
|
||||
|
||||
r = 0;
|
||||
/* Try to find a name we can load this with */
|
||||
if ((r = config_parse(t, sections, items, n)) == -ENOENT)
|
||||
continue;
|
||||
|
||||
fail:
|
||||
return r;
|
||||
/* Yay, we succeeded! Now let's call this our identifier */
|
||||
if (r == 0)
|
||||
n->meta.id = t;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
2
macro.h
2
macro.h
@ -76,4 +76,6 @@ static inline size_t ALIGN(size_t l) {
|
||||
#define memzero(x,l) (memset((x), 0, (l)))
|
||||
#define zero(x) (memzero(&(x), sizeof(x)))
|
||||
|
||||
#define char_array_0(x) x[sizeof(x)-1] = 0;
|
||||
|
||||
#endif
|
||||
|
2
main.c
2
main.c
@ -42,8 +42,6 @@ int main(int argc, char *argv[]) {
|
||||
printf("- By jobs:\n");
|
||||
manager_dump_jobs(m, stdout, "\t");
|
||||
|
||||
manager_run_jobs(m);
|
||||
|
||||
manager_loop(m);
|
||||
|
||||
retval = 0;
|
||||
|
234
manager.c
234
manager.c
@ -217,8 +217,8 @@ static int delete_one_unmergeable_job(Manager *m, Job *j) {
|
||||
|
||||
/* We rely here on the fact that if a merged with b does not
|
||||
* merge with c, either a or b merge with c neither */
|
||||
for (; j; j = j->transaction_next)
|
||||
for (k = j->transaction_next; k; k = k->transaction_next) {
|
||||
LIST_FOREACH(transaction, j, j)
|
||||
LIST_FOREACH(transaction, k, j->transaction_next) {
|
||||
Job *d;
|
||||
|
||||
/* Is this one mergeable? Then skip it */
|
||||
@ -245,19 +245,19 @@ static int delete_one_unmergeable_job(Manager *m, Job *j) {
|
||||
|
||||
static int transaction_merge_jobs(Manager *m) {
|
||||
Job *j;
|
||||
void *state;
|
||||
Iterator i;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
/* First step, check whether any of the jobs for one specific
|
||||
* task conflict. If so, try to drop one of them. */
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
|
||||
JobType t;
|
||||
Job *k;
|
||||
|
||||
t = j->type;
|
||||
for (k = j->transaction_next; k; k = k->transaction_next) {
|
||||
LIST_FOREACH(transaction, k, j->transaction_next) {
|
||||
if ((r = job_type_merge(&t, k->type)) >= 0)
|
||||
continue;
|
||||
|
||||
@ -277,12 +277,12 @@ static int transaction_merge_jobs(Manager *m) {
|
||||
}
|
||||
|
||||
/* Second step, merge the jobs. */
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
|
||||
JobType t = j->type;
|
||||
Job *k;
|
||||
|
||||
/* Merge all transactions */
|
||||
for (k = j->transaction_next; k; k = k->transaction_next)
|
||||
LIST_FOREACH(transaction, k, j->transaction_next)
|
||||
assert_se(job_type_merge(&t, k->type) == 0);
|
||||
|
||||
/* If an active job is mergeable, merge it too */
|
||||
@ -311,7 +311,7 @@ static bool name_matters_to_anchor(Name *n, Job *j) {
|
||||
/* Checks whether at least one of the jobs for this name
|
||||
* matters to the anchor. */
|
||||
|
||||
for (; j; j = j->transaction_next)
|
||||
LIST_FOREACH(transaction, j, j)
|
||||
if (j->matters_to_anchor)
|
||||
return true;
|
||||
|
||||
@ -319,7 +319,7 @@ static bool name_matters_to_anchor(Name *n, Job *j) {
|
||||
}
|
||||
|
||||
static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned generation) {
|
||||
void *state;
|
||||
Iterator i;
|
||||
Name *n;
|
||||
int r;
|
||||
|
||||
@ -368,7 +368,7 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned
|
||||
|
||||
/* We assume that the the dependencies are bidirectional, and
|
||||
* hence can ignore NAME_AFTER */
|
||||
SET_FOREACH(n, j->name->meta.dependencies[NAME_BEFORE], state) {
|
||||
SET_FOREACH(n, j->name->meta.dependencies[NAME_BEFORE], i) {
|
||||
Job *o;
|
||||
|
||||
/* Is there a job for this name? */
|
||||
@ -390,7 +390,7 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned
|
||||
static int transaction_verify_order(Manager *m, unsigned *generation) {
|
||||
Job *j;
|
||||
int r;
|
||||
void *state;
|
||||
Iterator i;
|
||||
|
||||
assert(m);
|
||||
assert(generation);
|
||||
@ -398,7 +398,7 @@ static int transaction_verify_order(Manager *m, unsigned *generation) {
|
||||
/* Check if the ordering graph is cyclic. If it is, try to fix
|
||||
* that up by dropping one of the jobs. */
|
||||
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, state)
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, i)
|
||||
if ((r = transaction_verify_order_one(m, j, NULL, (*generation)++)) < 0)
|
||||
return r;
|
||||
|
||||
@ -413,12 +413,12 @@ static void transaction_collect_garbage(Manager *m) {
|
||||
/* Drop jobs that are not required by any other job */
|
||||
|
||||
do {
|
||||
void *state;
|
||||
Iterator i;
|
||||
Job *j;
|
||||
|
||||
again = false;
|
||||
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
|
||||
if (j->object_list)
|
||||
continue;
|
||||
|
||||
@ -432,7 +432,7 @@ static void transaction_collect_garbage(Manager *m) {
|
||||
}
|
||||
|
||||
static int transaction_is_destructive(Manager *m, JobMode mode) {
|
||||
void *state;
|
||||
Iterator i;
|
||||
Job *j;
|
||||
|
||||
assert(m);
|
||||
@ -440,7 +440,7 @@ static int transaction_is_destructive(Manager *m, JobMode mode) {
|
||||
/* Checks whether applying this transaction means that
|
||||
* existing jobs would be replaced */
|
||||
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
|
||||
|
||||
/* Assume merged */
|
||||
assert(!j->transaction_prev);
|
||||
@ -464,12 +464,12 @@ static void transaction_minimize_impact(Manager *m) {
|
||||
|
||||
do {
|
||||
Job *j;
|
||||
void *state;
|
||||
Iterator i;
|
||||
|
||||
again = false;
|
||||
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
|
||||
for (; j; j = j->transaction_next) {
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
|
||||
LIST_FOREACH(transaction, j, j) {
|
||||
|
||||
/* If it matters, we shouldn't drop it */
|
||||
if (j->matters_to_anchor)
|
||||
@ -497,13 +497,13 @@ static void transaction_minimize_impact(Manager *m) {
|
||||
}
|
||||
|
||||
static int transaction_apply(Manager *m, JobMode mode) {
|
||||
void *state;
|
||||
Iterator i;
|
||||
Job *j;
|
||||
int r;
|
||||
|
||||
/* Moves the transaction jobs to the set of active jobs */
|
||||
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
|
||||
/* Assume merged */
|
||||
assert(!j->transaction_prev);
|
||||
assert(!j->transaction_next);
|
||||
@ -543,7 +543,7 @@ static int transaction_apply(Manager *m, JobMode mode) {
|
||||
|
||||
rollback:
|
||||
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, state) {
|
||||
HASHMAP_FOREACH(j, m->transaction_jobs, i) {
|
||||
if (j->linked)
|
||||
continue;
|
||||
|
||||
@ -637,7 +637,7 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool f
|
||||
|
||||
f = hashmap_get(m->transaction_jobs, name);
|
||||
|
||||
for (j = f; j; j = j->transaction_next) {
|
||||
LIST_FOREACH(transaction, j, f) {
|
||||
assert(j->name == name);
|
||||
|
||||
if (j->type == type) {
|
||||
@ -652,21 +652,18 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool f
|
||||
else if (!(j = job_new(m, type, name)))
|
||||
return NULL;
|
||||
|
||||
if ((r = hashmap_replace(m->transaction_jobs, name, j)) < 0) {
|
||||
job_free(j);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
j->transaction_next = f;
|
||||
|
||||
if (f)
|
||||
f->transaction_prev = j;
|
||||
|
||||
j->generation = 0;
|
||||
j->marker = NULL;
|
||||
j->matters_to_anchor = false;
|
||||
j->forced = force;
|
||||
|
||||
LIST_PREPEND(Job, transaction, f, j);
|
||||
|
||||
if ((r = hashmap_replace(m->transaction_jobs, name, f)) < 0) {
|
||||
job_free(j);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (is_new)
|
||||
*is_new = true;
|
||||
|
||||
@ -708,7 +705,7 @@ void manager_transaction_unlink_job(Manager *m, Job *j) {
|
||||
|
||||
static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *name, Job *by, bool matters, bool force, Job **_ret) {
|
||||
Job *ret;
|
||||
void *state;
|
||||
Iterator i;
|
||||
Name *dep;
|
||||
int r;
|
||||
bool is_new;
|
||||
@ -720,7 +717,7 @@ static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *
|
||||
if (name->meta.load_state != NAME_LOADED)
|
||||
return -EINVAL;
|
||||
|
||||
if (!job_type_is_applicable(type, name->meta.type))
|
||||
if (!name_job_is_applicable(name, type))
|
||||
return -EBADR;
|
||||
|
||||
/* First add the job. */
|
||||
@ -734,28 +731,28 @@ static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name *
|
||||
if (is_new) {
|
||||
/* Finally, recursively add in all dependencies. */
|
||||
if (type == JOB_START || type == JOB_RELOAD_OR_START) {
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRES], state)
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRES], i)
|
||||
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
|
||||
goto fail;
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUIRES], state)
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUIRES], i)
|
||||
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, !force, force, NULL)) < 0 && r != -EBADR)
|
||||
goto fail;
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_WANTS], state)
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_WANTS], i)
|
||||
if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, force, NULL)) < 0 && r != -EBADR)
|
||||
goto fail;
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUISITE], state)
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUISITE], i)
|
||||
if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
|
||||
goto fail;
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUISITE], state)
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUISITE], i)
|
||||
if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !force, force, NULL)) < 0 && r != -EBADR)
|
||||
goto fail;
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_CONFLICTS], state)
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_CONFLICTS], i)
|
||||
if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
|
||||
goto fail;
|
||||
|
||||
} else if (type == JOB_STOP || type == JOB_RESTART || type == JOB_TRY_RESTART) {
|
||||
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRED_BY], state)
|
||||
SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUIRED_BY], i)
|
||||
if ((r = transaction_add_job_and_dependencies(m, type, dep, ret, true, force, NULL)) < 0 && r != -EBADR)
|
||||
goto fail;
|
||||
}
|
||||
@ -805,14 +802,14 @@ Name *manager_get_name(Manager *m, const char *name) {
|
||||
return hashmap_get(m->names, name);
|
||||
}
|
||||
|
||||
static int dispatch_load_queue(Manager *m) {
|
||||
static void dispatch_load_queue(Manager *m) {
|
||||
Meta *meta;
|
||||
|
||||
assert(m);
|
||||
|
||||
/* Make sure we are not run recursively */
|
||||
if (m->dispatching_load_queue)
|
||||
return 0;
|
||||
return;
|
||||
|
||||
m->dispatching_load_queue = true;
|
||||
|
||||
@ -820,50 +817,36 @@ static int dispatch_load_queue(Manager *m) {
|
||||
* tries to load its data until the queue is empty */
|
||||
|
||||
while ((meta = m->load_queue)) {
|
||||
assert(meta->linked);
|
||||
assert(meta->in_load_queue);
|
||||
|
||||
name_load(NAME(meta));
|
||||
LIST_REMOVE(Meta, m->load_queue, meta);
|
||||
}
|
||||
|
||||
m->dispatching_load_queue = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int manager_load_name(Manager *m, const char *name, Name **_ret) {
|
||||
Name *ret;
|
||||
NameType t;
|
||||
int r;
|
||||
char *n;
|
||||
|
||||
assert(m);
|
||||
assert(name);
|
||||
assert(_ret);
|
||||
|
||||
if (!name_is_valid(name))
|
||||
return -EINVAL;
|
||||
|
||||
/* This will load the service information files, but not actually
|
||||
* start any services or anything */
|
||||
|
||||
if ((ret = manager_get_name(m, name)))
|
||||
goto finish;
|
||||
|
||||
if ((t = name_type_from_string(name)) == _NAME_TYPE_INVALID)
|
||||
return -EINVAL;
|
||||
if ((ret = manager_get_name(m, name))) {
|
||||
*_ret = ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(ret = name_new(m)))
|
||||
return -ENOMEM;
|
||||
|
||||
ret->meta.type = t;
|
||||
|
||||
if (!(n = strdup(name))) {
|
||||
if ((r = name_add_name(ret, name)) < 0) {
|
||||
name_free(ret);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if ((r = set_put(ret->meta.names, n)) < 0) {
|
||||
name_free(ret);
|
||||
free(n);
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -872,38 +855,36 @@ int manager_load_name(Manager *m, const char *name, Name **_ret) {
|
||||
return r;
|
||||
}
|
||||
|
||||
/* At this point the new entry is created and linked. However,
|
||||
/* At this point the new entry is created and linked. However
|
||||
* not loaded. Now load this entry and all its dependencies
|
||||
* recursively */
|
||||
|
||||
dispatch_load_queue(m);
|
||||
|
||||
finish:
|
||||
|
||||
*_ret = ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) {
|
||||
void *state;
|
||||
Iterator i;
|
||||
Job *j;
|
||||
|
||||
assert(s);
|
||||
assert(f);
|
||||
|
||||
HASHMAP_FOREACH(j, s->jobs, state)
|
||||
HASHMAP_FOREACH(j, s->jobs, i)
|
||||
job_dump(j, f, prefix);
|
||||
}
|
||||
|
||||
void manager_dump_names(Manager *s, FILE *f, const char *prefix) {
|
||||
void *state;
|
||||
Iterator i;
|
||||
Name *n;
|
||||
const char *t;
|
||||
|
||||
assert(s);
|
||||
assert(f);
|
||||
|
||||
HASHMAP_FOREACH_KEY(n, t, s->names, state)
|
||||
HASHMAP_FOREACH_KEY(n, t, s->names, i)
|
||||
if (name_id(n) == t)
|
||||
name_dump(n, f, prefix);
|
||||
}
|
||||
@ -919,19 +900,25 @@ void manager_clear_jobs(Manager *m) {
|
||||
job_free(j);
|
||||
}
|
||||
|
||||
void manager_run_jobs(Manager *m) {
|
||||
void manager_dispatch_run_queue(Manager *m) {
|
||||
Job *j;
|
||||
void *state;
|
||||
int r;
|
||||
|
||||
HASHMAP_FOREACH(j, m->jobs, state) {
|
||||
r = job_run_and_invalidate(j);
|
||||
if (m->dispatching_run_queue)
|
||||
return;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
13
manager.h
13
manager.h
@ -8,6 +8,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct Manager Manager;
|
||||
typedef enum ManagerEventType ManagerEventType;
|
||||
|
||||
#include "name.h"
|
||||
#include "job.h"
|
||||
@ -15,6 +16,12 @@ typedef struct Manager Manager;
|
||||
#include "list.h"
|
||||
#include "set.h"
|
||||
|
||||
enum ManagerEventType {
|
||||
MANAGER_SIGNAL,
|
||||
MANAGER_FD,
|
||||
MANAGER_TIMER
|
||||
};
|
||||
|
||||
struct Manager {
|
||||
uint32_t current_job_id;
|
||||
|
||||
@ -29,11 +36,15 @@ struct Manager {
|
||||
/* Names that need to be loaded */
|
||||
LIST_HEAD(Meta, load_queue); /* this is actually more a stack than a queue, but uh. */
|
||||
|
||||
/* Jobs that need to be run */
|
||||
LIST_HEAD(Job, run_queue); /* more a stack than a queue, too */
|
||||
|
||||
/* Jobs to be added */
|
||||
Hashmap *transaction_jobs; /* Name object => Job object list 1:1 */
|
||||
JobDependency *transaction_anchor;
|
||||
|
||||
bool dispatching_load_queue:1;
|
||||
bool dispatching_run_queue:1;
|
||||
|
||||
Hashmap *watch_pids; /* pid => Name object n:1 */
|
||||
|
||||
@ -57,7 +68,7 @@ void manager_transaction_unlink_job(Manager *m, Job *j);
|
||||
|
||||
void manager_clear_jobs(Manager *m);
|
||||
|
||||
void manager_run_jobs(Manager *m);
|
||||
void manager_dispatch_run_queue(Manager *m);
|
||||
int manager_loop(Manager *m);
|
||||
|
||||
#endif
|
||||
|
22
milestone.c
22
milestone.c
@ -4,11 +4,7 @@
|
||||
#include "milestone.h"
|
||||
#include "load-fragment.h"
|
||||
|
||||
static NameActiveState milestone_active_state(Name *n) {
|
||||
return MILESTONE(n)->state == MILESTONE_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
|
||||
}
|
||||
|
||||
static void milestone_free_hook(Name *n) {
|
||||
static void milestone_done(Name *n) {
|
||||
Milestone *m = MILESTONE(n);
|
||||
|
||||
assert(m);
|
||||
@ -16,17 +12,15 @@ static void milestone_free_hook(Name *n) {
|
||||
/* Nothing here for now */
|
||||
}
|
||||
|
||||
static NameActiveState milestone_active_state(Name *n) {
|
||||
return MILESTONE(n)->state == MILESTONE_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
|
||||
}
|
||||
|
||||
const NameVTable milestone_vtable = {
|
||||
.suffix = ".milestone",
|
||||
|
||||
.load = name_load_fragment,
|
||||
.dump = NULL,
|
||||
.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
26
mount.c
@ -8,7 +8,7 @@
|
||||
#include "load-fstab.h"
|
||||
#include "load-dropin.h"
|
||||
|
||||
static int mount_load(Name *n) {
|
||||
static int mount_init(Name *n) {
|
||||
int r;
|
||||
Mount *m = MOUNT(n);
|
||||
|
||||
@ -29,6 +29,13 @@ static int mount_load(Name *n) {
|
||||
return r;
|
||||
}
|
||||
|
||||
static void mount_done(Name *n) {
|
||||
Mount *d = MOUNT(n);
|
||||
|
||||
assert(d);
|
||||
free(d->path);
|
||||
}
|
||||
|
||||
static void mount_dump(Name *n, FILE *f, const char *prefix) {
|
||||
|
||||
static const char* const state_table[_MOUNT_STATE_MAX] = {
|
||||
@ -63,24 +70,13 @@ static NameActiveState mount_active_state(Name *n) {
|
||||
return table[MOUNT(n)->state];
|
||||
}
|
||||
|
||||
static void mount_free_hook(Name *n) {
|
||||
Mount *d = MOUNT(n);
|
||||
|
||||
assert(d);
|
||||
free(d->path);
|
||||
}
|
||||
|
||||
const NameVTable mount_vtable = {
|
||||
.suffix = ".mount",
|
||||
|
||||
.load = mount_load,
|
||||
.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
284
name.c
@ -4,6 +4,8 @@
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <sys/poll.h>
|
||||
|
||||
#include "set.h"
|
||||
#include "name.h"
|
||||
@ -77,7 +79,6 @@ Name *name_new(Manager *m) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Not much initialization happening here at this time */
|
||||
n->meta.manager = m;
|
||||
n->meta.type = _NAME_TYPE_INVALID;
|
||||
|
||||
@ -86,10 +87,40 @@ Name *name_new(Manager *m) {
|
||||
return n;
|
||||
}
|
||||
|
||||
int name_add_name(Name *n, const char *text) {
|
||||
NameType t;
|
||||
char *s;
|
||||
int r;
|
||||
|
||||
assert(n);
|
||||
assert(text);
|
||||
|
||||
if ((t = name_type_from_string(text)) == _NAME_TYPE_INVALID)
|
||||
return -EINVAL;
|
||||
|
||||
if (n->meta.type != _NAME_TYPE_INVALID && t != n->meta.type)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(s = strdup(text)))
|
||||
return -ENOMEM;
|
||||
|
||||
if ((r = set_put(n->meta.names, s)) < 0) {
|
||||
free(s);
|
||||
return r;
|
||||
}
|
||||
|
||||
n->meta.type = t;
|
||||
|
||||
if (!n->meta.id)
|
||||
n->meta.id = s;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FIXME: Does not rollback on failure! */
|
||||
int name_link_names(Name *n, bool replace) {
|
||||
char *t;
|
||||
void *state;
|
||||
Iterator i;
|
||||
int r;
|
||||
|
||||
assert(n);
|
||||
@ -99,7 +130,7 @@ int name_link_names(Name *n, bool replace) {
|
||||
|
||||
/* Link all names that aren't linked yet. */
|
||||
|
||||
SET_FOREACH(t, n->meta.names, state)
|
||||
SET_FOREACH(t, n->meta.names, i)
|
||||
if (replace) {
|
||||
if ((r = hashmap_replace(n->meta.manager->names, t, n)) < 0)
|
||||
return r;
|
||||
@ -125,24 +156,26 @@ int name_link(Name *n) {
|
||||
|
||||
if ((r = name_link_names(n, false)) < 0) {
|
||||
char *t;
|
||||
void *state;
|
||||
Iterator i;
|
||||
|
||||
/* Rollback the registered names */
|
||||
SET_FOREACH(t, n->meta.names, state)
|
||||
SET_FOREACH(t, n->meta.names, i)
|
||||
hashmap_remove_value(n->meta.manager->names, t, n);
|
||||
|
||||
n->meta.linked = false;
|
||||
return r;
|
||||
}
|
||||
|
||||
if (n->meta.load_state == NAME_STUB)
|
||||
LIST_PREPEND(Meta, n->meta.manager->load_queue, &n->meta);
|
||||
if (n->meta.load_state == NAME_STUB) {
|
||||
LIST_PREPEND(Meta, load_queue, n->meta.manager->load_queue, &n->meta);
|
||||
n->meta.in_load_queue = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bidi_set_free(Name *name, Set *s) {
|
||||
void *state;
|
||||
Iterator i;
|
||||
Name *other;
|
||||
|
||||
assert(name);
|
||||
@ -150,7 +183,7 @@ static void bidi_set_free(Name *name, Set *s) {
|
||||
/* Frees the set and makes sure we are dropped from the
|
||||
* inverse pointers */
|
||||
|
||||
SET_FOREACH(other, s, state) {
|
||||
SET_FOREACH(other, s, i) {
|
||||
NameDependency d;
|
||||
|
||||
for (d = 0; d < _NAME_DEPENDENCY_MAX; d++)
|
||||
@ -169,13 +202,13 @@ void name_free(Name *name) {
|
||||
/* Detach from next 'bigger' objects */
|
||||
if (name->meta.linked) {
|
||||
char *t;
|
||||
void *state;
|
||||
Iterator i;
|
||||
|
||||
SET_FOREACH(t, name->meta.names, state)
|
||||
SET_FOREACH(t, name->meta.names, i)
|
||||
hashmap_remove_value(name->meta.manager->names, t, name);
|
||||
|
||||
if (name->meta.load_state == NAME_STUB)
|
||||
LIST_REMOVE(Meta, name->meta.manager->load_queue, &name->meta);
|
||||
if (name->meta.in_load_queue)
|
||||
LIST_REMOVE(Meta, load_queue, name->meta.manager->load_queue, &name->meta);
|
||||
}
|
||||
|
||||
/* Free data and next 'smaller' objects */
|
||||
@ -185,8 +218,8 @@ void name_free(Name *name) {
|
||||
for (d = 0; d < _NAME_DEPENDENCY_MAX; d++)
|
||||
bidi_set_free(name, name->meta.dependencies[d]);
|
||||
|
||||
if (NAME_VTABLE(name)->free_hook)
|
||||
NAME_VTABLE(name)->free_hook(name);
|
||||
if (NAME_VTABLE(name)->done)
|
||||
NAME_VTABLE(name)->done(name);
|
||||
|
||||
free(name->meta.description);
|
||||
|
||||
@ -212,13 +245,11 @@ static int ensure_in_set(Set **s, void *data) {
|
||||
assert(s);
|
||||
assert(data);
|
||||
|
||||
if (!*s)
|
||||
if (!(*s = set_new(trivial_hash_func, trivial_compare_func)))
|
||||
return -ENOMEM;
|
||||
if ((r = set_ensure_allocated(s, trivial_hash_func, trivial_compare_func)) < 0)
|
||||
return r;
|
||||
|
||||
if ((r = set_put(*s, data)) < 0)
|
||||
if (r != -EEXIST)
|
||||
return r;
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -279,7 +310,7 @@ int name_merge(Name *name, Name *other) {
|
||||
/* FIXME: Does not rollback on failure! */
|
||||
static int augment(Name *n) {
|
||||
int r;
|
||||
void* state;
|
||||
Iterator i;
|
||||
Name *other;
|
||||
|
||||
assert(n);
|
||||
@ -287,29 +318,29 @@ static int augment(Name *n) {
|
||||
/* Adds in the missing links to make all dependencies
|
||||
* bidirectional. */
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], i)
|
||||
if ((r = ensure_in_set(&other->meta.dependencies[NAME_AFTER], n)) < 0)
|
||||
return r;
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], i)
|
||||
if ((r = ensure_in_set(&other->meta.dependencies[NAME_BEFORE], n)) < 0)
|
||||
return r;
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], i)
|
||||
if ((r = ensure_in_set(&other->meta.dependencies[NAME_CONFLICTS], n)) < 0)
|
||||
return r;
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], i)
|
||||
if ((r = ensure_in_set(&other->meta.dependencies[NAME_REQUIRED_BY], n)) < 0)
|
||||
return r;
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], i)
|
||||
if ((r = ensure_in_set(&other->meta.dependencies[NAME_REQUIRED_BY], n)) < 0)
|
||||
return r;
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], i)
|
||||
if ((r = ensure_in_set(&other->meta.dependencies[NAME_SOFT_REQUIRED_BY], n)) < 0)
|
||||
return r;
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], i)
|
||||
if ((r = ensure_in_set(&other->meta.dependencies[NAME_WANTED_BY], n)) < 0)
|
||||
return r;
|
||||
|
||||
@ -331,6 +362,9 @@ int name_sanitize(Name *n) {
|
||||
const char* name_id(Name *n) {
|
||||
assert(n);
|
||||
|
||||
if (n->meta.id)
|
||||
return n->meta.id;
|
||||
|
||||
return set_first(n->meta.names);
|
||||
}
|
||||
|
||||
@ -372,9 +406,9 @@ void name_dump(Name *n, FILE *f, const char *prefix) {
|
||||
[NAME_AFTER] = "After",
|
||||
};
|
||||
|
||||
void *state;
|
||||
char *t;
|
||||
NameDependency d;
|
||||
Iterator i;
|
||||
|
||||
assert(n);
|
||||
|
||||
@ -391,17 +425,16 @@ void name_dump(Name *n, FILE *f, const char *prefix) {
|
||||
prefix, load_state_table[n->meta.load_state],
|
||||
prefix, active_state_table[name_active_state(n)]);
|
||||
|
||||
SET_FOREACH(t, n->meta.names, state)
|
||||
SET_FOREACH(t, n->meta.names, i)
|
||||
fprintf(f, "%s\tName: %s\n", prefix, t);
|
||||
|
||||
for (d = 0; d < _NAME_DEPENDENCY_MAX; d++) {
|
||||
void *state;
|
||||
Name *other;
|
||||
|
||||
if (set_isempty(n->meta.dependencies[d]))
|
||||
continue;
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[d], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[d], i)
|
||||
fprintf(f, "%s\t%s: %s\n", prefix, dependency_table[d], name_id(other));
|
||||
}
|
||||
|
||||
@ -423,13 +456,13 @@ void name_dump(Name *n, FILE *f, const char *prefix) {
|
||||
|
||||
static int verify_type(Name *name) {
|
||||
char *n;
|
||||
void *state;
|
||||
Iterator i;
|
||||
|
||||
assert(name);
|
||||
|
||||
/* Checks that all aliases of this name have the same and valid type */
|
||||
|
||||
SET_FOREACH(n, name->meta.names, state) {
|
||||
SET_FOREACH(n, name->meta.names, i) {
|
||||
NameType t;
|
||||
|
||||
if ((t = name_type_from_string(n)) == _NAME_TYPE_INVALID)
|
||||
@ -472,14 +505,19 @@ int name_load(Name *name) {
|
||||
|
||||
assert(name);
|
||||
|
||||
if (name->meta.in_load_queue) {
|
||||
LIST_REMOVE(Meta, load_queue, name->meta.manager->load_queue, &name->meta);
|
||||
name->meta.in_load_queue = false;
|
||||
}
|
||||
|
||||
if (name->meta.load_state != NAME_STUB)
|
||||
return 0;
|
||||
|
||||
if ((r = verify_type(name)) < 0)
|
||||
return r;
|
||||
|
||||
if (NAME_VTABLE(name)->load)
|
||||
if ((r = NAME_VTABLE(name)->load(name)) < 0)
|
||||
if (NAME_VTABLE(name)->init)
|
||||
if ((r = NAME_VTABLE(name)->init(name)) < 0)
|
||||
goto fail;
|
||||
|
||||
if ((r = name_sanitize(name)) < 0)
|
||||
@ -513,8 +551,11 @@ int name_start(Name *n) {
|
||||
if (NAME_IS_ACTIVE_OR_RELOADING(state))
|
||||
return -EALREADY;
|
||||
|
||||
if (state == NAME_ACTIVATING)
|
||||
return 0;
|
||||
/* We don't suppress calls to ->start() here when we are
|
||||
* already starting, to allow this request to be used as a
|
||||
* "hurry up" call, for example when the name is in some "auto
|
||||
* restart" state where it waits for a holdoff timer to elapse
|
||||
* before it will start again. */
|
||||
|
||||
return NAME_VTABLE(n)->start(n);
|
||||
}
|
||||
@ -558,7 +599,7 @@ int name_reload(Name *n) {
|
||||
|
||||
assert(n);
|
||||
|
||||
if (!NAME_VTABLE(n)->reload)
|
||||
if (!name_can_reload(n))
|
||||
return -EBADR;
|
||||
|
||||
state = name_active_state(n);
|
||||
@ -573,45 +614,58 @@ int name_reload(Name *n) {
|
||||
|
||||
bool name_type_can_reload(NameType t) {
|
||||
assert(t >= 0 && t < _NAME_TYPE_MAX);
|
||||
|
||||
return !!name_vtable[t]->reload;
|
||||
}
|
||||
|
||||
bool name_can_reload(Name *n) {
|
||||
assert(n);
|
||||
|
||||
if (!name_type_can_reload(n->meta.type))
|
||||
return false;
|
||||
|
||||
if (!NAME_VTABLE(n)->can_reload)
|
||||
return true;
|
||||
|
||||
return NAME_VTABLE(n)->can_reload(n);
|
||||
}
|
||||
|
||||
static void retroactively_start_dependencies(Name *n) {
|
||||
void *state;
|
||||
Iterator i;
|
||||
Name *other;
|
||||
|
||||
assert(n);
|
||||
assert(NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(n)));
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], i)
|
||||
if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
|
||||
manager_add_job(n->meta.manager, JOB_START, other, JOB_REPLACE, true, NULL);
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], i)
|
||||
if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
|
||||
manager_add_job(n->meta.manager, JOB_START, other, JOB_FAIL, false, NULL);
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUISITE], i)
|
||||
if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
|
||||
manager_add_job(n->meta.manager, JOB_START, other, JOB_REPLACE, true, NULL);
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], i)
|
||||
if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
|
||||
manager_add_job(n->meta.manager, JOB_START, other, JOB_FAIL, false, NULL);
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], i)
|
||||
if (!NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(other)))
|
||||
manager_add_job(n->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL);
|
||||
}
|
||||
|
||||
static void retroactively_stop_dependencies(Name *n) {
|
||||
void *state;
|
||||
Iterator i;
|
||||
Name *other;
|
||||
|
||||
assert(n);
|
||||
assert(NAME_IS_INACTIVE_OR_DEACTIVATING(name_active_state(n)));
|
||||
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], state)
|
||||
SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], i)
|
||||
if (!NAME_IS_INACTIVE_OR_DEACTIVATING(name_active_state(other)))
|
||||
manager_add_job(n->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL);
|
||||
}
|
||||
@ -626,6 +680,11 @@ void name_notify(Name *n, NameActiveState os, NameActiveState ns) {
|
||||
if (os == ns)
|
||||
return;
|
||||
|
||||
if (!NAME_IS_ACTIVE_OR_RELOADING(os) && NAME_IS_ACTIVE_OR_RELOADING(ns))
|
||||
n->meta.active_enter_timestamp = now(CLOCK_REALTIME);
|
||||
else if (NAME_IS_ACTIVE_OR_RELOADING(os) && !NAME_IS_ACTIVE_OR_RELOADING(ns))
|
||||
n->meta.active_exit_timestamp = now(CLOCK_REALTIME);
|
||||
|
||||
if (n->meta.job) {
|
||||
|
||||
if (n->meta.job->state == JOB_WAITING)
|
||||
@ -633,7 +692,7 @@ void name_notify(Name *n, NameActiveState os, NameActiveState ns) {
|
||||
/* So we reached a different state for this
|
||||
* job. Let's see if we can run it now if it
|
||||
* failed previously due to EAGAIN. */
|
||||
job_run_and_invalidate(n->meta.job);
|
||||
job_schedule_run(n->meta.job);
|
||||
|
||||
else {
|
||||
assert(n->meta.job->state == JOB_RUNNING);
|
||||
@ -709,12 +768,17 @@ int name_watch_fd(Name *n, int fd, uint32_t events) {
|
||||
zero(ev);
|
||||
ev.data.fd = fd;
|
||||
ev.data.ptr = n;
|
||||
ev.data.u32 = MANAGER_FD;
|
||||
ev.events = events;
|
||||
|
||||
if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
|
||||
return -errno;
|
||||
if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) >= 0)
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
if (errno == EEXIST)
|
||||
if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_MOD, fd, &ev) >= 0)
|
||||
return 0;
|
||||
|
||||
return -errno;
|
||||
}
|
||||
|
||||
void name_unwatch_fd(Name *n, int fd) {
|
||||
@ -737,3 +801,121 @@ void name_unwatch_pid(Name *n, pid_t pid) {
|
||||
|
||||
hashmap_remove(n->meta.manager->watch_pids, UINT32_TO_PTR(pid));
|
||||
}
|
||||
|
||||
int name_watch_timer(Name *n, usec_t delay, int *id) {
|
||||
struct epoll_event ev;
|
||||
int fd;
|
||||
struct itimerspec its;
|
||||
int flags;
|
||||
bool ours;
|
||||
|
||||
assert(n);
|
||||
assert(id);
|
||||
|
||||
/* This will try to reuse the old timer if there is one */
|
||||
|
||||
if (*id >= 0) {
|
||||
ours = false;
|
||||
fd = *id;
|
||||
|
||||
} else {
|
||||
ours = true;
|
||||
|
||||
if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0)
|
||||
return -errno;
|
||||
}
|
||||
|
||||
zero(its);
|
||||
|
||||
if (delay <= 0) {
|
||||
/* Set absolute time in the past, but not 0, since we
|
||||
* don't want to disarm the timer */
|
||||
its.it_value.tv_sec = 0;
|
||||
its.it_value.tv_nsec = 1;
|
||||
|
||||
flags = TFD_TIMER_ABSTIME;
|
||||
} else {
|
||||
timespec_store(&its.it_value, delay);
|
||||
flags = 0;
|
||||
}
|
||||
|
||||
/* This will also flush the elapse counter */
|
||||
if (timerfd_settime(fd, flags, &its, NULL) < 0)
|
||||
goto fail;
|
||||
|
||||
zero(ev);
|
||||
ev.data.fd = fd;
|
||||
ev.data.ptr = n;
|
||||
ev.data.u32 = MANAGER_TIMER;
|
||||
ev.events = POLLIN;
|
||||
|
||||
if (epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
|
||||
goto fail;
|
||||
|
||||
*id = fd;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (ours)
|
||||
assert_se(close_nointr(fd) == 0);
|
||||
|
||||
return -errno;
|
||||
}
|
||||
|
||||
void name_unwatch_timer(Name *n, int *id) {
|
||||
assert(n);
|
||||
assert(id);
|
||||
|
||||
if (*id >= 0) {
|
||||
assert_se(epoll_ctl(n->meta.manager->epoll_fd, EPOLL_CTL_DEL, *id, NULL) >= 0);
|
||||
assert_se(close_nointr(*id) == 0);
|
||||
|
||||
*id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
char *name_change_suffix(const char *t, const char *suffix) {
|
||||
char *e, *n;
|
||||
size_t a, b;
|
||||
|
||||
assert(t);
|
||||
assert(name_is_valid(t));
|
||||
assert(suffix);
|
||||
|
||||
assert_se(e = strrchr(t, '.'));
|
||||
a = e - t;
|
||||
b = strlen(suffix);
|
||||
|
||||
if (!(n = new(char, a + b + 1)))
|
||||
return NULL;
|
||||
|
||||
memcpy(n, t, a);
|
||||
memcpy(n+a, t, b+1);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
bool name_job_is_applicable(Name *n, JobType j) {
|
||||
assert(n);
|
||||
assert(j >= 0 && j < _JOB_TYPE_MAX);
|
||||
|
||||
switch (j) {
|
||||
case JOB_VERIFY_ACTIVE:
|
||||
case JOB_START:
|
||||
return true;
|
||||
|
||||
case JOB_STOP:
|
||||
case JOB_RESTART:
|
||||
case JOB_TRY_RESTART:
|
||||
return name_can_start(n);
|
||||
|
||||
case JOB_RELOAD:
|
||||
return name_can_reload(n);
|
||||
|
||||
case JOB_RELOAD_OR_START:
|
||||
return name_can_reload(n) && name_can_start(n);
|
||||
|
||||
default:
|
||||
assert_not_reached("Invalid job type");
|
||||
}
|
||||
}
|
||||
|
36
name.h
36
name.h
@ -21,8 +21,11 @@ typedef enum NameDependency NameDependency;
|
||||
#include "list.h"
|
||||
#include "socket-util.h"
|
||||
#include "execute.h"
|
||||
#include "util.h"
|
||||
|
||||
#define NAME_MAX 32
|
||||
#define DEFAULT_TIMEOUT_USEC (20*USEC_PER_SEC)
|
||||
#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
|
||||
|
||||
enum NameType {
|
||||
NAME_SERVICE = 0,
|
||||
@ -90,6 +93,8 @@ struct Meta {
|
||||
NameType type;
|
||||
NameLoadState load_state;
|
||||
|
||||
char *id; /* One name is special because we use it for identification. Points to an entry in the names set */
|
||||
|
||||
Set *names;
|
||||
Set *dependencies[_NAME_DEPENDENCY_MAX];
|
||||
|
||||
@ -100,9 +105,13 @@ struct Meta {
|
||||
Job *job;
|
||||
|
||||
bool linked:1;
|
||||
bool in_load_queue:1;
|
||||
|
||||
usec_t active_enter_timestamp;
|
||||
usec_t active_exit_timestamp;
|
||||
|
||||
/* Load queue */
|
||||
LIST_FIELDS(Meta);
|
||||
LIST_FIELDS(Meta, load_queue);
|
||||
};
|
||||
|
||||
#include "service.h"
|
||||
@ -129,21 +138,27 @@ union Name {
|
||||
struct NameVTable {
|
||||
const char *suffix;
|
||||
|
||||
int (*load)(Name *n);
|
||||
int (*init)(Name *n);
|
||||
void (*done)(Name *n);
|
||||
|
||||
void (*dump)(Name *n, FILE *f, const char *prefix);
|
||||
|
||||
int (*start)(Name *n);
|
||||
int (*stop)(Name *n);
|
||||
int (*reload)(Name *n);
|
||||
|
||||
|
||||
bool (*can_reload)(Name *n);
|
||||
|
||||
/* Boils down the more complex internal state of this name to
|
||||
* a simpler one that the engine can understand */
|
||||
NameActiveState (*active_state)(Name *n);
|
||||
|
||||
void (*fd_event)(Name *n, int fd, uint32_t events);
|
||||
void (*sigchld_event)(Name *n, pid_t pid, int code, int status);
|
||||
void (*timer_event)(Name *n, int id, uint64_t n_elapsed);
|
||||
|
||||
void (*free_hook)(Name *n);
|
||||
void (*retry)(Name *n);
|
||||
};
|
||||
|
||||
extern const NameVTable * const name_vtable[_NAME_TYPE_MAX];
|
||||
@ -171,10 +186,10 @@ DEFINE_CAST(MOUNT, Mount);
|
||||
DEFINE_CAST(AUTOMOUNT, Automount);
|
||||
DEFINE_CAST(SNAPSHOT, Snapshot);
|
||||
|
||||
NameActiveState name_active_state(Name *name);
|
||||
|
||||
bool name_type_can_start(NameType t);
|
||||
bool name_type_can_reload(NameType t);
|
||||
bool name_can_reload(Name *n);
|
||||
#define name_can_start(n) name_type_can_start((n)->meta.type)
|
||||
|
||||
NameType name_type_from_string(const char *n);
|
||||
bool name_is_valid(const char *n);
|
||||
@ -190,6 +205,10 @@ int name_load(Name *name);
|
||||
const char* name_id(Name *n);
|
||||
const char *name_description(Name *n);
|
||||
|
||||
int name_add_name(Name *n, const char *text);
|
||||
|
||||
NameActiveState name_active_state(Name *name);
|
||||
|
||||
void name_dump(Name *n, FILE *f, const char *prefix);
|
||||
|
||||
int name_start(Name *n);
|
||||
@ -204,4 +223,11 @@ void name_unwatch_fd(Name *n, int fd);
|
||||
int name_watch_pid(Name *n, pid_t pid);
|
||||
void name_unwatch_pid(Name *n, pid_t pid);
|
||||
|
||||
int name_watch_timer(Name *n, usec_t delay, int *id);
|
||||
void name_unwatch_timer(Name *n, int *id);
|
||||
|
||||
char *name_change_suffix(const char *t, const char *suffix);
|
||||
|
||||
bool name_job_is_applicable(Name *n, JobType j);
|
||||
|
||||
#endif
|
||||
|
824
service.c
824
service.c
@ -1,11 +1,30 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8 -*-*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "name.h"
|
||||
#include "service.h"
|
||||
#include "load-fragment.h"
|
||||
#include "load-dropin.h"
|
||||
#include "log.h"
|
||||
|
||||
static const NameActiveState state_table[_SERVICE_STATE_MAX] = {
|
||||
[SERVICE_DEAD] = NAME_INACTIVE,
|
||||
[SERVICE_START_PRE] = NAME_ACTIVATING,
|
||||
[SERVICE_START] = NAME_ACTIVATING,
|
||||
[SERVICE_START_POST] = NAME_ACTIVATING,
|
||||
[SERVICE_RUNNING] = NAME_ACTIVE,
|
||||
[SERVICE_RELOAD] = NAME_ACTIVE_RELOADING,
|
||||
[SERVICE_STOP] = NAME_DEACTIVATING,
|
||||
[SERVICE_STOP_SIGTERM] = NAME_DEACTIVATING,
|
||||
[SERVICE_STOP_SIGKILL] = NAME_DEACTIVATING,
|
||||
[SERVICE_STOP_POST] = NAME_DEACTIVATING,
|
||||
[SERVICE_FINAL_SIGTERM] = NAME_DEACTIVATING,
|
||||
[SERVICE_FINAL_SIGKILL] = NAME_DEACTIVATING,
|
||||
[SERVICE_MAINTAINANCE] = NAME_INACTIVE,
|
||||
[SERVICE_AUTO_RESTART] = NAME_ACTIVATING,
|
||||
};
|
||||
|
||||
static int service_load_sysv(Service *s) {
|
||||
assert(s);
|
||||
@ -16,13 +35,26 @@ static int service_load_sysv(Service *s) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int service_load(Name *n) {
|
||||
static int service_init(Name *n) {
|
||||
int r;
|
||||
Service *s = SERVICE(n);
|
||||
|
||||
assert(s);
|
||||
|
||||
exec_context_defaults(&s->exec_context);
|
||||
/* First, reset everything to the defaults, in case this is a
|
||||
* reload */
|
||||
|
||||
s->type = 0;
|
||||
s->restart = 0;
|
||||
|
||||
s->timeout_usec = DEFAULT_TIMEOUT_USEC;
|
||||
s->restart_usec = DEFAULT_RESTART_USEC;
|
||||
|
||||
exec_context_init(&s->exec_context);
|
||||
|
||||
s->timer_id = -1;
|
||||
|
||||
s->state = SERVICE_DEAD;
|
||||
|
||||
/* Load a .service file */
|
||||
r = name_load_fragment(n);
|
||||
@ -41,6 +73,33 @@ static int service_load(Name *n) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void service_done(Name *n) {
|
||||
Service *s = SERVICE(n);
|
||||
|
||||
assert(s);
|
||||
|
||||
free(s->pid_file);
|
||||
s->pid_file = NULL;
|
||||
|
||||
exec_context_done(&s->exec_context);
|
||||
exec_command_free_array(s->exec_command, _SERVICE_EXEC_MAX);
|
||||
s->control_command = NULL;
|
||||
|
||||
/* This will leak a process, but at least no memory or any of
|
||||
* our resources */
|
||||
if (s->main_pid > 0) {
|
||||
name_unwatch_pid(n, s->main_pid);
|
||||
s->main_pid = 0;
|
||||
}
|
||||
|
||||
if (s->control_pid > 0) {
|
||||
name_unwatch_pid(n, s->control_pid);
|
||||
s->control_pid = 0;
|
||||
}
|
||||
|
||||
name_unwatch_timer(n, &s->timer_id);
|
||||
}
|
||||
|
||||
static void service_dump(Name *n, FILE *f, const char *prefix) {
|
||||
|
||||
static const char* const state_table[_SERVICE_STATE_MAX] = {
|
||||
@ -49,27 +108,24 @@ static void service_dump(Name *n, FILE *f, const char *prefix) {
|
||||
[SERVICE_START] = "start",
|
||||
[SERVICE_START_POST] = "post",
|
||||
[SERVICE_RUNNING] = "running",
|
||||
[SERVICE_RELOAD_PRE] = "reload-pre",
|
||||
[SERVICE_RELOAD] = "reload",
|
||||
[SERVICE_RELOAD_POST] = "reload-post",
|
||||
[SERVICE_STOP_PRE] = "stop-pre",
|
||||
[SERVICE_STOP] = "stop",
|
||||
[SERVICE_SIGTERM] = "sigterm",
|
||||
[SERVICE_SIGKILL] = "sigkill",
|
||||
[SERVICE_STOP_SIGTERM] = "stop-sigterm",
|
||||
[SERVICE_STOP_SIGKILL] = "stop-sigkill",
|
||||
[SERVICE_STOP_POST] = "stop-post",
|
||||
[SERVICE_MAINTAINANCE] = "maintainance"
|
||||
[SERVICE_FINAL_SIGTERM] = "final-sigterm",
|
||||
[SERVICE_FINAL_SIGKILL] = "final-sigkill",
|
||||
[SERVICE_MAINTAINANCE] = "maintainance",
|
||||
[SERVICE_AUTO_RESTART] = "auto-restart",
|
||||
};
|
||||
|
||||
static const char* const command_table[_SERVICE_EXEC_MAX] = {
|
||||
[SERVICE_EXEC_START_PRE] = "StartPre",
|
||||
[SERVICE_EXEC_START] = "Start",
|
||||
[SERVICE_EXEC_START_POST] = "StartPost",
|
||||
[SERVICE_EXEC_RELOAD_PRE] = "ReloadPre",
|
||||
[SERVICE_EXEC_RELOAD] = "Reload",
|
||||
[SERVICE_EXEC_RELOAD_POST] = "ReloadPost",
|
||||
[SERVICE_EXEC_STOP_PRE] = "StopPre",
|
||||
[SERVICE_EXEC_STOP] = "Stop",
|
||||
[SERVICE_EXEC_STOP_POST] = "StopPost",
|
||||
[SERVICE_EXEC_START_PRE] = "ExecStartPre",
|
||||
[SERVICE_EXEC_START] = "ExecStart",
|
||||
[SERVICE_EXEC_START_POST] = "ExecStartPost",
|
||||
[SERVICE_EXEC_RELOAD] = "ExecReload",
|
||||
[SERVICE_EXEC_STOP] = "ExecStop",
|
||||
[SERVICE_EXEC_STOP_POST] = "ExecStopPost",
|
||||
};
|
||||
|
||||
ServiceExecCommand c;
|
||||
@ -81,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,
|
||||
};
|
||||
|
51
service.h
51
service.h
@ -6,8 +6,6 @@
|
||||
typedef struct Service Service;
|
||||
|
||||
#include "name.h"
|
||||
#include "socket.h"
|
||||
#include "timer.h"
|
||||
|
||||
typedef enum ServiceState {
|
||||
SERVICE_DEAD,
|
||||
@ -15,31 +13,34 @@ typedef enum ServiceState {
|
||||
SERVICE_START,
|
||||
SERVICE_START_POST,
|
||||
SERVICE_RUNNING,
|
||||
SERVICE_RELOAD_PRE,
|
||||
SERVICE_RELOAD,
|
||||
SERVICE_RELOAD_POST,
|
||||
SERVICE_STOP_PRE,
|
||||
SERVICE_STOP,
|
||||
SERVICE_SIGTERM,
|
||||
SERVICE_SIGKILL,
|
||||
SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */
|
||||
SERVICE_STOP_SIGTERM,
|
||||
SERVICE_STOP_SIGKILL,
|
||||
SERVICE_STOP_POST,
|
||||
SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */
|
||||
SERVICE_FINAL_SIGKILL,
|
||||
SERVICE_MAINTAINANCE,
|
||||
SERVICE_AUTO_RESTART,
|
||||
_SERVICE_STATE_MAX,
|
||||
} ServiceState;
|
||||
|
||||
typedef enum ServiceMode {
|
||||
typedef enum ServiceRestart {
|
||||
SERVICE_ONCE,
|
||||
SERVICE_RESTART
|
||||
} ServiceMode;
|
||||
SERVICE_RESTART_ON_SUCCESS,
|
||||
SERVICE_RESTART_ALWAYS
|
||||
} ServiceRestart;
|
||||
|
||||
typedef enum ServiceType {
|
||||
SERVICE_FORKING,
|
||||
SERVICE_SIMPLE
|
||||
} ServiceType;
|
||||
|
||||
typedef enum ServiceExecCommand {
|
||||
SERVICE_EXEC_START_PRE,
|
||||
SERVICE_EXEC_START,
|
||||
SERVICE_EXEC_START_POST,
|
||||
SERVICE_EXEC_RELOAD_PRE,
|
||||
SERVICE_EXEC_RELOAD,
|
||||
SERVICE_EXEC_RELOAD_POST,
|
||||
SERVICE_EXEC_STOP_PRE,
|
||||
SERVICE_EXEC_STOP,
|
||||
SERVICE_EXEC_STOP_POST,
|
||||
_SERVICE_EXEC_MAX
|
||||
@ -48,16 +49,28 @@ typedef enum ServiceExecCommand {
|
||||
struct Service {
|
||||
Meta meta;
|
||||
|
||||
ServiceState state;
|
||||
ServiceMode mode;
|
||||
ServiceType type;
|
||||
ServiceRestart restart;
|
||||
|
||||
/* If set we'll read the main daemon PID from this file */
|
||||
char *pid_file;
|
||||
|
||||
usec_t restart_usec;
|
||||
usec_t timeout_usec;
|
||||
|
||||
ExecCommand* exec_command[_SERVICE_EXEC_MAX];
|
||||
ExecContext exec_context;
|
||||
|
||||
pid_t service_pid, control_pid;
|
||||
ServiceState state;
|
||||
|
||||
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
16
set.c
@ -22,6 +22,10 @@ void set_free(Set* s) {
|
||||
hashmap_free(MAKE_HASHMAP(s));
|
||||
}
|
||||
|
||||
int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func) {
|
||||
return hashmap_ensure_allocated((Hashmap**) s, hash_func, compare_func);
|
||||
}
|
||||
|
||||
int set_put(Set *s, void *value) {
|
||||
return hashmap_put(MAKE_HASHMAP(s), value, value);
|
||||
}
|
||||
@ -46,12 +50,16 @@ bool set_isempty(Set *s) {
|
||||
return hashmap_isempty(MAKE_HASHMAP(s));
|
||||
}
|
||||
|
||||
void *set_iterate(Set *s, void **state) {
|
||||
return hashmap_iterate(MAKE_HASHMAP(s), state, NULL);
|
||||
void *set_iterate(Set *s, Iterator *i) {
|
||||
return hashmap_iterate(MAKE_HASHMAP(s), i, NULL);
|
||||
}
|
||||
|
||||
void *set_iterate_backwards(Set *s, 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
16
set.h
@ -14,8 +14,9 @@
|
||||
typedef struct Set Set;
|
||||
|
||||
Set *set_new(hash_func_t hash_func, compare_func_t compare_func);
|
||||
Set* set_copy(Set *s);
|
||||
void set_free(Set* s);
|
||||
Set* set_copy(Set *s);
|
||||
int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_func);
|
||||
|
||||
int set_put(Set *s, void *value);
|
||||
int set_replace(Set *s, void *value);
|
||||
@ -27,18 +28,19 @@ int set_merge(Set *s, Set *other);
|
||||
unsigned set_size(Set *s);
|
||||
bool set_isempty(Set *s);
|
||||
|
||||
void *set_iterate(Set *s, void **state);
|
||||
void *set_iterate_backwards(Set *s, void **state);
|
||||
void *set_iterate(Set *s, Iterator *i);
|
||||
void *set_iterate_backwards(Set *s, Iterator *i);
|
||||
void *set_iterate_skip(Set *s, void *value, Iterator *i);
|
||||
|
||||
void set_clear(Set *s);
|
||||
void *set_steal_first(Set *s);
|
||||
void* set_first(Set *s);
|
||||
void* set_last(Set *s);
|
||||
|
||||
#define SET_FOREACH(e, s, state) \
|
||||
for ((state) = NULL, (e) = set_iterate((s), &(state)); (e); (e) = set_iterate((s), &(state)))
|
||||
#define SET_FOREACH(e, s, i) \
|
||||
for ((i) = ITERATOR_FIRST, (e) = set_iterate((s), &(i)); (e); (e) = set_iterate((s), &(i)))
|
||||
|
||||
#define SET_FOREACH_BACKWARDS(e, s, state) \
|
||||
for ((state) = NULL, (e) = set_iterate_backwards((s), &(state)); (e); (e) = set_iterate_backwards((s), &(state)))
|
||||
#define SET_FOREACH_BACKWARDS(e, s, i) \
|
||||
for ((i) = ITERATOR_LAST, (e) = set_iterate_backwards((s), &(i)); (e); (e) = set_iterate_backwards((s), &(i)))
|
||||
|
||||
#endif
|
||||
|
21
snapshot.c
21
snapshot.c
@ -3,11 +3,7 @@
|
||||
#include "name.h"
|
||||
#include "snapshot.h"
|
||||
|
||||
static NameActiveState snapshot_active_state(Name *n) {
|
||||
return SNAPSHOT(n)->state == SNAPSHOT_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
|
||||
}
|
||||
|
||||
static void snapshot_free_hook(Name *n) {
|
||||
static void snapshot_done(Name *n) {
|
||||
Snapshot *s = SNAPSHOT(n);
|
||||
|
||||
assert(s);
|
||||
@ -15,17 +11,14 @@ static void snapshot_free_hook(Name *n) {
|
||||
/* Nothing here for now */
|
||||
}
|
||||
|
||||
static NameActiveState snapshot_active_state(Name *n) {
|
||||
return SNAPSHOT(n)->state == SNAPSHOT_DEAD ? NAME_INACTIVE : NAME_ACTIVE;
|
||||
}
|
||||
|
||||
const NameVTable snapshot_vtable = {
|
||||
.suffix = ".snapshot",
|
||||
|
||||
.load = NULL,
|
||||
.dump = NULL,
|
||||
.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
605
socket.c
@ -6,6 +6,7 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/poll.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "name.h"
|
||||
#include "socket.h"
|
||||
@ -18,17 +19,75 @@ static const NameActiveState state_table[_SOCKET_STATE_MAX] = {
|
||||
[SOCKET_LISTENING] = NAME_ACTIVE,
|
||||
[SOCKET_RUNNING] = NAME_ACTIVE,
|
||||
[SOCKET_STOP_PRE] = NAME_DEACTIVATING,
|
||||
[SOCKET_STOP_PRE_SIGTERM] = NAME_DEACTIVATING,
|
||||
[SOCKET_STOP_PRE_SIGKILL] = NAME_DEACTIVATING,
|
||||
[SOCKET_STOP_POST] = NAME_DEACTIVATING,
|
||||
[SOCKET_STOP_POST_SIGTERM] = NAME_DEACTIVATING,
|
||||
[SOCKET_STOP_POST_SIGKILL] = NAME_DEACTIVATING,
|
||||
[SOCKET_MAINTAINANCE] = NAME_INACTIVE,
|
||||
};
|
||||
|
||||
static int socket_load(Name *n) {
|
||||
static int socket_init(Name *n) {
|
||||
Socket *s = SOCKET(n);
|
||||
char *t;
|
||||
int r;
|
||||
|
||||
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
|
||||
};
|
||||
|
18
socket.h
18
socket.h
@ -15,7 +15,11 @@ typedef enum SocketState {
|
||||
SOCKET_LISTENING,
|
||||
SOCKET_RUNNING,
|
||||
SOCKET_STOP_PRE,
|
||||
SOCKET_STOP_PRE_SIGTERM,
|
||||
SOCKET_STOP_PRE_SIGKILL,
|
||||
SOCKET_STOP_POST,
|
||||
SOCKET_STOP_POST_SIGTERM,
|
||||
SOCKET_STOP_POST_SIGKILL,
|
||||
SOCKET_MAINTAINANCE,
|
||||
_SOCKET_STATE_MAX
|
||||
} SocketState;
|
||||
@ -43,26 +47,32 @@ struct SocketPort {
|
||||
|
||||
int fd;
|
||||
|
||||
LIST_FIELDS(SocketPort);
|
||||
LIST_FIELDS(SocketPort, port);
|
||||
};
|
||||
|
||||
struct Socket {
|
||||
Meta meta;
|
||||
|
||||
SocketState state;
|
||||
|
||||
LIST_HEAD(SocketPort, ports);
|
||||
|
||||
/* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */
|
||||
bool bind_ipv6_only;
|
||||
unsigned backlog;
|
||||
|
||||
usec_t timeout_usec;
|
||||
|
||||
ExecCommand* exec_command[_SOCKET_EXEC_MAX];
|
||||
ExecContext exec_context;
|
||||
|
||||
Service *service;
|
||||
|
||||
SocketState state;
|
||||
|
||||
ExecCommand* control_command;
|
||||
pid_t control_pid;
|
||||
|
||||
Service *service;
|
||||
bool failure;
|
||||
int timer_id;
|
||||
};
|
||||
|
||||
extern const NameVTable socket_vtable;
|
||||
|
30
strv.c
30
strv.c
@ -115,3 +115,33 @@ fail:
|
||||
free(a);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char **strv_merge(char **a, char **b) {
|
||||
char **r, **k;
|
||||
|
||||
if (!a)
|
||||
return strv_copy(b);
|
||||
|
||||
if (!b)
|
||||
return strv_copy(a);
|
||||
|
||||
if (!(r = new(char*, strv_length(a)+strv_length(b)+1)))
|
||||
return NULL;
|
||||
|
||||
for (k = r; *a; k++, a++)
|
||||
if (!(*k = strdup(*a)))
|
||||
goto fail;
|
||||
for (; *b; k++, b++)
|
||||
if (!(*k = strdup(*b)))
|
||||
goto fail;
|
||||
|
||||
*k = NULL;
|
||||
return r;
|
||||
|
||||
fail:
|
||||
for (k--; k >= r; k--)
|
||||
free(*k);
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
2
strv.h
2
strv.h
@ -10,6 +10,8 @@ void strv_free(char **l);
|
||||
char **strv_copy(char **l);
|
||||
unsigned strv_length(char **l);
|
||||
|
||||
char **strv_merge(char **a, char **b);
|
||||
|
||||
char **strv_new(const char *x, ...) __sentinel;
|
||||
|
||||
#define STRV_FOREACH(s, l) \
|
||||
|
27
timer.c
27
timer.c
@ -3,6 +3,12 @@
|
||||
#include "name.h"
|
||||
#include "timer.h"
|
||||
|
||||
static void timer_done(Name *n) {
|
||||
Timer *t = TIMER(n);
|
||||
|
||||
assert(t);
|
||||
}
|
||||
|
||||
static NameActiveState timer_active_state(Name *n) {
|
||||
|
||||
static const NameActiveState table[_TIMER_STATE_MAX] = {
|
||||
@ -14,26 +20,11 @@ static NameActiveState timer_active_state(Name *n) {
|
||||
return table[TIMER(n)->state];
|
||||
}
|
||||
|
||||
static void timer_free_hook(Name *n) {
|
||||
Timer *t = TIMER(n);
|
||||
|
||||
assert(t);
|
||||
|
||||
if (t->service)
|
||||
t->service->timer = NULL;
|
||||
}
|
||||
|
||||
const NameVTable timer_vtable = {
|
||||
.suffix = ".timer",
|
||||
|
||||
.load = name_load_fragment_and_dropin,
|
||||
.dump = NULL,
|
||||
.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
228
util.c
@ -5,6 +5,8 @@
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "macro.h"
|
||||
#include "util.h"
|
||||
@ -108,7 +110,7 @@ int parse_boolean(const char *v) {
|
||||
|
||||
int safe_atou(const char *s, unsigned *ret_u) {
|
||||
char *x = NULL;
|
||||
unsigned l;
|
||||
unsigned long l;
|
||||
|
||||
assert(s);
|
||||
assert(ret_u);
|
||||
@ -119,7 +121,7 @@ int safe_atou(const char *s, unsigned *ret_u) {
|
||||
if (!x || *x || errno)
|
||||
return errno ? -errno : -EINVAL;
|
||||
|
||||
if ((unsigned) l != l)
|
||||
if ((unsigned long) (unsigned) l != l)
|
||||
return -ERANGE;
|
||||
|
||||
*ret_u = (unsigned) l;
|
||||
@ -128,7 +130,7 @@ int safe_atou(const char *s, unsigned *ret_u) {
|
||||
|
||||
int safe_atoi(const char *s, int *ret_i) {
|
||||
char *x = NULL;
|
||||
int l;
|
||||
long l;
|
||||
|
||||
assert(s);
|
||||
assert(ret_i);
|
||||
@ -139,10 +141,78 @@ int safe_atoi(const char *s, int *ret_i) {
|
||||
if (!x || *x || errno)
|
||||
return errno ? -errno : -EINVAL;
|
||||
|
||||
if ((int) l != l)
|
||||
if ((long) (int) l != l)
|
||||
return -ERANGE;
|
||||
|
||||
*ret_i = (unsigned) l;
|
||||
*ret_i = (int) l;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int safe_atolu(const char *s, long unsigned *ret_lu) {
|
||||
char *x = NULL;
|
||||
unsigned long l;
|
||||
|
||||
assert(s);
|
||||
assert(ret_lu);
|
||||
|
||||
errno = 0;
|
||||
l = strtoul(s, &x, 0);
|
||||
|
||||
if (!x || *x || errno)
|
||||
return errno ? -errno : -EINVAL;
|
||||
|
||||
*ret_lu = l;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int safe_atoli(const char *s, long int *ret_li) {
|
||||
char *x = NULL;
|
||||
long l;
|
||||
|
||||
assert(s);
|
||||
assert(ret_li);
|
||||
|
||||
errno = 0;
|
||||
l = strtol(s, &x, 0);
|
||||
|
||||
if (!x || *x || errno)
|
||||
return errno ? -errno : -EINVAL;
|
||||
|
||||
*ret_li = l;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int safe_atollu(const char *s, long long unsigned *ret_llu) {
|
||||
char *x = NULL;
|
||||
unsigned long long l;
|
||||
|
||||
assert(s);
|
||||
assert(ret_llu);
|
||||
|
||||
errno = 0;
|
||||
l = strtoull(s, &x, 0);
|
||||
|
||||
if (!x || *x || errno)
|
||||
return errno ? -errno : -EINVAL;
|
||||
|
||||
*ret_llu = l;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int safe_atolli(const char *s, long long int *ret_lli) {
|
||||
char *x = NULL;
|
||||
long long l;
|
||||
|
||||
assert(s);
|
||||
assert(ret_lli);
|
||||
|
||||
errno = 0;
|
||||
l = strtoll(s, &x, 0);
|
||||
|
||||
if (!x || *x || errno)
|
||||
return errno ? -errno : -EINVAL;
|
||||
|
||||
*ret_lli = l;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -164,3 +234,151 @@ char *split_spaces(const char *c, size_t *l, char **state) {
|
||||
|
||||
return (char*) current;
|
||||
}
|
||||
|
||||
/* Split a string into words, but consider strings enclosed in '' and
|
||||
* "" as words even if they include spaces. */
|
||||
char *split_quoted(const char *c, size_t *l, char **state) {
|
||||
char *current;
|
||||
|
||||
current = *state ? *state : (char*) c;
|
||||
|
||||
if (!*current || *c == 0)
|
||||
return NULL;
|
||||
|
||||
current += strspn(current, WHITESPACE);
|
||||
|
||||
if (*current == '\'') {
|
||||
current ++;
|
||||
*l = strcspn(current, "'");
|
||||
*state = current+*l;
|
||||
|
||||
if (**state == '\'')
|
||||
(*state)++;
|
||||
} else if (*current == '\"') {
|
||||
current ++;
|
||||
*l = strcspn(current+1, "\"");
|
||||
*state = current+*l;
|
||||
|
||||
if (**state == '\"')
|
||||
(*state)++;
|
||||
} else {
|
||||
*l = strcspn(current, WHITESPACE);
|
||||
*state = current+*l;
|
||||
}
|
||||
|
||||
return (char*) current;
|
||||
}
|
||||
|
||||
const char *sigchld_code(int code) {
|
||||
|
||||
if (code == CLD_EXITED)
|
||||
return "exited";
|
||||
else if (code == CLD_KILLED)
|
||||
return "killed";
|
||||
else if (code == CLD_DUMPED)
|
||||
return "dumped";
|
||||
else if (code == CLD_TRAPPED)
|
||||
return "trapped";
|
||||
else if (code == CLD_STOPPED)
|
||||
return "stopped";
|
||||
else if (code == CLD_CONTINUED)
|
||||
return "continued";
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
|
||||
int r;
|
||||
FILE *f;
|
||||
char fn[132], line[256], *p;
|
||||
long long unsigned ppid;
|
||||
|
||||
assert(pid >= 0);
|
||||
assert(_ppid);
|
||||
|
||||
assert_se(snprintf(fn, sizeof(fn)-1, "/proc/%llu/stat", (unsigned long long) pid) < (int) (sizeof(fn)-1));
|
||||
fn[sizeof(fn)-1] = 0;
|
||||
|
||||
if (!(f = fopen(fn, "r")))
|
||||
return -errno;
|
||||
|
||||
if (!(fgets(line, sizeof(line), f))) {
|
||||
r = -errno;
|
||||
fclose(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
/* Let's skip the pid and comm fields. The latter is enclosed
|
||||
* in () but does not escape any () in its value, so let's
|
||||
* skip over it manually */
|
||||
|
||||
if (!(p = strrchr(line, ')')))
|
||||
return -EIO;
|
||||
|
||||
p++;
|
||||
|
||||
if (sscanf(p, " "
|
||||
"%*c " /* state */
|
||||
"%llu ", /* ppid */
|
||||
&ppid) != 1)
|
||||
return -EIO;
|
||||
|
||||
if ((long long unsigned) (pid_t) ppid != ppid)
|
||||
return -ERANGE;
|
||||
|
||||
*_ppid = (pid_t) ppid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int write_one_line_file(const char *fn, const char *line) {
|
||||
FILE *f;
|
||||
int r;
|
||||
|
||||
assert(fn);
|
||||
assert(line);
|
||||
|
||||
if (!(f = fopen(fn, "we")))
|
||||
return -errno;
|
||||
|
||||
if (fputs(line, f) < 0) {
|
||||
r = -errno;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = 0;
|
||||
finish:
|
||||
fclose(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
int read_one_line_file(const char *fn, char **line) {
|
||||
FILE *f;
|
||||
int r;
|
||||
char t[64], *c;
|
||||
|
||||
assert(fn);
|
||||
assert(line);
|
||||
|
||||
if (!(f = fopen(fn, "re")))
|
||||
return -errno;
|
||||
|
||||
if (!(fgets(t, sizeof(t), f))) {
|
||||
r = -errno;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (!(c = strdup(t))) {
|
||||
r = -ENOMEM;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
*line = c;
|
||||
r = 0;
|
||||
|
||||
finish:
|
||||
fclose(f);
|
||||
return r;
|
||||
}
|
||||
|
27
util.h
27
util.h
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user