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

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
This diff is collapsed.
......@@ -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
/*-*- 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;
}
/*-*- 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
......@@ -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];
......
/*-*- 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;
}
/*-*- 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
......@@ -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)
......
......@@ -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);
......
/*-*- 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,