From 5cb5a6ffc33667c93e9bc3572534dcaa684046e3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 23 Jan 2010 01:52:57 +0100 Subject: [PATCH] first attempt in implementinging execution logic --- Makefile | 26 ++- automount.c | 111 ++++++++++ automount.h | 46 ++++ device.c | 47 +++++ device.h | 29 +++ execute.c | 68 ++++++ execute.h | 59 ++++++ job.c | 257 ++++++++++++++++++++--- job.h | 31 ++- load-dropin.c | 11 + load-dropin.h | 12 ++ load-fragment.c | 6 +- load-fstab.c | 11 + load-fstab.h | 12 ++ manager.c | 40 ++-- manager.h | 2 + milestone.c | 32 +++ milestone.h | 23 ++ mount.c | 86 ++++++++ mount.h | 28 +++ name.c | 545 +++++++++++++++++++++++------------------------- name.h | 284 ++++++++----------------- service.c | 182 ++++++++++++++++ service.h | 65 ++++++ snapshot.c | 31 +++ snapshot.h | 24 +++ socket.c | 113 ++++++++++ socket.h | 49 +++++ test-job-type.c | 14 +- test2/h.service | 3 + timer.c | 39 ++++ timer.h | 30 +++ util.h | 1 + 33 files changed, 1775 insertions(+), 542 deletions(-) create mode 100644 automount.c create mode 100644 automount.h create mode 100644 device.c create mode 100644 device.h create mode 100644 execute.c create mode 100644 execute.h create mode 100644 load-dropin.c create mode 100644 load-dropin.h create mode 100644 load-fstab.c create mode 100644 load-fstab.h create mode 100644 milestone.c create mode 100644 milestone.h create mode 100644 mount.c create mode 100644 mount.h create mode 100644 service.c create mode 100644 service.h create mode 100644 snapshot.c create mode 100644 snapshot.h create mode 100644 socket.c create mode 100644 socket.h create mode 100644 test2/h.service create mode 100644 timer.c create mode 100644 timer.h diff --git a/Makefile b/Makefile index 5c6d798f..ac815a1f 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,29 @@ CFLAGS=-Wall -Wextra -O0 -g -pipe -D_GNU_SOURCE -fdiagnostics-show-option -Wno-unused-parameter -LIBS=-lrt +LIBS=-lrt -lcap -COMMON=name.o util.o set.o hashmap.o strv.o job.o manager.o conf-parser.o load-fragment.o socket-util.o log.o +COMMON= \ + name.o \ + util.o \ + set.o \ + hashmap.o \ + strv.o \ + job.o \ + manager.o \ + conf-parser.o \ + load-fragment.o \ + socket-util.o \ + log.o \ + service.o \ + automount.o \ + mount.o \ + device.o \ + milestone.o \ + snapshot.o \ + socket.o \ + timer.o \ + load-fstab.o \ + load-dropin.o \ + execute.o all: systemd test-engine test-job-type diff --git a/automount.c b/automount.c new file mode 100644 index 00000000..84691e6e --- /dev/null +++ b/automount.c @@ -0,0 +1,111 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include + +#include "name.h" +#include "automount.h" +#include "load-fragment.h" +#include "load-fstab.h" +#include "load-dropin.h" + +static int automount_load(Name *n) { + int r; + Automount *a = AUTOMOUNT(n); + + assert(a); + + exec_context_defaults(&a->exec_context); + + /* Load a .automount file */ + if ((r = name_load_fragment(n)) < 0 && errno != -ENOENT) + return r; + + /* Load entry from /etc/fstab */ + if ((r = name_load_fstab(n)) < 0) + return r; + + /* Load drop-in directory data */ + if ((r = name_load_dropin(n)) < 0) + return r; + + return 0; +} + +static void automount_dump(Name *n, FILE *f, const char *prefix) { + + static const char* const state_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = "dead", + [AUTOMOUNT_START_PRE] = "start-pre", + [AUTOMOUNT_START_POST] = "start-post", + [AUTOMOUNT_WAITING] = "waiting", + [AUTOMOUNT_RUNNING] = "running", + [AUTOMOUNT_STOP_PRE] = "stop-pre", + [AUTOMOUNT_STOP_POST] = "stop-post", + [AUTOMOUNT_MAINTAINANCE] = "maintainance" + }; + + static const char* const command_table[_AUTOMOUNT_EXEC_MAX] = { + [AUTOMOUNT_EXEC_START_PRE] = "StartPre", + [AUTOMOUNT_EXEC_START_POST] = "StartPost", + [AUTOMOUNT_EXEC_STOP_PRE] = "StopPre", + [AUTOMOUNT_EXEC_STOP_POST] = "StopPost" + }; + + AutomountExecCommand c; + Automount *s = AUTOMOUNT(n); + + assert(s); + + fprintf(f, + "%sAutomount State: %s\n" + "%sPath: %s\n", + prefix, state_table[s->state], + prefix, s->path); + + exec_context_dump(&s->exec_context, f, prefix); + + for (c = 0; c < _AUTOMOUNT_EXEC_MAX; c++) { + ExecCommand *i; + + LIST_FOREACH(i, s->exec_command[c]) + fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path); + } +} + +static NameActiveState automount_active_state(Name *n) { + + static const NameActiveState table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = NAME_INACTIVE, + [AUTOMOUNT_START_PRE] = NAME_ACTIVATING, + [AUTOMOUNT_START_POST] = NAME_ACTIVATING, + [AUTOMOUNT_WAITING] = NAME_ACTIVE, + [AUTOMOUNT_RUNNING] = NAME_ACTIVE, + [AUTOMOUNT_STOP_PRE] = NAME_DEACTIVATING, + [AUTOMOUNT_STOP_POST] = NAME_DEACTIVATING, + [AUTOMOUNT_MAINTAINANCE] = NAME_INACTIVE, + }; + + return table[AUTOMOUNT(n)->state]; +} + +static void automount_free_hook(Name *n) { + Automount *d = AUTOMOUNT(n); + + assert(d); + free(d->path); +} + +const NameVTable automount_vtable = { + .suffix = ".mount", + + .load = automount_load, + .dump = automount_dump, + + .start = NULL, + .stop = NULL, + .reload = NULL, + + .active_state = automount_active_state, + + .free_hook = automount_free_hook +}; diff --git a/automount.h b/automount.h new file mode 100644 index 00000000..b6dfd5bb --- /dev/null +++ b/automount.h @@ -0,0 +1,46 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef fooautomounthfoo +#define fooautomounthfoo + +typedef struct Automount Automount; + +#include "name.h" + +typedef enum AutomountState { + AUTOMOUNT_DEAD, + AUTOMOUNT_START_PRE, + AUTOMOUNT_START_POST, + AUTOMOUNT_WAITING, + AUTOMOUNT_RUNNING, + AUTOMOUNT_STOP_PRE, + AUTOMOUNT_STOP_POST, + AUTOMOUNT_MAINTAINANCE, + _AUTOMOUNT_STATE_MAX +} AutomountState; + +typedef enum AutomountExecCommand { + AUTOMOUNT_EXEC_START_PRE, + AUTOMOUNT_EXEC_START_POST, + AUTOMOUNT_EXEC_STOP_PRE, + AUTOMOUNT_EXEC_STOP_POST, + _AUTOMOUNT_EXEC_MAX +} AutomountExecCommand; + +struct Automount { + Meta meta; + + AutomountState state; + char *path; + + ExecCommand* exec_command[_AUTOMOUNT_EXEC_MAX]; + ExecContext exec_context; + + pid_t contol_pid; + + Mount *mount; +}; + +extern const NameVTable automount_vtable; + +#endif diff --git a/device.c b/device.c new file mode 100644 index 00000000..79847c46 --- /dev/null +++ b/device.c @@ -0,0 +1,47 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include "name.h" +#include "device.h" +#include "strv.h" + +static void device_dump(Name *n, FILE *f, const char *prefix) { + + static const char* const state_table[_DEVICE_STATE_MAX] = { + [DEVICE_DEAD] = "dead", + [DEVICE_AVAILABLE] = "available" + }; + + Device *s = DEVICE(n); + + assert(s); + + fprintf(f, + "%sDevice State: %s\n", + prefix, state_table[s->state]); +} + +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, + .dump = device_dump, + + .start = NULL, + .stop = NULL, + .reload = NULL, + + .active_state = device_active_state, + + .free_hook = device_free_hook +}; diff --git a/device.h b/device.h new file mode 100644 index 00000000..e2597f71 --- /dev/null +++ b/device.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foodevicehfoo +#define foodevicehfoo + +typedef struct Device Device; + +#include "name.h" + +/* We simply watch devices, we cannot plug/unplug them. That + * simplifies the state engine greatly */ +typedef enum DeviceState { + DEVICE_DEAD, + DEVICE_AVAILABLE, + _DEVICE_STATE_MAX +} DeviceState; + +struct Device { + Meta meta; + + DeviceState state; + + /* A single device can be created by multiple sysfs objects */ + char **sysfs; +}; + +extern const NameVTable device_vtable; + +#endif diff --git a/execute.c b/execute.c new file mode 100644 index 00000000..9bb8351f --- /dev/null +++ b/execute.c @@ -0,0 +1,68 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include + +#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); + + return 0; +} + +void exec_context_free(ExecContext *c) { + unsigned l; + + assert(c); + + strv_free(c->environment); + + for (l = 0; l < ELEMENTSOF(c->rlimit); l++) + free(c->rlimit[l]); + + free(c->chdir); + free(c->user); + free(c->group); + free(c->supplementary_groups); +} + +void exec_command_free_list(ExecCommand *c) { + ExecCommand *i; + + while ((i = c)) { + LIST_REMOVE(ExecCommand, c, i); + + free(i->path); + free(i->argv); + free(i); + } +} + +void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { + assert(c); + assert(f); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%sUmask: %04o\n" + "%sDumpable: %s\n" + "%sDirectory: %s\n", + prefix, c->umask, + prefix, yes_no(c->dumpable), + prefix, c->chdir ? c->chdir : "/"); +} + +void exec_context_defaults(ExecContext *c) { + assert(c); + + c->umask = 0002; + cap_clear(c->capabilities); + c->dumpable = true; +} diff --git a/execute.h b/execute.h new file mode 100644 index 00000000..fb952d1e --- /dev/null +++ b/execute.h @@ -0,0 +1,59 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef fooexecutehfoo +#define fooexecutehfoo + +typedef struct ExecStatus ExecStatus; +typedef struct ExecCommand ExecCommand; +typedef struct ExecContext ExecContext; + +#include +#include +#include +#include +#include + +#include "list.h" + +struct ExecStatus { + pid_t pid; + time_t timestamp; + int status; /* as in wait() */ +}; + +struct ExecCommand { + char *path; + char **argv; + ExecStatus last_exec_status; + LIST_FIELDS(ExecCommand); +}; + +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; + + /* since resolving these names might might involve socket + * connections and we don't want to deadlock ourselves these + * names are resolved on execution only. */ + char *user; + char *group; + char **supplementary_groups; +}; + +int exec_spawn(const ExecCommand *command, const ExecContext *context, pid_t *ret); + +void exec_context_free(ExecContext *c); +void exec_command_free_list(ExecCommand *c); + +void exec_context_dump(ExecContext *c, FILE* f, const char *prefix); + +void exec_context_defaults(ExecContext *c); + +#endif diff --git a/job.c b/job.c index 4688164e..0ae1a765 100644 --- a/job.c +++ b/job.c @@ -30,14 +30,15 @@ void job_free(Job *j) { assert(j); /* Detach from next 'bigger' objects */ - if (j->linked) { if (j->name->meta.job == j) j->name->meta.job = NULL; hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id)); + j->linked = false; } + /* Detach from next 'smaller' objects */ manager_transaction_unlink_job(j->manager, j); free(j); @@ -132,15 +133,14 @@ const char* job_type_to_string(JobType t) { static const char* const job_type_table[_JOB_TYPE_MAX] = { [JOB_START] = "start", + [JOB_VERIFY_ACTIVE] = "verify-active", [JOB_STOP] = "stop", - [JOB_VERIFY_STARTED] = "verify-started", [JOB_RELOAD] = "reload", [JOB_RELOAD_OR_START] = "reload-or-start", [JOB_RESTART] = "restart", [JOB_TRY_RESTART] = "try-restart", }; - if (t < 0 || t >= _JOB_TYPE_MAX) return "n/a"; @@ -151,8 +151,7 @@ void job_dump(Job *j, FILE*f, const char *prefix) { static const char* const job_state_table[_JOB_STATE_MAX] = { [JOB_WAITING] = "waiting", - [JOB_RUNNING] = "running", - [JOB_DONE] = "done" + [JOB_RUNNING] = "running" }; assert(j); @@ -161,10 +160,12 @@ void job_dump(Job *j, FILE*f, const char *prefix) { fprintf(f, "%sJob %u:\n" "%s\tAction: %s → %s\n" - "%s\tState: %s\n", + "%s\tState: %s\n" + "%s\tForced: %s\n", prefix, j->id, prefix, name_id(j->name), job_type_to_string(j->type), - prefix, job_state_table[j->state]); + prefix, job_state_table[j->state], + prefix, yes_no(j->forced)); } bool job_is_anchor(Job *j) { @@ -198,24 +199,24 @@ int job_type_merge(JobType *a, JobType b) { /* Also, if a merged with b cannot be merged with c, then * either a or b cannot be merged with c either */ - if (types_match(*a, b, JOB_START, JOB_VERIFY_STARTED)) + if (types_match(*a, b, JOB_START, JOB_VERIFY_ACTIVE)) *a = JOB_START; else if (types_match(*a, b, JOB_START, JOB_RELOAD) || types_match(*a, b, JOB_START, JOB_RELOAD_OR_START) || - types_match(*a, b, JOB_VERIFY_STARTED, JOB_RELOAD_OR_START) || + types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RELOAD_OR_START) || types_match(*a, b, JOB_RELOAD, JOB_RELOAD_OR_START)) *a = JOB_RELOAD_OR_START; else if (types_match(*a, b, JOB_START, JOB_RESTART) || types_match(*a, b, JOB_START, JOB_TRY_RESTART) || - types_match(*a, b, JOB_VERIFY_STARTED, JOB_RESTART) || + types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RESTART) || types_match(*a, b, JOB_RELOAD, JOB_RESTART) || types_match(*a, b, JOB_RELOAD_OR_START, JOB_RESTART) || types_match(*a, b, JOB_RELOAD_OR_START, JOB_TRY_RESTART) || types_match(*a, b, JOB_RESTART, JOB_TRY_RESTART)) *a = JOB_RESTART; - else if (types_match(*a, b, JOB_VERIFY_STARTED, JOB_RELOAD)) + else if (types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_RELOAD)) *a = JOB_RELOAD; - else if (types_match(*a, b, JOB_VERIFY_STARTED, JOB_TRY_RESTART) || + else if (types_match(*a, b, JOB_VERIFY_ACTIVE, JOB_TRY_RESTART) || types_match(*a, b, JOB_RELOAD, JOB_TRY_RESTART)) *a = JOB_TRY_RESTART; else @@ -224,40 +225,43 @@ int job_type_merge(JobType *a, JobType b) { return 0; } -bool job_type_mergeable(JobType a, JobType b) { +bool job_type_is_mergeable(JobType a, JobType b) { return job_type_merge(&a, b) >= 0; } bool job_type_is_superset(JobType a, JobType b) { - /* Checks whether operation a is a "superset" of b */ + /* Checks whether operation a is a "superset" of b in its + * actions */ if (a == b) return true; switch (a) { case JOB_START: - return b == JOB_VERIFY_STARTED; + return b == JOB_VERIFY_ACTIVE; case JOB_RELOAD: - return b == JOB_VERIFY_STARTED; + return + b == JOB_VERIFY_ACTIVE; case JOB_RELOAD_OR_START: return b == JOB_RELOAD || - b == JOB_START; + b == JOB_START || + b == JOB_VERIFY_ACTIVE; case JOB_RESTART: return b == JOB_START || - b == JOB_VERIFY_STARTED || + b == JOB_VERIFY_ACTIVE || b == JOB_RELOAD || b == JOB_RELOAD_OR_START || b == JOB_TRY_RESTART; case JOB_TRY_RESTART: return - b == JOB_VERIFY_STARTED || + b == JOB_VERIFY_ACTIVE || b == JOB_RELOAD; default: return false; @@ -269,30 +273,223 @@ bool job_type_is_conflicting(JobType a, JobType b) { assert(a >= 0 && a < _JOB_TYPE_MAX); assert(b >= 0 && b < _JOB_TYPE_MAX); - return - (a == JOB_STOP && b != JOB_STOP) || - (b == JOB_STOP && a != JOB_STOP); + return (a == JOB_STOP) != (b == JOB_STOP); } -bool job_type_applicable(JobType j, NameType n) { +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: - case JOB_STOP: - case JOB_VERIFY_STARTED: return true; - case JOB_RELOAD: - case JOB_RELOAD_OR_START: - return n == NAME_SERVICE || n == NAME_TIMER || n == NAME_MOUNT; - + case JOB_STOP: case JOB_RESTART: case JOB_TRY_RESTART: - return n == NAME_SERVICE || n == NAME_TIMER || n == NAME_SOCKET || NAME_MOUNT || NAME_SNAPSHOT; + 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; + Name *other; + + assert(j); + assert(j->linked); + + /* Checks whether there is any job running for the names this + * job needs to be running after (in the case of a 'positive' + * job type) or before (in the case of a 'negative' job type + * . */ + + if (j->type == JOB_START || + j->type == JOB_VERIFY_ACTIVE || + j->type == JOB_RELOAD || + j->type == JOB_RELOAD_OR_START) { + + /* Immediate result is that the job is or might be + * started. In this case lets wait for the + * dependencies, regardless whether they are + * starting or stopping something. */ + + SET_FOREACH(other, j->name->meta.dependencies[NAME_AFTER], state) + if (other->meta.job) + return false; + } + + /* Also, if something else is being stopped and we should + * change state after it, then lets wait. */ + + SET_FOREACH(other, j->name->meta.dependencies[NAME_BEFORE], state) + if (other->meta.job && + (other->meta.job->type == JOB_STOP || + other->meta.job->type == JOB_RESTART || + other->meta.job->type == JOB_TRY_RESTART)) + return false; + + /* This means that for a service a and a service b where b + * shall be started after a: + * + * start a + start b → 1st step start a, 2nd step start b + * start a + stop b → 1st step stop b, 2nd step start a + * stop a + start b → 1st step stop a, 2nd step start b + * stop a + stop b → 1st step stop b, 2nd step stop a + * + * This has the side effect that restarts are properly + * synchronized too. */ + + return true; +} + +int job_run_and_invalidate(Job *j) { + int r; + assert(j); + + if (!job_is_runnable(j)) + return -EAGAIN; + + if (j->state != JOB_WAITING) + return 0; + + switch (j->type) { + + case JOB_START: + r = name_start(j->name); + if (r == -EBADR) + r = 0; + break; + + case JOB_VERIFY_ACTIVE: { + NameActiveState t = name_active_state(j->name); + if (NAME_IS_ACTIVE_OR_RELOADING(t)) + r = -EALREADY; + else if (t == NAME_ACTIVATING) + r = -EAGAIN; + else + r = -ENOEXEC; + break; + } + + case JOB_STOP: + r = name_stop(j->name); + break; + + case JOB_RELOAD: + r = name_reload(j->name); + break; + + case JOB_RELOAD_OR_START: + if (name_active_state(j->name) == NAME_ACTIVE) + r = name_reload(j->name); + else + r = name_start(j->name); + break; + + case JOB_RESTART: { + NameActiveState t = name_active_state(j->name); + if (t == NAME_INACTIVE || t == NAME_ACTIVATING) { + j->type = JOB_START; + r = name_start(j->name); + } else + r = name_stop(j->name); + break; + } + + case JOB_TRY_RESTART: { + NameActiveState t = name_active_state(j->name); + if (t == NAME_INACTIVE || t == NAME_DEACTIVATING) + r = -ENOEXEC; + else if (t == NAME_ACTIVATING) { + j->type = JOB_START; + r = name_start(j->name); + } else + r = name_stop(j->name); + break; + } + + default: + ; + } + + if (r >= 0) + j->state = JOB_RUNNING; + else if (r == -EALREADY) + r = job_finish_and_invalidate(j, true); + else if (r != -EAGAIN) + r = job_finish_and_invalidate(j, false); + + return r; +} + +int job_finish_and_invalidate(Job *j, bool success) { + Name *n; + void *state; + Name *other; + NameType t; + + assert(j); + + 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); + } + + n = j->name; + t = j->type; + job_free(j); + + /* Fail depending jobs on failure */ + if (!success) { + + if (t == JOB_START || + t == JOB_VERIFY_ACTIVE || + t == JOB_RELOAD_OR_START) { + + SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], state) + 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) + if (other->meta.job && + !other->meta.job->forced && + (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); + + } else if (t == JOB_STOP) { + + SET_FOREACH(other, n->meta.dependencies[NAME_CONFLICTS], state) + if (other->meta.job && + (t == JOB_START || + t == JOB_VERIFY_ACTIVE || + t == JOB_RELOAD_OR_START)) + job_finish_and_invalidate(other->meta.job, false); + } + } + + /* Try to start the next jobs that can be started */ + SET_FOREACH(other, n->meta.dependencies[NAME_AFTER], state) + if (other->meta.job) + job_run_and_invalidate(other->meta.job); + SET_FOREACH(other, n->meta.dependencies[NAME_BEFORE], state) + if (other->meta.job) + job_run_and_invalidate(other->meta.job); + + return 0; +} diff --git a/job.h b/job.h index d839db5a..fbfaa16b 100644 --- a/job.h +++ b/job.h @@ -9,8 +9,8 @@ typedef struct Job Job; typedef struct JobDependency JobDependency; typedef enum JobType JobType; -typedef enum JobMode JobMode; typedef enum JobState JobState; +typedef enum JobMode JobMode; #include "manager.h" #include "name.h" @@ -18,13 +18,20 @@ typedef enum JobState JobState; #include "list.h" enum JobType { - JOB_START, + JOB_START, /* if a name does not support being started, we'll just wait until it becomes active */ + JOB_VERIFY_ACTIVE, + JOB_STOP, - JOB_VERIFY_STARTED, - JOB_RELOAD, /* reload if running */ - JOB_RELOAD_OR_START, /* reload if running, start if not running */ - JOB_RESTART, /* stop if running, then start unconditionally */ - JOB_TRY_RESTART, /* stop and start if running */ + + JOB_RELOAD, /* if running reload */ + JOB_RELOAD_OR_START, /* if running reload, if not running start */ + + /* Note that restarts are first treated like JOB_STOP, but + * then instead of finishing are patched to become + * JOB_START. */ + JOB_RESTART, /* if running stop, then start unconditionally */ + JOB_TRY_RESTART, /* if running stop and then start */ + _JOB_TYPE_MAX, _JOB_TYPE_INVALID = -1 }; @@ -32,7 +39,6 @@ enum JobType { enum JobState { JOB_WAITING, JOB_RUNNING, - JOB_DONE, _JOB_STATE_MAX }; @@ -66,6 +72,7 @@ struct Job { bool linked:1; bool matters_to_anchor:1; + bool forced:1; /* These fields are used only while building a transaction */ Job *transaction_next, *transaction_prev; @@ -73,7 +80,7 @@ struct Job { JobDependency *subject_list; JobDependency *object_list; - /* used for graph algs as a "I have been here" marker */ + /* Used for graph algs as a "I have been here" marker */ Job* marker; unsigned generation; }; @@ -92,8 +99,12 @@ int job_merge(Job *j, Job *other); const char* job_type_to_string(JobType t); int job_type_merge(JobType *a, JobType b); -bool job_type_mergeable(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); + +int job_run_and_invalidate(Job *j); +int job_finish_and_invalidate(Job *j, bool success); #endif diff --git a/load-dropin.c b/load-dropin.c new file mode 100644 index 00000000..7105cb86 --- /dev/null +++ b/load-dropin.c @@ -0,0 +1,11 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include "load-dropin.h" + +int name_load_dropin(Name *n) { + assert(n); + + /* Load dependencies from supplementary drop-in directories */ + + return 0; +} diff --git a/load-dropin.h b/load-dropin.h new file mode 100644 index 00000000..c4971a51 --- /dev/null +++ b/load-dropin.h @@ -0,0 +1,12 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef fooloaddropinhfoo +#define fooloaddropinhfoo + +#include "name.h" + +/* Read service data supplementary drop-in directories */ + +int name_load_dropin(Name *n); + +#endif diff --git a/load-fragment.c b/load-fragment.c index ed046d03..a2c36368 100644 --- a/load-fragment.c +++ b/load-fragment.c @@ -89,7 +89,7 @@ static int config_parse_names( if (other != name) { - if (other->meta.state != NAME_STUB) { + if (other->meta.load_state != NAME_STUB) { free(t); return -EEXIST; } @@ -176,7 +176,7 @@ static int config_parse_type( int name_load_fragment(Name *n) { - const char *const section_table[_NAME_TYPE_MAX] = { + static const char* const section_table[_NAME_TYPE_MAX] = { [NAME_SERVICE] = "Service", [NAME_TIMER] = "Timer", [NAME_SOCKET] = "Socket", @@ -211,7 +211,7 @@ int name_load_fragment(Name *n) { const char *sections[3]; assert(n); - assert(n->meta.state == NAME_STUB); + assert(n->meta.load_state == NAME_STUB); sections[0] = "Meta"; sections[1] = section_table[n->meta.type]; diff --git a/load-fstab.c b/load-fstab.c new file mode 100644 index 00000000..cfefcb66 --- /dev/null +++ b/load-fstab.c @@ -0,0 +1,11 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include "load-fstab.h" + +int name_load_fstab(Name *n) { + assert(n); + + /* Load dependencies from /etc/fstab */ + + return 0; +} diff --git a/load-fstab.h b/load-fstab.h new file mode 100644 index 00000000..e7056502 --- /dev/null +++ b/load-fstab.h @@ -0,0 +1,12 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef fooloadfstabhfoo +#define fooloadfstabhfoo + +#include "name.h" + +/* Read service data from /etc/fstab */ + +int name_load_fstab(Name *n); + +#endif diff --git a/manager.c b/manager.c index 14ae6525..701e6451 100644 --- a/manager.c +++ b/manager.c @@ -127,6 +127,7 @@ static void transaction_merge_and_delete_job(Manager *m, Job *j, Job *other, Job j->type = t; j->state = JOB_WAITING; + j->forced = j->forced || other->forced; j->matters_to_anchor = j->matters_to_anchor || other->matters_to_anchor; @@ -168,7 +169,7 @@ static void transaction_merge_and_delete_job(Manager *m, Job *j, Job *other, Job transaction_delete_job(m, other); } -static int delete_one_unmergable_job(Manager *m, Job *j) { +static int delete_one_unmergeable_job(Manager *m, Job *j) { Job *k; assert(j); @@ -185,7 +186,7 @@ static int delete_one_unmergable_job(Manager *m, Job *j) { Job *d; /* Is this one mergeable? Then skip it */ - if (job_type_mergeable(j->type, k->type)) + if (job_type_is_mergeable(j->type, k->type)) continue; /* Ok, we found two that conflict, let's see if we can @@ -198,7 +199,7 @@ static int delete_one_unmergable_job(Manager *m, Job *j) { return -ENOEXEC; /* Ok, we can drop one, so let's do so. */ - log_debug("Try to fix job merging by deleting job %s", name_id(d->name)); + log_debug("Try to fix job merging by deleting job %s/%s", name_id(d->name), job_type_to_string(d->type)); transaction_delete_job(m, d); return 0; } @@ -228,7 +229,7 @@ static int transaction_merge_jobs(Manager *m) { * action. Let's see if we can get rid of one * of them */ - if ((r = delete_one_unmergable_job(m, j)) >= 0) + if ((r = delete_one_unmergeable_job(m, j)) >= 0) /* Ok, we managed to drop one, now * let's ask our callers to call us * again after garbage collecting */ @@ -248,7 +249,7 @@ static int transaction_merge_jobs(Manager *m) { for (k = j->transaction_next; k; k = k->transaction_next) assert_se(job_type_merge(&t, k->type) == 0); - /* If an active job is mergable, merge it too */ + /* If an active job is mergeable, merge it too */ if (j->name->meta.job) job_type_merge(&t, j->name->meta.job->type); /* Might fail. Which is OK */ @@ -310,7 +311,7 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned !name_matters_to_anchor(k->name, k)) { /* Ok, we can drop this one, so let's * do so. */ - log_debug("Breaking order cycle by deleting job %s", name_id(k->name)); + log_debug("Breaking order cycle by deleting job %s/%s", name_id(k->name), job_type_to_string(k->type)); transaction_delete_name(m, k->name); return -EAGAIN; } @@ -385,8 +386,7 @@ static void transaction_collect_garbage(Manager *m) { if (j->object_list) continue; - log_debug("Garbage collecting job %s", name_id(j->name)); - + log_debug("Garbage collecting job %s/%s", name_id(j->name), job_type_to_string(j->type)); transaction_delete_job(m, j); again = true; break; @@ -442,11 +442,12 @@ static void transaction_minimize_impact(Manager *m) { /* Would this stop a running service? * Would this change an existing job? * If so, let's drop this entry */ - if ((j->type != JOB_STOP || name_is_dead(j->name)) && + if ((j->type != JOB_STOP || NAME_IS_INACTIVE_OR_DEACTIVATING(name_active_state(j->name))) && (!j->name->meta.job || job_type_is_conflicting(j->type, j->name->meta.job->state))) continue; /* Ok, let's get rid of this */ + log_debug("Deleting %s/%s to minimize impact", name_id(j->name), job_type_to_string(j->type)); transaction_delete_job(m, j); again = true; break; @@ -551,7 +552,7 @@ static int transaction_activate(Manager *m, JobMode mode) { } for (;;) { - /* Fifth step: let's drop unmergable entries if + /* Fifth step: let's drop unmergeable entries if * necessary and possible, merge entries we can * merge */ if ((r = transaction_merge_jobs(m)) >= 0) @@ -565,7 +566,7 @@ static int transaction_activate(Manager *m, JobMode mode) { transaction_collect_garbage(m); /* Let's see if the resulting transaction still has - * unmergable entries ... */ + * unmergeable entries ... */ } /* Seventh step: check whether we can actually apply this */ @@ -587,7 +588,7 @@ rollback: return r; } -static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool *is_new) { +static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool force, bool *is_new) { Job *j, *f; int r; @@ -628,6 +629,7 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Name *name, bool * j->generation = 0; j->marker = NULL; j->matters_to_anchor = false; + j->forced = force; if (is_new) *is_new = true; @@ -660,7 +662,9 @@ void manager_transaction_unlink_job(Manager *m, Job *j) { job_dependency_free(j->object_list); if (other) { - log_debug("Deleting job %s as dependency of job %s", name_id(other->name), name_id(j->name)); + log_debug("Deleting job %s/%s as dependency of job %s/%s", + name_id(other->name), job_type_to_string(other->type), + name_id(j->name), job_type_to_string(j->type)); transaction_delete_job(m, other); } } @@ -677,14 +681,14 @@ static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name * assert(type < _JOB_TYPE_MAX); assert(name); - if (name->meta.state != NAME_LOADED) + if (name->meta.load_state != NAME_LOADED) return -EINVAL; - if (!job_type_applicable(type, name->meta.type)) + if (!job_type_is_applicable(type, name->meta.type)) return -EBADR; /* First add the job. */ - if (!(ret = transaction_add_one_job(m, type, name, &is_new))) + if (!(ret = transaction_add_one_job(m, type, name, force, &is_new))) return -ENOMEM; /* Then, add a link to the job. */ @@ -704,10 +708,10 @@ static int transaction_add_job_and_dependencies(Manager *m, JobType type, Name * if ((r = transaction_add_job_and_dependencies(m, JOB_START, dep, ret, false, force, NULL)) != -EBADR) goto fail; SET_FOREACH(dep, ret->name->meta.dependencies[NAME_REQUISITE], state) - if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_STARTED, dep, ret, true, force, NULL)) != -EBADR) + if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, true, force, NULL)) != -EBADR) goto fail; SET_FOREACH(dep, ret->name->meta.dependencies[NAME_SOFT_REQUISITE], state) - if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_STARTED, dep, ret, !force, force, NULL)) != -EBADR) + if ((r = transaction_add_job_and_dependencies(m, JOB_VERIFY_ACTIVE, dep, ret, !force, force, NULL)) != -EBADR) goto fail; SET_FOREACH(dep, ret->name->meta.dependencies[NAME_CONFLICTS], state) if ((r = transaction_add_job_and_dependencies(m, JOB_STOP, dep, ret, true, force, NULL)) != -EBADR) diff --git a/manager.h b/manager.h index 287e532b..e669f9f9 100644 --- a/manager.h +++ b/manager.h @@ -34,6 +34,8 @@ struct Manager { JobDependency *transaction_anchor; bool dispatching_load_queue:1; + + Hashmap *pids; /* pid => Name object n:1 */ }; Manager* manager_new(void); diff --git a/milestone.c b/milestone.c new file mode 100644 index 00000000..ad8080ae --- /dev/null +++ b/milestone.c @@ -0,0 +1,32 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include "name.h" +#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) { + Milestone *m = MILESTONE(n); + + assert(m); + + /* Nothing here for now */ +} + +const NameVTable milestone_vtable = { + .suffix = ".milestone", + + .load = name_load_fragment, + .dump = NULL, + + .start = NULL, + .stop = NULL, + .reload = NULL, + + .active_state = milestone_active_state, + + .free_hook = milestone_free_hook +}; diff --git a/milestone.h b/milestone.h new file mode 100644 index 00000000..332fbe44 --- /dev/null +++ b/milestone.h @@ -0,0 +1,23 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foomilestonehfoo +#define foomilestonehfoo + +typedef struct Milestone Milestone; + +#include "name.h" + +typedef enum MilestoneState { + MILESTONE_DEAD, + MILESTONE_ACTIVE +} MilestoneState; + +struct Milestone { + Meta meta; + + MilestoneState state; +}; + +extern const NameVTable milestone_vtable; + +#endif diff --git a/mount.c b/mount.c new file mode 100644 index 00000000..fa92e5b7 --- /dev/null +++ b/mount.c @@ -0,0 +1,86 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include + +#include "name.h" +#include "mount.h" +#include "load-fragment.h" +#include "load-fstab.h" +#include "load-dropin.h" + +static int mount_load(Name *n) { + int r; + Mount *m = MOUNT(n); + + assert(m); + + /* Load a .mount file */ + if ((r = name_load_fragment(n)) < 0 && errno != -ENOENT) + return r; + + /* Load entry from /etc/fstab */ + if ((r = name_load_fstab(n)) < 0) + return r; + + /* Load drop-in directory data */ + if ((r = name_load_dropin(n)) < 0) + return r; + + return r; +} + +static void mount_dump(Name *n, FILE *f, const char *prefix) { + + static const char* const state_table[_MOUNT_STATE_MAX] = { + [MOUNT_DEAD] = "dead", + [MOUNT_MOUNTING] = "mounting", + [MOUNT_MOUNTED] = "mounted", + [MOUNT_UNMOUNTING] = "unmounting", + [MOUNT_MAINTAINANCE] = "maintainance" + }; + + Mount *s = MOUNT(n); + + assert(s); + + fprintf(f, + "%sMount State: %s\n" + "%sPath: %s\n", + prefix, state_table[s->state], + prefix, s->path); +} + +static NameActiveState mount_active_state(Name *n) { + + static const NameActiveState table[_MOUNT_STATE_MAX] = { + [MOUNT_DEAD] = NAME_INACTIVE, + [MOUNT_MOUNTING] = NAME_ACTIVATING, + [MOUNT_MOUNTED] = NAME_ACTIVE, + [MOUNT_UNMOUNTING] = NAME_DEACTIVATING, + [MOUNT_MAINTAINANCE] = NAME_INACTIVE, + }; + + return table[MOUNT(n)->state]; +} + +static void mount_free_hook(Name *n) { + Mount *d = MOUNT(n); + + assert(d); + free(d->path); +} + +const NameVTable mount_vtable = { + .suffix = ".mount", + + .load = mount_load, + .dump = mount_dump, + + .start = NULL, + .stop = NULL, + .reload = NULL, + + .active_state = mount_active_state, + + .free_hook = mount_free_hook +}; diff --git a/mount.h b/mount.h new file mode 100644 index 00000000..b437b2e7 --- /dev/null +++ b/mount.h @@ -0,0 +1,28 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foomounthfoo +#define foomounthfoo + +typedef struct Mount Mount; + +#include "name.h" + +typedef enum MountState { + MOUNT_DEAD, + MOUNT_MOUNTING, + MOUNT_MOUNTED, + MOUNT_UNMOUNTING, + MOUNT_MAINTAINANCE, + _MOUNT_STATE_MAX +} MountState; + +struct Mount { + Meta meta; + + MountState state; + char *path; +}; + +extern const NameVTable mount_vtable; + +#endif diff --git a/name.c b/name.c index 08194d05..5dba0105 100644 --- a/name.c +++ b/name.c @@ -9,24 +9,28 @@ #include "macro.h" #include "strv.h" #include "load-fragment.h" +#include "load-dropin.h" + +static const NameVTable * const name_vtable[_NAME_TYPE_MAX] = { + [NAME_SERVICE] = &service_vtable, + [NAME_TIMER] = &timer_vtable, + [NAME_SOCKET] = &socket_vtable, + [NAME_MILESTONE] = &milestone_vtable, + [NAME_DEVICE] = &device_vtable, + [NAME_MOUNT] = &mount_vtable, + [NAME_AUTOMOUNT] = &automount_vtable, + [NAME_SNAPSHOT] = &snapshot_vtable +}; + +#define NAME_VTABLE(n) name_vtable[(n)->meta.type] NameType name_type_from_string(const char *n) { NameType t; - static const char* suffixes[_NAME_TYPE_MAX] = { - [NAME_SERVICE] = ".service", - [NAME_TIMER] = ".timer", - [NAME_SOCKET] = ".socket", - [NAME_MILESTONE] = ".milestone", - [NAME_DEVICE] = ".device", - [NAME_MOUNT] = ".mount", - [NAME_AUTOMOUNT] = ".automount", - [NAME_SNAPSHOT] = ".snapshot", - }; assert(n); for (t = 0; t < _NAME_TYPE_MAX; t++) - if (endswith(n, suffixes[t])) + if (endswith(n, name_vtable[t]->suffix)) return t; return _NAME_TYPE_INVALID; @@ -44,6 +48,9 @@ bool name_is_valid(const char *n) { assert(n); + if (strlen(n) >= NAME_MAX) + return false; + t = name_type_from_string(n); if (t < 0 || t >= _NAME_TYPE_MAX) return false; @@ -74,7 +81,6 @@ Name *name_new(Manager *m) { /* Not much initialization happening here at this time */ n->meta.manager = m; n->meta.type = _NAME_TYPE_INVALID; - n->meta.state = NAME_STUB; /* We don't link the name here, that is left for name_link() */ @@ -130,7 +136,7 @@ int name_link(Name *n) { return r; } - if (n->meta.state == NAME_STUB) + if (n->meta.load_state == NAME_STUB) LIST_PREPEND(Meta, n->meta.manager->load_queue, &n->meta); return 0; @@ -169,7 +175,7 @@ void name_free(Name *name) { SET_FOREACH(t, name->meta.names, state) hashmap_remove_value(name->meta.manager->names, t, name); - if (name->meta.state == NAME_STUB) + if (name->meta.load_state == NAME_STUB) LIST_REMOVE(Meta, name->meta.manager->load_queue, &name->meta); } @@ -180,41 +186,8 @@ void name_free(Name *name) { for (d = 0; d < _NAME_DEPENDENCY_MAX; d++) bidi_set_free(name, name->meta.dependencies[d]); - switch (name->meta.type) { - - case NAME_SOCKET: { - unsigned i; - Socket *s = SOCKET(name); - - for (i = 0; i < s->n_fds; i++) - close_nointr(s->fds[i]); - break; - } - - case NAME_DEVICE: { - Device *d = DEVICE(name); - - free(d->sysfs); - break; - } - - case NAME_MOUNT: { - Mount *m = MOUNT(name); - - free(m->path); - break; - } - - case NAME_AUTOMOUNT: { - Automount *a = AUTOMOUNT(name); - - free(a->path); - break; - } - - default: - ; - } + if (NAME_VTABLE(name)->free_hook) + NAME_VTABLE(name)->free_hook(name); free(name->meta.description); @@ -225,134 +198,15 @@ void name_free(Name *name) { free(name); } -bool name_is_ready(Name *name) { +NameActiveState name_active_state(Name *name) { assert(name); - if (name->meta.state != NAME_LOADED) - return false; - - assert(name->meta.type < _NAME_TYPE_MAX); + if (name->meta.load_state != NAME_LOADED) + return NAME_INACTIVE; - switch (name->meta.type) { - case NAME_SERVICE: { - Service *s = SERVICE(name); - - return - s->state == SERVICE_RUNNING || - s->state == SERVICE_RELOAD_PRE || - s->state == SERVICE_RELOAD || - s->state == SERVICE_RELOAD_POST; - } - - case NAME_TIMER: { - Timer *t = TIMER(name); - - return - t->state == TIMER_WAITING || - t->state == TIMER_RUNNING; - } - - case NAME_SOCKET: { - Socket *s = SOCKET(name); - - return - s->state == SOCKET_LISTENING || - s->state == SOCKET_RUNNING; - } - - case NAME_MILESTONE: - return MILESTONE(name)->state == MILESTONE_ACTIVE; - - case NAME_DEVICE: - return DEVICE(name)->state == DEVICE_AVAILABLE; - - case NAME_MOUNT: - return MOUNT(name)->state == MOUNT_MOUNTED; - - case NAME_AUTOMOUNT: { - Automount *a = AUTOMOUNT(name); - - return - a->state == AUTOMOUNT_WAITING || - a->state == AUTOMOUNT_RUNNING; - } - - case NAME_SNAPSHOT: - return SNAPSHOT(name)->state == SNAPSHOT_ACTIVE; - - - case _NAME_TYPE_MAX: - case _NAME_TYPE_INVALID: - ; - } - - assert_not_reached("Unknown name type."); - return false; + return NAME_VTABLE(name)->active_state(name); } -bool name_is_dead(Name *name) { - assert(name); - - if (name->meta.state != NAME_LOADED) - return true; - assert(name->meta.type < _NAME_TYPE_MAX); - - switch (name->meta.type) { - case NAME_SERVICE: { - Service *s = SERVICE(name); - - return - s->state == SERVICE_DEAD || - s->state == SERVICE_MAINTAINANCE; - } - - case NAME_TIMER: - return TIMER(name)->state == TIMER_DEAD; - - case NAME_SOCKET: { - Socket *s = SOCKET(name); - - return - s->state == SOCKET_DEAD || - s->state == SOCKET_MAINTAINANCE; - } - - case NAME_MILESTONE: - return MILESTONE(name)->state == MILESTONE_DEAD; - - case NAME_DEVICE: - return DEVICE(name)->state == DEVICE_DEAD; - - case NAME_MOUNT: { - Mount *a = MOUNT(name); - - return - a->state == AUTOMOUNT_DEAD || - a->state == AUTOMOUNT_MAINTAINANCE; - } - - case NAME_AUTOMOUNT: { - Automount *a = AUTOMOUNT(name); - - return - a->state == AUTOMOUNT_DEAD || - a->state == AUTOMOUNT_MAINTAINANCE; - } - - case NAME_SNAPSHOT: - return SNAPSHOT(name)->state == SNAPSHOT_DEAD; - - - case _NAME_TYPE_MAX: - case _NAME_TYPE_INVALID: - ; - } - - assert_not_reached("Unknown name type."); - return false; -} - - static int ensure_in_set(Set **s, void *data) { int r; @@ -399,7 +253,7 @@ int name_merge(Name *name, Name *other) { if (name->meta.type != other->meta.type) return -EINVAL; - if (other->meta.state != NAME_STUB) + if (other->meta.load_state != NAME_STUB) return -EINVAL; /* Merge names */ @@ -452,13 +306,11 @@ static int augment(Name *n) { if ((r = ensure_in_set(&other->meta.dependencies[NAME_REQUIRED_BY], n)) < 0) return r; - SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], state) - if ((r = ensure_in_set(&other->meta.dependencies[NAME_WANTED_BY], n)) < 0) - return r; SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUIRES], state) - if ((r = ensure_in_set(&other->meta.dependencies[NAME_WANTED_BY], n)) < 0) + if ((r = ensure_in_set(&other->meta.dependencies[NAME_SOFT_REQUIRED_BY], n)) < 0) return r; - SET_FOREACH(other, n->meta.dependencies[NAME_SOFT_REQUISITE], state) + + SET_FOREACH(other, n->meta.dependencies[NAME_WANTS], state) if ((r = ensure_in_set(&other->meta.dependencies[NAME_WANTED_BY], n)) < 0) return r; @@ -483,26 +335,28 @@ const char* name_id(Name *n) { return set_first(n->meta.names); } +const char *name_description(Name *n) { + assert(n); + + if (n->meta.description) + return n->meta.description; + + return name_id(n); +} + void name_dump(Name *n, FILE *f, const char *prefix) { - static const char* const state_table[_NAME_STATE_MAX] = { + static const char* const load_state_table[_NAME_LOAD_STATE_MAX] = { [NAME_STUB] = "stub", [NAME_LOADED] = "loaded", [NAME_FAILED] = "failed" }; - static const char* const socket_state_table[_SOCKET_STATE_MAX] = { - [SOCKET_DEAD] = "dead", - [SOCKET_BEFORE] = "before", - [SOCKET_START_PRE] = "start-pre", - [SOCKET_START] = "start", - [SOCKET_START_POST] = "start-post", - [SOCKET_LISTENING] = "listening", - [SOCKET_RUNNING] = "running", - [SOCKET_STOP_PRE] = "stop-pre", - [SOCKET_STOP] = "stop", - [SOCKET_STOP_POST] = "stop-post", - [SOCKET_MAINTAINANCE] = "maintainance" + static const char* const active_state_table[_NAME_ACTIVE_STATE_MAX] = { + [NAME_ACTIVE] = "active", + [NAME_INACTIVE] = "inactive", + [NAME_ACTIVATING] = "activating", + [NAME_DEACTIVATING] = "deactivating" }; static const char* const dependency_table[_NAME_DEPENDENCY_MAX] = { @@ -512,7 +366,7 @@ void name_dump(Name *n, FILE *f, const char *prefix) { [NAME_REQUISITE] = "Requisite", [NAME_SOFT_REQUISITE] = "SoftRequisite", [NAME_REQUIRED_BY] = "RequiredBy", - [NAME_WANTED_BY] = "WantedBy", + [NAME_SOFT_REQUIRED_BY] = "SoftRequiredBy", [NAME_CONFLICTS] = "Conflicts", [NAME_BEFORE] = "Before", [NAME_AFTER] = "After", @@ -530,15 +384,15 @@ void name_dump(Name *n, FILE *f, const char *prefix) { fprintf(f, "%sName %s:\n" "%s\tDescription: %s\n" - "%s\tName State: %s\n", + "%s\tName Load State: %s\n" + "%s\tName Active State: %s\n", prefix, name_id(n), - prefix, n->meta.description ? n->meta.description : name_id(n), - prefix, state_table[n->meta.state]); + prefix, name_description(n), + prefix, load_state_table[n->meta.load_state], + prefix, active_state_table[name_active_state(n)]); - fprintf(f, "%s\tNames: ", prefix); SET_FOREACH(t, n->meta.names, state) - fprintf(f, "%s ", t); - fprintf(f, "\n"); + fprintf(f, "%s\tName: %s\n", prefix, t); for (d = 0; d < _NAME_DEPENDENCY_MAX; d++) { void *state; @@ -547,39 +401,12 @@ void name_dump(Name *n, FILE *f, const char *prefix) { if (set_isempty(n->meta.dependencies[d])) continue; - fprintf(f, "%s\t%s: ", prefix, dependency_table[d]); - SET_FOREACH(other, n->meta.dependencies[d], state) - fprintf(f, "%s ", name_id(other)); - - fprintf(f, "\n"); + fprintf(f, "%s\t%s: %s\n", prefix, dependency_table[d], name_id(other)); } - - switch (n->meta.type) { - case NAME_SOCKET: { - int r; - char *s = NULL; - const char *t; - - if ((r = address_print(&n->socket.address, &s)) < 0) - t = strerror(-r); - else - t = s; - - fprintf(f, - "%s\tAddress: %s\n" - "%s\tSocket State: %s\n", - prefix, t, - prefix, socket_state_table[n->socket.state]); - - free(s); - break; - } - - default: - ; - } + if (NAME_VTABLE(n)->dump) + NAME_VTABLE(n)->dump(n, f, prefix); if (n->meta.job) { char *p; @@ -623,91 +450,251 @@ static int verify_type(Name *name) { return 0; } -static int service_load_sysv(Service *s) { - assert(s); +/* Common implementation for multiple backends */ +int name_load_fragment_and_dropin(Name *n) { + int r; - /* Load service data from SysV init scripts, preferably with - * LSB headers ... */ + assert(n); - return -ENOENT; + /* Load a .socket file */ + if ((r = name_load_fragment(n)) < 0) + return r; + + /* Load drop-in directory data */ + if ((r = name_load_dropin(n)) < 0) + return r; + + return 0; } -static int name_load_fstab(Name *n) { - assert(n); - assert(n->meta.type == NAME_MOUNT || n->meta.type == NAME_AUTOMOUNT); +int name_load(Name *name) { + int r; + + assert(name); + + if (name->meta.load_state != NAME_STUB) + return 0; - /* Load mount data from /etc/fstab */ + if ((r = verify_type(name)) < 0) + return r; + + if (NAME_VTABLE(name)->load) + if ((r = NAME_VTABLE(name)->load(name)) < 0) + goto fail; + if ((r = name_sanitize(name)) < 0) + goto fail; + + if ((r = name_link_names(name, false)) < 0) + goto fail; + + name->meta.load_state = NAME_LOADED; return 0; + +fail: + name->meta.load_state = NAME_FAILED; + return r; } -static int snapshot_load(Snapshot *s) { - assert(s); +/* Errors: + * -EBADR: This name type does not support starting. + * -EALREADY: Name is already started. + * -EAGAIN: An operation is already in progress. Retry later. + */ +int name_start(Name *n) { + NameActiveState state; + + assert(n); - /* Load snapshots from disk */ + if (!NAME_VTABLE(n)->start) + return -EBADR; - return 0; + state = name_active_state(n); + if (NAME_IS_ACTIVE_OR_RELOADING(state)) + return -EALREADY; + + if (state == NAME_ACTIVATING) + return 0; + + return NAME_VTABLE(n)->start(n); } -static int name_load_dropin(Name *n) { +bool name_type_can_start(NameType t) { + assert(t >= 0 && t < _NAME_TYPE_MAX); + + return !!name_vtable[t]->start; +} + +/* Errors: + * -EBADR: This name type does not support stopping. + * -EALREADY: Name is already stopped. + * -EAGAIN: An operation is already in progress. Retry later. + */ +int name_stop(Name *n) { + NameActiveState state; + assert(n); - /* Load dependencies from drop-in directories */ + if (!NAME_VTABLE(n)->stop) + return -EBADR; - return 0; + state = name_active_state(n); + if (state == NAME_INACTIVE) + return -EALREADY; + + if (state == NAME_DEACTIVATING) + return 0; + + return NAME_VTABLE(n)->stop(n); } -int name_load(Name *name) { - int r; +/* Errors: + * -EBADR: This name type does not support reloading. + * -ENOEXEC: Name is not started. + * -EAGAIN: An operation is already in progress. Retry later. + */ +int name_reload(Name *n) { + NameActiveState state; - assert(name); + assert(n); + + if (!NAME_VTABLE(n)->reload) + return -EBADR; + + state = name_active_state(n); + if (name_active_state(n) == NAME_ACTIVE_RELOADING) + return -EALREADY; + + if (name_active_state(n) != NAME_ACTIVE) + return -ENOEXEC; + + return NAME_VTABLE(n)->reload(n); +} - if (name->meta.state != NAME_STUB) +bool name_type_can_reload(NameType t) { + assert(t >= 0 && t < _NAME_TYPE_MAX); + return !!name_vtable[t]->reload; +} + +static void retroactively_start_dependencies(Name *n) { + void *state; + Name *other; + + assert(n); + assert(NAME_IS_ACTIVE_OR_ACTIVATING(name_active_state(n))); + + SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRES], state) + 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) + 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) + 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) + 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) + 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; + Name *other; + + assert(n); + assert(NAME_IS_INACTIVE_OR_DEACTIVATING(name_active_state(n))); + + SET_FOREACH(other, n->meta.dependencies[NAME_REQUIRED_BY], state) + if (!NAME_IS_INACTIVE_OR_DEACTIVATING(name_active_state(other))) + manager_add_job(n->meta.manager, JOB_STOP, other, JOB_REPLACE, true, NULL); +} + +int name_notify(Name *n, NameActiveState os, NameActiveState ns) { + assert(n); + assert(os < _NAME_ACTIVE_STATE_MAX); + assert(ns < _NAME_ACTIVE_STATE_MAX); + assert(!(os == NAME_ACTIVE && ns == NAME_ACTIVATING)); + assert(!(os == NAME_INACTIVE && ns == NAME_DEACTIVATING)); + + if (os == ns) return 0; - if ((r = verify_type(name)) < 0) - return r; + if (n->meta.job) { - if (name->meta.type == NAME_SERVICE) { + if (n->meta.job->state == JOB_WAITING) - /* Load a .service file */ - if ((r = name_load_fragment(name)) == 0) - goto finish; + /* 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); - /* Load a classic init script */ - if (r == -ENOENT) - if ((r = service_load_sysv(SERVICE(name))) == 0) - goto finish; + else { + assert(n->meta.job->state == JOB_RUNNING); - } else if (name->meta.type == NAME_MOUNT || - name->meta.type == NAME_AUTOMOUNT) { + /* Let's check of this state change + * constitutes a finished job, or maybe + * cotradicts a running job and hence needs to + * invalidate jobs. */ - if ((r = name_load_fstab(name)) == 0) - goto finish; + switch (n->meta.job->type) { - } else if (name->meta.type == NAME_SNAPSHOT) { + case JOB_START: + case JOB_VERIFY_ACTIVE: - if ((r = snapshot_load(SNAPSHOT(name))) == 0) - goto finish; + if (NAME_IS_ACTIVE_OR_RELOADING(ns)) + return job_finish_and_invalidate(n->meta.job, true); + else if (ns == NAME_ACTIVATING) + return 0; + else + job_finish_and_invalidate(n->meta.job, false); - } else { - if ((r = name_load_fragment(name)) == 0) - goto finish; - } + break; - name->meta.state = NAME_FAILED; - return r; + case JOB_RELOAD: + case JOB_RELOAD_OR_START: -finish: - if ((r = name_load_dropin(name)) < 0) - return r; + if (ns == NAME_ACTIVE) + return job_finish_and_invalidate(n->meta.job, true); + else if (ns == NAME_ACTIVATING || ns == NAME_ACTIVE_RELOADING) + return 0; + else + job_finish_and_invalidate(n->meta.job, false); - if ((r = name_sanitize(name)) < 0) - return r; + break; - if ((r = name_link_names(name, false)) < 0) - return r; + case JOB_STOP: + case JOB_RESTART: + case JOB_TRY_RESTART: + + if (ns == NAME_INACTIVE) + return job_finish_and_invalidate(n->meta.job, true); + else if (ns == NAME_DEACTIVATING) + return 0; + else + job_finish_and_invalidate(n->meta.job, false); + + break; + + default: + assert_not_reached("Job type unknown"); + } + } + } + + /* If this state change happened without being requested by a + * job, then let's retroactively start or stop dependencies */ + + if (NAME_IS_INACTIVE_OR_DEACTIVATING(os) && NAME_IS_ACTIVE_OR_ACTIVATING(ns)) + retroactively_start_dependencies(n); + else if (NAME_IS_ACTIVE_OR_ACTIVATING(os) && NAME_IS_INACTIVE_OR_DEACTIVATING(ns)) + retroactively_stop_dependencies(n); - name->meta.state = NAME_LOADED; return 0; } diff --git a/name.h b/name.h index 869e71bb..33baea1b 100644 --- a/name.h +++ b/name.h @@ -8,14 +8,11 @@ typedef union Name Name; typedef struct Meta Meta; -typedef struct Service Service; -typedef struct Timer Timer; -typedef struct Socket Socket; -typedef struct Milestone Milestone; -typedef struct Device Device; -typedef struct Mount Mount; -typedef struct Automount Automount; -typedef struct Snapshot Snapshot; +typedef struct NameVTable NameVTable; +typedef enum NameType NameType; +typedef enum NameLoadState NameLoadState; +typedef enum NameActiveState NameActiveState; +typedef enum NameDependency NameDependency; #include "job.h" #include "manager.h" @@ -23,8 +20,11 @@ typedef struct Snapshot Snapshot; #include "util.h" #include "list.h" #include "socket-util.h" +#include "execute.h" -typedef enum NameType { +#define NAME_MAX 32 + +enum NameType { NAME_SERVICE = 0, NAME_TIMER, NAME_SOCKET, @@ -35,38 +35,60 @@ typedef enum NameType { NAME_SNAPSHOT, _NAME_TYPE_MAX, _NAME_TYPE_INVALID = -1, -} NameType; +}; -typedef enum NameState { +enum NameLoadState { NAME_STUB, NAME_LOADED, NAME_FAILED, - _NAME_STATE_MAX -} NameState; + _NAME_LOAD_STATE_MAX +}; + +enum NameActiveState { + NAME_ACTIVE, + NAME_ACTIVE_RELOADING, + NAME_INACTIVE, + NAME_ACTIVATING, + NAME_DEACTIVATING, + _NAME_ACTIVE_STATE_MAX +}; + +static inline bool NAME_IS_ACTIVE_OR_RELOADING(NameActiveState t) { + return t == NAME_ACTIVE || t == NAME_ACTIVE_RELOADING; +} + +static inline bool NAME_IS_ACTIVE_OR_ACTIVATING(NameActiveState t) { + return t == NAME_ACTIVE || t == NAME_ACTIVATING || t == NAME_ACTIVE_RELOADING; +} + +static inline bool NAME_IS_INACTIVE_OR_DEACTIVATING(NameActiveState t) { + return t == NAME_INACTIVE || t == NAME_DEACTIVATING; +} -typedef enum NameDependency { +enum NameDependency { /* Positive dependencies */ NAME_REQUIRES, NAME_SOFT_REQUIRES, NAME_WANTS, NAME_REQUISITE, NAME_SOFT_REQUISITE, - NAME_REQUIRED_BY, /* inverse of 'requires' and 'requisite' is 'required_by' */ - NAME_WANTED_BY, /* inverse of 'wants', 'soft_requires' and 'soft_requisite' is 'wanted_by' */ + NAME_REQUIRED_BY, /* inverse of 'requires' and 'requisite' is 'required_by' */ + NAME_SOFT_REQUIRED_BY, /* inverse of 'soft_requires' and 'soft_requisite' is 'soft_required_by' */ + NAME_WANTED_BY, /* inverse of 'wants' */ /* Negative dependencies */ - NAME_CONFLICTS, /* inverse of 'conflicts' is 'conflicts' */ + NAME_CONFLICTS, /* inverse of 'conflicts' is 'conflicts' */ /* Order */ - NAME_BEFORE, /* inverse of before is after and vice versa */ + NAME_BEFORE, /* inverse of before is after and vice versa */ NAME_AFTER, _NAME_DEPENDENCY_MAX -} NameDependency; +}; struct Meta { Manager *manager; NameType type; - NameState state; + NameLoadState load_state; Set *names; Set *dependencies[_NAME_DEPENDENCY_MAX]; @@ -83,164 +105,14 @@ struct Meta { LIST_FIELDS(Meta); }; -typedef enum ServiceState { - SERVICE_DEAD, - SERVICE_BEFORE, - SERVICE_START_PRE, - 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_POST, - SERVICE_HOLDOFF, - SERVICE_MAINTAINANCE -} ServiceState; - -typedef enum ServiceMode { - SERVICE_ONCE, - SERVICE_RESTART -} ServiceMode; - -struct Service { - Meta meta; - - ServiceState state; - ServiceMode mode; -}; - -typedef enum TimerState { - TIMER_DEAD, - TIMER_BEFORE, - TIMER_START_PRE, - TIMER_START, - TIMER_START_POST, - TIMER_WAITING, - TIMER_RUNNING, - TIMER_STOP_PRE, - TIMER_STOP, - TIMER_STOP_POST -} TimerState; - -struct Timer { - Meta meta; - - TimerState state; - Service *subject; - - clockid_t clock_id; - usec_t next_elapse; -}; - -typedef enum SocketState { - SOCKET_DEAD, - SOCKET_BEFORE, - SOCKET_START_PRE, - SOCKET_START, - SOCKET_START_POST, - SOCKET_LISTENING, - SOCKET_RUNNING, - SOCKET_STOP_PRE, - SOCKET_STOP, - SOCKET_STOP_POST, - SOCKET_MAINTAINANCE, - _SOCKET_STATE_MAX -} SocketState; - -struct Socket { - Meta meta; - - SocketState state; - - Address address; - int *fds; - unsigned n_fds; - - Service *subject; -}; - -typedef enum MilestoneState { - MILESTONE_DEAD, - MILESTONE_BEFORE, - MILESTONE_ACTIVE -} MilestoneState; - -struct Milestone { - Meta meta; - - MilestoneState state; -}; - -typedef enum DeviceState { - DEVICE_DEAD, - DEVICE_BEFORE, - DEVICE_AVAILABLE -} DeviceState; - -struct Device { - Meta meta; - - DeviceState state; - char *sysfs; -}; - -typedef enum MountState { - MOUNT_DEAD, - MOUNT_BEFORE, - MOUNT_MOUNTING, - MOUNT_MOUNTED, - MOUNT_UNMOUNTING, - MOUNT_SIGTERM, /* if the mount command hangs */ - MOUNT_SIGKILL, - MOUNT_MAINTAINANCE -} MountState; - -struct Mount { - Meta meta; - - MountState state; - char *path; -}; - -typedef enum AutomountState { - AUTOMOUNT_DEAD, - AUTOMOUNT_BEFORE, - AUTOMOUNT_START_PRE, - AUTOMOUNT_START, - AUTOMOUNT_START_POST, - AUTOMOUNT_WAITING, - AUTOMOUNT_RUNNING, - AUTOMOUNT_STOP_PRE, - AUTOMOUNT_STOP, - AUTOMOUNT_STOP_POST, - AUTOMOUNT_MAINTAINANCE -} AutomountState; - -struct Automount { - Meta meta; - - AutomountState state; - char *path; - Mount *subject; -}; - -typedef enum SnapshotState { - SNAPSHOT_DEAD, - SNAPSHOT_BEFORE, - SNAPSHOT_ACTIVE -} SnapshotState; - -struct Snapshot { - Meta meta; - - SnapshotState state; - bool cleanup:1; -}; +#include "service.h" +#include "timer.h" +#include "socket.h" +#include "milestone.h" +#include "device.h" +#include "mount.h" +#include "automount.h" +#include "snapshot.h" union Name { Meta meta; @@ -254,30 +126,48 @@ union Name { Snapshot snapshot; }; -/* For casting a name into the various name types */ +struct NameVTable { + const char *suffix; + + int (*load)(Name *n); + void (*dump)(Name *n, FILE *f, const char *prefix); + + int (*start)(Name *n); + int (*stop)(Name *n); + int (*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 (*free_hook)(Name *n); +}; -#define DEFINE_CAST(UPPERCASE, MixedCase, lowercase) \ +/* For casting a name into the various name types */ +#define DEFINE_CAST(UPPERCASE, MixedCase) \ static inline MixedCase* UPPERCASE(Name *name) { \ - if (name->meta.type != NAME_##UPPERCASE) \ + if (!name || name->meta.type != NAME_##UPPERCASE) \ return NULL; \ \ - return &name->lowercase; \ + return (MixedCase*) name; \ } -DEFINE_CAST(SERVICE, Service, service); -DEFINE_CAST(TIMER, Timer, timer); -DEFINE_CAST(SOCKET, Socket, socket); -DEFINE_CAST(MILESTONE, Milestone, milestone); -DEFINE_CAST(DEVICE, Device, device); -DEFINE_CAST(MOUNT, Mount, mount); -DEFINE_CAST(AUTOMOUNT, Automount, automount); -DEFINE_CAST(SNAPSHOT, Snapshot, snapshot); - /* For casting the various name types into a name */ #define NAME(o) ((Name*) (o)) -bool name_is_running(Name *name); -bool name_is_dead(Name *name); +DEFINE_CAST(SOCKET, Socket); +DEFINE_CAST(TIMER, Timer); +DEFINE_CAST(SERVICE, Service); +DEFINE_CAST(MILESTONE, Milestone); +DEFINE_CAST(DEVICE, Device); +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); NameType name_type_from_string(const char *n); bool name_is_valid(const char *n); @@ -288,9 +178,17 @@ int name_link(Name *name); int name_link_names(Name *name, bool replace); int name_merge(Name *name, Name *other); int name_sanitize(Name *n); +int name_load_fragment_and_dropin(Name *n); int name_load(Name *name); const char* name_id(Name *n); +const char *name_description(Name *n); void name_dump(Name *n, FILE *f, const char *prefix); +int name_start(Name *n); +int name_stop(Name *n); +int name_reload(Name *n); + +int name_notify(Name *n, NameActiveState old, NameActiveState new); + #endif diff --git a/service.c b/service.c new file mode 100644 index 00000000..ac9c39c3 --- /dev/null +++ b/service.c @@ -0,0 +1,182 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include + +#include "name.h" +#include "service.h" +#include "load-fragment.h" +#include "load-dropin.h" + +static int service_load_sysv(Service *s) { + assert(s); + + /* Load service data from SysV init scripts, preferably with + * LSB headers ... */ + + return -ENOENT; +} + +static int service_load(Name *n) { + int r; + Service *s = SERVICE(n); + + assert(s); + + exec_context_defaults(&s->exec_context); + + /* Load a .service file */ + r = name_load_fragment(n); + + /* Load a classic init script as a fallback */ + if (r == -ENOENT) + r = service_load_sysv(s); + + if (r < 0) + return r; + + /* Load dropin directory data */ + if ((r = name_load_dropin(n)) < 0) + return r; + + return 0; +} + +static void service_dump(Name *n, FILE *f, const char *prefix) { + + static const char* const state_table[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = "dead", + [SERVICE_START_PRE] = "start-pre", + [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_POST] = "stop-post", + [SERVICE_MAINTAINANCE] = "maintainance" + }; + + 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", + }; + + ServiceExecCommand c; + Service *s = SERVICE(n); + + assert(s); + + fprintf(f, + "%sService State: %s\n", + prefix, state_table[s->state]); + + 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]) + fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path); + } +} + +static int service_set_state(Service *s, ServiceState state) { + assert(s); + + s->state = state; + return 0; +} + +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) + return -EAGAIN; + + assert(s->state == SERVICE_DEAD || s->state == SERVICE_MAINTAINANCE); + + return service_set_state(s, SERVICE_START_PRE); +} + +static int service_stop(Name *n) { + Service *s = SERVICE(n); + + assert(s); + + + 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); + + for (c = 0; c < _SERVICE_EXEC_MAX; c++) + exec_command_free_list(s->exec_command[c]); + + if (s->socket) + s->socket->service = NULL; +} + +const NameVTable service_vtable = { + .suffix = ".service", + + .load = service_load, + .dump = service_dump, + + .start = service_start, + .stop = service_stop, + .reload = service_reload, + + .active_state = service_active_state, + + .free_hook = service_free_hook +}; diff --git a/service.h b/service.h new file mode 100644 index 00000000..d7f2c708 --- /dev/null +++ b/service.h @@ -0,0 +1,65 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef fooservicehfoo +#define fooservicehfoo + +typedef struct Service Service; + +#include "name.h" +#include "socket.h" +#include "timer.h" + +typedef enum ServiceState { + SERVICE_DEAD, + SERVICE_START_PRE, + 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_POST, + SERVICE_MAINTAINANCE, + _SERVICE_STATE_MAX, +} ServiceState; + +typedef enum ServiceMode { + SERVICE_ONCE, + SERVICE_RESTART +} ServiceMode; + +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 +} ServiceExecCommand; + +struct Service { + Meta meta; + + ServiceState state; + ServiceMode mode; + + ExecCommand* exec_command[_SERVICE_EXEC_MAX]; + ExecContext exec_context; + + pid_t service_pid, control_pid; + + Socket *socket; + Timer *timer; +}; + +const NameVTable service_vtable; + +#endif diff --git a/snapshot.c b/snapshot.c new file mode 100644 index 00000000..fc7bb5f4 --- /dev/null +++ b/snapshot.c @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#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) { + Snapshot *s = SNAPSHOT(n); + + assert(s); + + /* Nothing here for now */ +} + +const NameVTable snapshot_vtable = { + .suffix = ".snapshot", + + .load = NULL, + .dump = NULL, + + .start = NULL, + .stop = NULL, + .reload = NULL, + + .active_state = snapshot_active_state, + + .free_hook = snapshot_free_hook +}; diff --git a/snapshot.h b/snapshot.h new file mode 100644 index 00000000..8d35e174 --- /dev/null +++ b/snapshot.h @@ -0,0 +1,24 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foosnapshothfoo +#define foosnapshothfoo + +typedef struct Snapshot Snapshot; + +#include "name.h" + +typedef enum SnapshotState { + SNAPSHOT_DEAD, + SNAPSHOT_ACTIVE +} SnapshotState; + +struct Snapshot { + Meta meta; + + SnapshotState state; + bool cleanup:1; +}; + +extern const NameVTable snapshot_vtable; + +#endif diff --git a/socket.c b/socket.c new file mode 100644 index 00000000..9f871844 --- /dev/null +++ b/socket.c @@ -0,0 +1,113 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include "name.h" +#include "socket.h" + +static int socket_load(Name *n) { + Socket *s = SOCKET(n); + + exec_context_defaults(&s->exec_context); + + return name_load_fragment_and_dropin(n); +} + +static void socket_dump(Name *n, FILE *f, const char *prefix) { + + static const char* const state_table[_SOCKET_STATE_MAX] = { + [SOCKET_DEAD] = "dead", + [SOCKET_START_PRE] = "start-pre", + [SOCKET_START_POST] = "start-post", + [SOCKET_LISTENING] = "listening", + [SOCKET_RUNNING] = "running", + [SOCKET_STOP_PRE] = "stop-pre", + [SOCKET_STOP_POST] = "stop-post", + [SOCKET_MAINTAINANCE] = "maintainance" + }; + + static const char* const command_table[_SOCKET_EXEC_MAX] = { + [SOCKET_EXEC_START_PRE] = "StartPre", + [SOCKET_EXEC_START_POST] = "StartPost", + [SOCKET_EXEC_STOP_PRE] = "StopPre", + [SOCKET_EXEC_STOP_POST] = "StopPost" + }; + + SocketExecCommand c; + Socket *s = SOCKET(n); + const char *t; + int r; + char *k; + + assert(s); + + if ((r = address_print(&n->socket.address, &k)) < 0) + t = strerror(-r); + else + t = k; + + fprintf(f, + "%sSocket State: %s\n" + "%sAddress: %s\n", + prefix, state_table[s->state], + prefix, t); + + free(k); + + exec_context_dump(&s->exec_context, f, prefix); + + for (c = 0; c < _SOCKET_EXEC_MAX; c++) { + ExecCommand *i; + + LIST_FOREACH(i, s->exec_command[c]) + fprintf(f, "%s%s: %s\n", prefix, command_table[c], i->path); + } +} + +static NameActiveState socket_active_state(Name *n) { + + static const NameActiveState table[_SOCKET_STATE_MAX] = { + [SOCKET_DEAD] = NAME_INACTIVE, + [SOCKET_START_PRE] = NAME_ACTIVATING, + [SOCKET_START_POST] = NAME_ACTIVATING, + [SOCKET_LISTENING] = NAME_ACTIVE, + [SOCKET_RUNNING] = NAME_ACTIVE, + [SOCKET_STOP_PRE] = NAME_DEACTIVATING, + [SOCKET_STOP_POST] = NAME_DEACTIVATING, + [SOCKET_MAINTAINANCE] = NAME_INACTIVE, + }; + + return table[SOCKET(n)->state]; +} + +static void socket_free_hook(Name *n) { + unsigned i; + SocketExecCommand c; + Socket *s = SOCKET(n); + + assert(s); + + for (i = 0; i < s->n_fds; i++) + close_nointr(s->fds[i]); + + exec_context_free(&s->exec_context); + + for (c = 0; c < _SOCKET_EXEC_MAX; c++) + exec_command_free_list(s->exec_command[c]); + + if (s->service) + s->service->socket = NULL; +} + +const NameVTable socket_vtable = { + .suffix = ".socket", + + .load = socket_load, + .dump = socket_dump, + + .start = NULL, + .stop = NULL, + .reload = NULL, + + .active_state = socket_active_state, + + .free_hook = socket_free_hook +}; diff --git a/socket.h b/socket.h new file mode 100644 index 00000000..fcf09dd0 --- /dev/null +++ b/socket.h @@ -0,0 +1,49 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foosockethfoo +#define foosockethfoo + +typedef struct Socket Socket; + +#include "name.h" + +typedef enum SocketState { + SOCKET_DEAD, + SOCKET_START_PRE, + SOCKET_START_POST, + SOCKET_LISTENING, + SOCKET_RUNNING, + SOCKET_STOP_PRE, + SOCKET_STOP_POST, + SOCKET_MAINTAINANCE, + _SOCKET_STATE_MAX +} SocketState; + +typedef enum SocketExecCommand { + SOCKET_EXEC_START_PRE, + SOCKET_EXEC_START_POST, + SOCKET_EXEC_STOP_PRE, + SOCKET_EXEC_STOP_POST, + _SOCKET_EXEC_MAX +} SocketExecCommand; + +struct Socket { + Meta meta; + + SocketState state; + + Address address; + int *fds; + unsigned n_fds; + + ExecCommand* exec_command[_SOCKET_EXEC_MAX]; + ExecContext exec_context; + + pid_t control_pid; + + Service *service; +}; + +extern const NameVTable socket_vtable; + +#endif diff --git a/test-job-type.c b/test-job-type.c index db5c329e..62f04fdf 100644 --- a/test-job-type.c +++ b/test-job-type.c @@ -13,16 +13,16 @@ int main(int argc, char*argv[]) { for (a = 0; a < _JOB_TYPE_MAX; a++) for (b = 0; b < _JOB_TYPE_MAX; b++) { - if (!job_type_mergeable(a, b)) + if (!job_type_is_mergeable(a, b)) printf("Not mergeable: %s + %s\n", job_type_to_string(a), job_type_to_string(b)); for (c = 0; c < _JOB_TYPE_MAX; c++) { /* Verify transitivity of mergeability * of job types */ - assert(!job_type_mergeable(a, b) || - !job_type_mergeable(b, c) || - job_type_mergeable(a, c)); + assert(!job_type_is_mergeable(a, b) || + !job_type_is_mergeable(b, c) || + job_type_is_mergeable(a, c)); d = a; if (job_type_merge(&d, b) >= 0) { @@ -32,14 +32,14 @@ int main(int argc, char*argv[]) { /* Verify that merged entries can be * merged with the same entries they * can be merged with seperately */ - assert(!job_type_mergeable(a, c) || job_type_mergeable(d, c)); - assert(!job_type_mergeable(b, c) || job_type_mergeable(d, c)); + assert(!job_type_is_mergeable(a, c) || job_type_is_mergeable(d, c)); + assert(!job_type_is_mergeable(b, c) || job_type_is_mergeable(d, c)); /* Verify that if a merged * with b is not mergable with * c then either a or b is not * mergeable with c either. */ - assert(job_type_mergeable(d, c) || !job_type_mergeable(a, c) || !job_type_mergeable(b, c)); + assert(job_type_is_mergeable(d, c) || !job_type_is_mergeable(a, c) || !job_type_is_mergeable(b, c)); e = b; if (job_type_merge(&e, c) >= 0) { diff --git a/test2/h.service b/test2/h.service new file mode 100644 index 00000000..4b9ffa3e --- /dev/null +++ b/test2/h.service @@ -0,0 +1,3 @@ +[Meta] +Description=H +Wants=g.service diff --git a/timer.c b/timer.c new file mode 100644 index 00000000..b1571ce7 --- /dev/null +++ b/timer.c @@ -0,0 +1,39 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#include "name.h" +#include "timer.h" + +static NameActiveState timer_active_state(Name *n) { + + static const NameActiveState table[_TIMER_STATE_MAX] = { + [TIMER_DEAD] = NAME_INACTIVE, + [TIMER_WAITING] = NAME_ACTIVE, + [TIMER_RUNNING] = NAME_ACTIVE + }; + + return table[TIMER(n)->state]; +} + +static void timer_free_hook(Name *n) { + Timer *t = TIMER(n); + + assert(t); + + if (t->service) + t->service->timer = NULL; +} + +const NameVTable timer_vtable = { + .suffix = ".timer", + + .load = name_load_fragment_and_dropin, + .dump = NULL, + + .start = NULL, + .stop = NULL, + .reload = NULL, + + .active_state = timer_active_state, + + .free_hook = timer_free_hook +}; diff --git a/timer.h b/timer.h new file mode 100644 index 00000000..fcbddc0a --- /dev/null +++ b/timer.h @@ -0,0 +1,30 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef footimerhfoo +#define footimerhfoo + +typedef struct Timer Timer; + +#include "name.h" + +typedef enum TimerState { + TIMER_DEAD, + TIMER_WAITING, + TIMER_RUNNING, + _TIMER_STATE_MAX +} TimerState; + +struct Timer { + Meta meta; + + TimerState state; + + clockid_t clock_id; + usec_t next_elapse; + + Service *service; +}; + +const NameVTable timer_vtable; + +#endif diff --git a/util.h b/util.h index f5aaf704..67314146 100644 --- a/util.h +++ b/util.h @@ -7,6 +7,7 @@ #include #include #include +#include typedef uint64_t usec_t; -- 2.39.5