From: Lennart Poettering Date: Sat, 23 Jan 2010 00:52:57 +0000 (+0100) Subject: first attempt in implementinging execution logic X-Git-Tag: v1~826 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=5cb5a6ffc33667c93e9bc3572534dcaa684046e3;hp=cd2dbd7df9f1b8c46386b2898523aec3dd4578fd first attempt in implementinging execution logic --- diff --git a/Makefile b/Makefile index 5c6d798fb..ac815a1fe 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 000000000..84691e6e6 --- /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 000000000..b6dfd5bb0 --- /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 000000000..79847c46b --- /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 000000000..e2597f710 --- /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 000000000..9bb8351fb --- /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 000000000..fb952d1e6 --- /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 4688164e5..0ae1a765f 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 d839db5a4..fbfaa16b2 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 000000000..7105cb86a --- /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 000000000..c4971a51b --- /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 ed046d035..a2c36368d 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 000000000..cfefcb661 --- /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 000000000..e7056502d --- /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 14ae65257..701e64512 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 287e532b5..e669f9f90 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 000000000..ad8080aef --- /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 000000000..332fbe44c --- /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 000000000..fa92e5b7b --- /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 000000000..b437b2e7f --- /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 08194d05f..5dba01055 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 869e71bb0..33baea1b5 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 000000000..ac9c39c3c --- /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 000000000..d7f2c708d --- /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 000000000..fc7bb5f47 --- /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 000000000..8d35e174c --- /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 000000000..9f8718447 --- /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 000000000..fcf09dd0d --- /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 db5c329e1..62f04fdf1 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 000000000..4b9ffa3e8 --- /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 000000000..b1571ce7e --- /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 000000000..fcbddc0ac --- /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 f5aaf704d..67314146f 100644 --- a/util.h +++ b/util.h @@ -7,6 +7,7 @@ #include #include #include +#include typedef uint64_t usec_t;