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
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#include <errno.h>
+
+#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
+};
--- /dev/null
+/*-*- 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
--- /dev/null
+/*-*- 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
+};
--- /dev/null
+/*-*- 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
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#include <assert.h>
+
+#include "execute.h"
+#include "strv.h"
+#include "macro.h"
+#include "util.h"
+
+int exec_spawn(const ExecCommand *command, const ExecContext *context, pid_t *ret) {
+ assert(command);
+ assert(context);
+ assert(ret);
+
+ 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;
+}
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef fooexecutehfoo
+#define fooexecutehfoo
+
+typedef struct ExecStatus ExecStatus;
+typedef struct ExecCommand ExecCommand;
+typedef struct ExecContext ExecContext;
+
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/capability.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#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
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);
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";
static const char* const job_state_table[_JOB_STATE_MAX] = {
[JOB_WAITING] = "waiting",
- [JOB_RUNNING] = "running",
- [JOB_DONE] = "done"
+ [JOB_RUNNING] = "running"
};
assert(j);
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) {
/* 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
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;
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;
+}
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"
#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
};
enum JobState {
JOB_WAITING,
JOB_RUNNING,
- JOB_DONE,
_JOB_STATE_MAX
};
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;
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;
};
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
--- /dev/null
+/*-*- 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;
+}
--- /dev/null
+/*-*- 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
if (other != name) {
- if (other->meta.state != NAME_STUB) {
+ if (other->meta.load_state != NAME_STUB) {
free(t);
return -EEXIST;
}
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",
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];
--- /dev/null
+/*-*- 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;
+}
--- /dev/null
+/*-*- 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
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;
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);
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
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;
}
* 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 */
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 */
!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;
}
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;
/* 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;
}
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)
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 */
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;
j->generation = 0;
j->marker = NULL;
j->matters_to_anchor = false;
+ j->forced = force;
if (is_new)
*is_new = true;
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);
}
}
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. */
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)
JobDependency *transaction_anchor;
bool dispatching_load_queue:1;
+
+ Hashmap *pids; /* pid => Name object n:1 */
};
Manager* manager_new(void);
--- /dev/null
+/*-*- 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
+};
--- /dev/null
+/*-*- 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
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#include <errno.h>
+
+#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
+};
--- /dev/null
+/*-*- 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
#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;
assert(n);
+ if (strlen(n) >= NAME_MAX)
+ return false;
+
t = name_type_from_string(n);
if (t < 0 || t >= _NAME_TYPE_MAX)
return false;
/* 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() */
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;
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);
}
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);
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;
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 */
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;
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] = {
[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",
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;
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;
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;
}
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"
#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,
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];
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;
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);
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
--- /dev/null
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#include <errno.h>
+
+#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
+};
--- /dev/null
+/*-*- 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
--- /dev/null
+/*-*- 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
+};
--- /dev/null
+/*-*- 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
--- /dev/null
+/*-*- 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
+};
--- /dev/null
+/*-*- 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
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) {
/* 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) {
--- /dev/null
+[Meta]
+Description=H
+Wants=g.service
--- /dev/null
+/*-*- 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
+};
--- /dev/null
+/*-*- 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
#include <time.h>
#include <sys/time.h>
#include <stdbool.h>
+#include <stdlib.h>
typedef uint64_t usec_t;