Commit 5cb5a6ff authored by Lennart Poettering's avatar Lennart Poettering
Browse files

first attempt in implementinging execution logic

parent cd2dbd7d
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
......
/*-*- 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
};
/*-*- 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
/*-*- 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
};
/*-*- 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
/*-*- 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;
}
/*-*- 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
......@@ -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;