#include "load-fragment.h"
#include "load-dropin.h"
-static int automount_init(Unit *u) {
+static int automount_init(Unit *u, UnitLoadState *new_state) {
int r;
Automount *a = AUTOMOUNT(u);
exec_context_init(&a->exec_context);
/* Load a .automount file */
- if ((r = unit_load_fragment(u)) < 0)
+ if ((r = unit_load_fragment(u, new_state)) < 0)
return r;
+ if (*new_state == UNIT_STUB)
+ *new_state = UNIT_LOADED;
+
/* Load drop-in directory data */
- if ((r = unit_load_dropin(u)) < 0)
+ if ((r = unit_load_dropin(unit_follow_merge(u))) < 0)
return r;
+ if (*new_state == UNIT_LOADED) {
+
+ if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(a->mount))) < 0)
+ return r;
+
+ if ((r = unit_add_exec_dependencies(u, &a->exec_context)) < 0)
+ return r;
+
+ if ((r = unit_add_default_cgroup(u)) < 0)
+ return r;
+ }
+
return 0;
}
const UnitVTable device_vtable = {
.suffix = ".device",
- .init = unit_load_fragment_and_dropin,
+ .init = unit_load_fragment_and_dropin_optional,
.done = device_done,
.coldplug = device_coldplug,
}
/* Detach from next 'smaller' objects */
- manager_transaction_unlink_job(j->manager, j);
+ manager_transaction_unlink_job(j->manager, j, true);
if (j->in_run_queue)
LIST_REMOVE(Job, run_queue, j->manager->run_queue, j);
FOREACH_WORD(w, l, rvalue, state) {
char *t;
int r;
- Unit *other;
if (!(t = strndup(w, l)))
return -ENOMEM;
- other = manager_get_unit(u->meta.manager, t);
-
- if (other) {
-
- if (other != u) {
-
- if (other->meta.load_state != UNIT_STUB) {
- free(t);
- return -EEXIST;
- }
-
- if ((r = unit_merge(u, other)) < 0) {
- free(t);
- return r;
- }
- }
-
- } else {
- if ((r = unit_add_name(u, t)) < 0) {
- free(t);
- return r;
- }
- }
-
+ r = unit_merge_by_name(u, t);
free(t);
+
+ if (r < 0)
+ return r;
}
return 0;
return 0;
}
-static int load_from_path(Unit *u, const char *path) {
+static int merge_by_names(Unit **u, Set *names, const char *id) {
+ char *k;
+ int r;
+
+ assert(u);
+ assert(*u);
+ assert(names);
+
+ /* Let's try to add in all symlink names we found */
+ while ((k = set_steal_first(names))) {
+
+ /* First try to merge in the other name into our
+ * unit */
+ if ((r = unit_merge_by_name(*u, k)) < 0) {
+ Unit *other;
+
+ /* Hmm, we couldn't merge the other unit into
+ * ours? Then let's try it the other way
+ * round */
+
+ other = manager_get_unit((*u)->meta.manager, k);
+ free(k);
+
+ if (other)
+ if ((r = unit_merge(other, *u)) >= 0) {
+ *u = other;
+ return merge_by_names(u, names, NULL);
+ }
+
+ return r;
+ }
+
+ if (id == k)
+ unit_choose_id(*u, id);
+
+ free(k);
+ }
+
+ return 0;
+}
+
+static int load_from_path(Unit *u, const char *path, UnitLoadState *new_state) {
static const char* const section_table[_UNIT_TYPE_MAX] = {
[UNIT_SERVICE] = "Service",
char *k;
int r;
Set *symlink_names;
- FILE *f;
- char *filename = NULL, *id;
+ FILE *f = NULL;
+ char *filename = NULL, *id = NULL;
+ Unit *merged;
+
+ assert(u);
+ assert(new_state);
sections[0] = "Meta";
sections[1] = section_table[u->meta.type];
}
if (!filename) {
- r = 0; /* returning 0 means: no suitable config file found */
+ r = 0;
goto finish;
}
- /* Now, parse the file contents */
- r = config_parse(filename, f, sections, items, u);
- if (r < 0)
+ merged = u;
+ if ((r = merge_by_names(&merged, symlink_names, id)) < 0)
goto finish;
- /* Let's try to add in all symlink names we found */
- while ((k = set_steal_first(symlink_names))) {
- if ((r = unit_add_name(u, k)) < 0)
- goto finish;
-
-
- if (id == k)
- unit_choose_id(u, id);
- free(k);
+ if (merged != u) {
+ *new_state = UNIT_MERGED;
+ r = 0;
+ goto finish;
}
+ /* Now, parse the file contents */
+ if ((r = config_parse(filename, f, sections, items, u)) < 0)
+ goto finish;
free(u->meta.fragment_path);
u->meta.fragment_path = filename;
filename = NULL;
- r = 1; /* returning 1 means: suitable config file found and loaded */
+ *new_state = UNIT_LOADED;
+ r = 0;
finish:
while ((k = set_steal_first(symlink_names)))
free(k);
+
set_free(symlink_names);
free(filename);
+ if (f)
+ fclose(f);
+
return r;
}
-int unit_load_fragment(Unit *u) {
- int r = 0;
+int unit_load_fragment(Unit *u, UnitLoadState *new_state) {
+ int r;
assert(u);
- assert(u->meta.load_state == UNIT_STUB);
+ assert(new_state);
+ assert(*new_state == UNIT_STUB);
+
+ if (u->meta.fragment_path) {
+
+ if ((r = load_from_path(u, u->meta.fragment_path, new_state)) < 0)
+ return r;
- if (u->meta.fragment_path)
- r = load_from_path(u, u->meta.fragment_path);
- else {
+ } else {
Iterator i;
const char *t;
/* Try to find the unit under its id */
if ((t = unit_id(u)))
- r = load_from_path(u, t);
+ if ((r = load_from_path(u, t, new_state)) < 0)
+ return r;
/* Try to find an alias we can load this with */
- if (r == 0)
- SET_FOREACH(t, u->meta.names, i)
- if ((r = load_from_path(u, t)) != 0)
- break;
- }
+ if (*new_state == UNIT_STUB)
+ SET_FOREACH(t, u->meta.names, i) {
- if (r >= 0) {
- ExecContext *c;
+ if (unit_id(u) == t)
+ continue;
- if (u->meta.type == UNIT_SOCKET)
- c = &u->socket.exec_context;
- else if (u->meta.type == UNIT_SERVICE)
- c = &u->service.exec_context;
- else
- c = NULL;
-
- if (c &&
- (c->output == EXEC_OUTPUT_KERNEL || c->output == EXEC_OUTPUT_SYSLOG)) {
- int k;
-
- /* If syslog or kernel logging is requested, make sure
- * our own logging daemon is run first. */
-
- if ((k = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_LOGGER_SOCKET)) < 0)
- return k;
+ if ((r = load_from_path(u, t, new_state)) < 0)
+ return r;
- if (u->meta.manager->running_as != MANAGER_SESSION)
- if ((k = unit_add_dependency_by_name(u, UNIT_REQUIRES, SPECIAL_LOGGER_SOCKET)) < 0)
- return k;
- }
+ if (*new_state != UNIT_STUB)
+ break;
+ }
}
- return r;
+ return 0;
}
/* Read service data from .desktop file style configuration fragments */
-int unit_load_fragment(Unit *u);
+int unit_load_fragment(Unit *u, UnitLoadState *new_state);
#endif
static int manager_find_paths(Manager *m) {
const char *e;
char *t;
+
assert(m);
/* First priority is whatever has been passed to us via env
return r;
}
+static unsigned manager_dispatch_cleanup_queue(Manager *m) {
+ Meta *meta;
+ unsigned n = 0;
+
+ assert(m);
+
+ while ((meta = m->cleanup_queue)) {
+ assert(meta->in_cleanup_queue);
+
+ unit_free(UNIT(meta));
+ n++;
+ }
+
+ return n;
+}
+
void manager_free(Manager *m) {
UnitType c;
Unit *u;
while ((u = hashmap_first(m->units)))
unit_free(u);
+ manager_dispatch_cleanup_queue(m);
+
for (c = 0; c < _UNIT_TYPE_MAX; c++)
if (unit_vtable[c]->shutdown)
unit_vtable[c]->shutdown(m);
return 0;
}
-static void transaction_delete_job(Manager *m, Job *j) {
+static void transaction_delete_job(Manager *m, Job *j, bool delete_dependencies) {
assert(m);
assert(j);
/* Deletes one job from the transaction */
- manager_transaction_unlink_job(m, j);
+ manager_transaction_unlink_job(m, j, delete_dependencies);
if (!j->installed)
job_free(j);
* transaction */
while ((j = hashmap_get(m->transaction_jobs, u)))
- transaction_delete_job(m, j);
+ transaction_delete_job(m, j, true);
}
static void transaction_clean_dependencies(Manager *m) {
while ((j = hashmap_first(m->transaction_jobs)))
if (j->installed)
- transaction_delete_job(m, j);
+ transaction_delete_job(m, j, true);
else
job_free(j);
/* Kill the other job */
other->subject_list = NULL;
other->object_list = NULL;
- transaction_delete_job(m, other);
+ transaction_delete_job(m, other, true);
}
static int delete_one_unmergeable_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/%s", unit_id(d->unit), job_type_to_string(d->type));
- transaction_delete_job(m, d);
+ log_debug("Trying to fix job merging by deleting job %s/%s", unit_id(d->unit), job_type_to_string(d->type));
+ transaction_delete_job(m, d, true);
return 0;
}
return 0;
}
+static void transaction_drop_redundant(Manager *m) {
+ bool again;
+
+ assert(m);
+
+ /* Goes through the transaction and removes all jobs that are
+ * a noop */
+
+ do {
+ Job *j;
+ Iterator i;
+
+ again = false;
+
+ HASHMAP_FOREACH(j, m->transaction_jobs, i) {
+ bool changes_something = false;
+ Job *k;
+
+ LIST_FOREACH(transaction, k, j) {
+
+ if (!job_is_anchor(k) &&
+ job_type_is_redundant(k->type, unit_active_state(k->unit)))
+ continue;
+
+ changes_something = true;
+ break;
+ }
+
+ if (changes_something)
+ continue;
+
+ log_debug("Found redundant job %s/%s, dropping.", unit_id(j->unit), job_type_to_string(j->type));
+ transaction_delete_job(m, j, false);
+ again = true;
+ break;
+ }
+
+ } while (again);
+}
+
static bool unit_matters_to_anchor(Unit *u, Job *j) {
assert(u);
assert(!j->transaction_prev);
* since smart how we are we stored our way back in
* there. */
- log_debug("Found cycle on %s/%s", unit_id(j->unit), job_type_to_string(j->type));
+ log_debug("Found ordering cycle on %s/%s", unit_id(j->unit), job_type_to_string(j->type));
for (k = from; k; k = (k->generation == generation ? k->marker : NULL)) {
- log_debug("Walked on cycle path to %s/%s", unit_id(j->unit), job_type_to_string(j->type));
+ log_debug("Walked on cycle path to %s/%s", unit_id(k->unit), job_type_to_string(k->type));
if (!k->installed &&
!unit_matters_to_anchor(k->unit, k)) {
continue;
log_debug("Garbage collecting job %s/%s", unit_id(j->unit), job_type_to_string(j->type));
- transaction_delete_job(m, j);
+ transaction_delete_job(m, j, true);
again = true;
break;
}
/* Ok, let's get rid of this */
log_debug("Deleting %s/%s to minimize impact.", unit_id(j->unit), job_type_to_string(j->type));
- transaction_delete_job(m, j);
+ transaction_delete_job(m, j, true);
again = true;
break;
}
* jobs if we don't have to. */
transaction_minimize_impact(m);
+ /* Third step: Drop redundant jobs */
+ transaction_drop_redundant(m);
+
for (;;) {
- /* Third step: Let's remove unneeded jobs that might
+ /* Fourth step: Let's remove unneeded jobs that might
* be lurking. */
transaction_collect_garbage(m);
- /* Fourth step: verify order makes sense and correct
+ /* Fifth step: verify order makes sense and correct
* cycles if necessary and possible */
if ((r = transaction_verify_order(m, &generation)) >= 0)
break;
}
for (;;) {
- /* Fifth step: let's drop unmergeable entries if
+ /* Sixth step: let's drop unmergeable entries if
* necessary and possible, merge entries we can
* merge */
if ((r = transaction_merge_jobs(m)) >= 0)
goto rollback;
}
- /* Sixth step: an entry got dropped, let's garbage
+ /* Seventh step: an entry got dropped, let's garbage
* collect its dependencies. */
transaction_collect_garbage(m);
* unmergeable entries ... */
}
- /* Seventh step: check whether we can actually apply this */
+ /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */
+ transaction_drop_redundant(m);
+
+ /* Ninth step: check whether we can actually apply this */
if (mode == JOB_FAIL)
if ((r = transaction_is_destructive(m, mode)) < 0) {
log_debug("Requested transaction contradicts existing jobs: %s", strerror(-r));
goto rollback;
}
- /* Eights step: apply changes */
+ /* Tenth step: apply changes */
if ((r = transaction_apply(m, mode)) < 0) {
log_debug("Failed to apply transaction: %s", strerror(-r));
goto rollback;
if (is_new)
*is_new = true;
+ log_debug("Added job %s/%s to transaction.", unit_id(unit), job_type_to_string(type));
+
return j;
}
-void manager_transaction_unlink_job(Manager *m, Job *j) {
+void manager_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies) {
assert(m);
assert(j);
job_dependency_free(j->object_list);
- if (other) {
+ if (other && delete_dependencies) {
log_debug("Deleting job %s/%s as dependency of job %s/%s",
unit_id(other->unit), job_type_to_string(other->type),
unit_id(j->unit), job_type_to_string(j->type));
- transaction_delete_job(m, other);
+ transaction_delete_job(m, other, delete_dependencies);
}
}
}
manager_dispatch_load_queue(m);
- *_ret = ret;
+ *_ret = unit_follow_merge(ret);
return 0;
}
m->dispatching_dbus_queue = true;
while ((meta = m->dbus_unit_queue)) {
- Unit *u = (Unit*) meta;
- assert(u->meta.in_dbus_queue);
+ assert(meta->in_dbus_queue);
- bus_unit_send_change_signal(u);
+ bus_unit_send_change_signal(UNIT(meta));
n++;
}
assert(m);
- for (;;) {
+ do {
struct epoll_event event;
int n;
sleep(1);
}
+ if (manager_dispatch_cleanup_queue(m) > 0)
+ continue;
+
if (manager_dispatch_load_queue(m) > 0)
continue;
if ((r = process_event(m, &event, &quit)) < 0)
return r;
+ } while (!quit);
- if (quit)
- return 0;
- }
+ return 0;
}
int manager_get_unit_from_dbus_path(Manager *m, const char *s, Unit **_u) {
LIST_HEAD(Meta, dbus_unit_queue);
LIST_HEAD(Job, dbus_job_queue);
+ LIST_HEAD(Meta, cleanup_queue);
+
/* Jobs to be added */
Hashmap *transaction_jobs; /* Unit object => Job object list 1:1 */
JobDependency *transaction_anchor;
void manager_dump_units(Manager *s, FILE *f, const char *prefix);
void manager_dump_jobs(Manager *s, FILE *f, const char *prefix);
-void manager_transaction_unlink_job(Manager *m, Job *j);
+void manager_transaction_unlink_job(Manager *m, Job *j, bool delete_dependencies);
void manager_clear_jobs(Manager *m);
const UnitVTable mount_vtable = {
.suffix = ".mount",
- .init = unit_load_fragment_and_dropin,
+ .init = unit_load_fragment_and_dropin_optional,
.done = mount_done,
.coldplug = mount_coldplug,
#define NEWLINES "\n\r"
#define LINE_MAX 4096
+static const char * const rcnd_table[] = {
+ "../rc0.d", SPECIAL_RUNLEVEL0_TARGET,
+ "../rc1.d", SPECIAL_RUNLEVEL1_TARGET,
+ "../rc2.d", SPECIAL_RUNLEVEL2_TARGET,
+ "../rc3.d", SPECIAL_RUNLEVEL3_TARGET,
+ "../rc4.d", SPECIAL_RUNLEVEL4_TARGET,
+ "../rc5.d", SPECIAL_RUNLEVEL5_TARGET,
+ "../rc6.d", SPECIAL_RUNLEVEL6_TARGET
+};
+
+
static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
[SERVICE_DEAD] = UNIT_INACTIVE,
[SERVICE_START_PRE] = UNIT_ACTIVATING,
if (s->sysv_start_priority < 0)
return 0;
- /* If no LSB header is found we try to order init scripts via
- * the start priority of the chkconfig header. */
+ /* For each pair of services where at least one lacks a LSB
+ * header, we use the start priority value to order things. */
LIST_FOREACH(units_per_type, other, UNIT(s)->meta.manager->units_per_type[UNIT_SERVICE]) {
Service *t;
if (t->sysv_start_priority < 0)
continue;
+ if (s->sysv_has_lsb && t->sysv_has_lsb)
+ continue;
+
if (t->sysv_start_priority < s->sysv_start_priority)
d = UNIT_AFTER;
else if (t->sysv_start_priority > s->sysv_start_priority)
return 0;
}
-static int service_load_sysv_path(Service *s, const char *path) {
+static int priority_from_rcd(Service *s, const char *init_script) {
+ char **p;
+ unsigned i;
+
+ STRV_FOREACH(p, UNIT(s)->meta.manager->sysvinit_path)
+ for (i = 0; i < ELEMENTSOF(rcnd_table); i += 2) {
+ char *path;
+ DIR *d;
+ struct dirent *de;
+
+ if (asprintf(&path, "%s/%s", *p, rcnd_table[i]) < 0)
+ return -ENOMEM;
+
+ d = opendir(path);
+ free(path);
+
+ if (!d) {
+ if (errno != ENOENT)
+ log_warning("opendir() failed on %s: %s", path, strerror(errno));
+
+ continue;
+ }
+
+ while ((de = readdir(d))) {
+ int a, b;
+
+ if (ignore_file(de->d_name))
+ continue;
+
+ if (de->d_name[0] != 'S')
+ continue;
+
+ if (strlen(de->d_name) < 4)
+ continue;
+
+ if (!streq(de->d_name + 3, init_script))
+ continue;
+
+ /* Yay, we found it! Now decode the priority */
+
+ a = undecchar(de->d_name[1]);
+ b = undecchar(de->d_name[2]);
+
+ if (a < 0 || b < 0)
+ continue;
+
+ s->sysv_start_priority = a*10 + b;
+
+ log_debug("Determined priority %i from link farm for %s", s->sysv_start_priority, unit_id(UNIT(s)));
+
+ closedir(d);
+ return 0;
+ }
+
+ closedir(d);
+ }
+
+ return 0;
+}
+
+static int service_load_sysv_path(Service *s, const char *path, UnitLoadState *new_state) {
FILE *f;
Unit *u;
unsigned line = 0;
LSB,
LSB_DESCRIPTION
} state = NORMAL;
- bool has_lsb = false;
+
+ assert(s);
+ assert(path);
+ assert(new_state);
u = UNIT(s);
if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) {
state = LSB;
- has_lsb = true;
+ s->sysv_has_lsb = true;
continue;
}
}
}
- /* If the init script has no LSB header, then let's
- * enforce the ordering via the chkconfig
- * priorities */
+ /* If init scripts have no LSB header, then we enforce the
+ * ordering via the chkconfig priorities. We try to determine
+ * a priority for *all* init scripts here, since they are
+ * needed as soon as at least one non-LSB script is used. */
- if (!has_lsb)
- if ((r = sysv_chkconfig_order(s)) < 0)
+ if (s->sysv_start_priority < 0) {
+ log_debug("%s has no chkconfig header, trying to determine SysV priority from link farm.", unit_id(u));
+
+ if ((r = priority_from_rcd(s, file_name_from_path(path))) < 0)
goto finish;
+ if (s->sysv_start_priority < 0)
+ log_warning("%s has neither a chkconfig header nor a directory link, cannot order unit!", unit_id(u));
+ }
+
if ((r = sysv_exec_commands(s)) < 0)
goto finish;
(r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_SYSINIT_SERVICE)) < 0)
goto finish;
- r = 1;
+ *new_state = UNIT_LOADED;
+ r = 0;
finish:
return r;
}
-static int service_load_sysv_name(Service *s, const char *name) {
+static int service_load_sysv_name(Service *s, const char *name, UnitLoadState *new_state) {
char **p;
assert(s);
assert(endswith(path, ".service"));
path[strlen(path)-8] = 0;
- r = service_load_sysv_path(s, path);
+ r = service_load_sysv_path(s, path, new_state);
free(path);
- if (r != 0)
+ if (r < 0)
return r;
+
+ if (*new_state != UNIT_STUB)
+ break;
}
return 0;
}
-static int service_load_sysv(Service *s) {
+static int service_load_sysv(Service *s, UnitLoadState *new_state) {
const char *t;
Iterator i;
int r;
assert(s);
+ assert(new_state);
/* Load service data from SysV init scripts, preferably with
* LSB headers ... */
return 0;
if ((t = unit_id(UNIT(s))))
- if ((r = service_load_sysv_name(s, t)) != 0)
+ if ((r = service_load_sysv_name(s, t, new_state)) < 0)
return r;
- SET_FOREACH(t, UNIT(s)->meta.names, i)
- if ((r == service_load_sysv_name(s, t)) != 0)
- return r;
+ if (*new_state == UNIT_STUB)
+ SET_FOREACH(t, UNIT(s)->meta.names, i) {
+ if ((r == service_load_sysv_name(s, t, new_state)) < 0)
+ return r;
+
+ if (*new_state != UNIT_STUB)
+ break;
+ }
return 0;
}
-static int service_init(Unit *u) {
+static int service_init(Unit *u, UnitLoadState *new_state) {
int r;
Service *s = SERVICE(u);
assert(s);
+ assert(new_state);
+ assert(*new_state == UNIT_STUB);
/* First, reset everything to the defaults, in case this is a
* reload */
s->permissions_start_only = false;
s->root_directory_start_only = false;
+ s->sysv_has_lsb = false;
+
RATELIMIT_INIT(s->ratelimit, 10*USEC_PER_SEC, 5);
/* Load a .service file */
- if ((r = unit_load_fragment(u)) < 0) {
- service_done(u);
+ if ((r = unit_load_fragment(u, new_state)) < 0)
return r;
- }
/* Load a classic init script as a fallback, if we couldn't find anything */
- if (r == 0)
- if ((r = service_load_sysv(s)) <= 0) {
- service_done(u);
- return r < 0 ? r : -ENOENT;
- }
+ if (*new_state == UNIT_STUB)
+ if ((r = service_load_sysv(s, new_state)) < 0)
+ return r;
- /* Load dropin directory data */
- if ((r = unit_load_dropin(u)) < 0) {
- service_done(u);
- return r;
- }
+ /* Still nothing found? Then let's give up */
+ if (*new_state == UNIT_STUB)
+ return -ENOENT;
- /* Add default cgroup */
- if ((r = unit_add_default_cgroup(u)) < 0) {
- service_done(u);
+ /* We were able to load something, then let's add in the
+ * dropin directories. */
+ if ((r = unit_load_dropin(unit_follow_merge(u))) < 0)
return r;
+
+ /* This is a new unit? Then let's add in some extras */
+ if (*new_state == UNIT_LOADED) {
+ if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0)
+ return r;
+
+ if ((r = unit_add_default_cgroup(u)) < 0)
+ return r;
+
+ if ((r = sysv_chkconfig_order(s)) < 0)
+ return r;
}
return 0;
if (s->sysv_path)
fprintf(f,
- "%sSysV Init Script Path: %s\n",
- prefix, s->sysv_path);
+ "%sSysV Init Script Path: %s\n"
+ "%sSysV Init Script has LSB Header: %s\n",
+ prefix, s->sysv_path,
+ prefix, yes_no(s->sysv_has_lsb));
if (s->sysv_start_priority >= 0)
fprintf(f,
- "%sSysV Start Priority: %i\n",
+ "%sSysVStartPriority: %i\n",
prefix, s->sysv_start_priority);
+
free(p2);
}
}
static int service_enumerate(Manager *m) {
-
- static const char * const rcnd[] = {
- "../rc0.d", SPECIAL_RUNLEVEL0_TARGET,
- "../rc1.d", SPECIAL_RUNLEVEL1_TARGET,
- "../rc2.d", SPECIAL_RUNLEVEL2_TARGET,
- "../rc3.d", SPECIAL_RUNLEVEL3_TARGET,
- "../rc4.d", SPECIAL_RUNLEVEL4_TARGET,
- "../rc5.d", SPECIAL_RUNLEVEL5_TARGET,
- "../rc6.d", SPECIAL_RUNLEVEL6_TARGET
- };
-
char **p;
unsigned i;
DIR *d = NULL;
assert(m);
STRV_FOREACH(p, m->sysvinit_path)
- for (i = 0; i < ELEMENTSOF(rcnd); i += 2) {
+ for (i = 0; i < ELEMENTSOF(rcnd_table); i += 2) {
struct dirent *de;
free(path);
path = NULL;
- if (asprintf(&path, "%s/%s", *p, rcnd[i]) < 0) {
+ if (asprintf(&path, "%s/%s", *p, rcnd_table[i]) < 0) {
r = -ENOMEM;
goto finish;
}
free(fpath);
fpath = NULL;
- if (asprintf(&fpath, "%s/%s/%s", *p, rcnd[i], de->d_name) < 0) {
+ if (asprintf(&fpath, "%s/%s/%s", *p, rcnd_table[i], de->d_name) < 0) {
r = -ENOMEM;
goto finish;
}
if ((r = manager_load_unit(m, name, &service)) < 0)
goto finish;
- if ((r = manager_load_unit(m, rcnd[i+1], &runlevel)) < 0)
+ if ((r = manager_load_unit(m, rcnd_table[i+1], &runlevel)) < 0)
goto finish;
if (de->d_name[0] == 'S') {
if ((r = unit_add_dependency(runlevel, UNIT_AFTER, service)) < 0)
goto finish;
- } else {
+
+ } else if (de->d_name[0] == 'K' &&
+ (streq(rcnd_table[i+1], SPECIAL_RUNLEVEL0_TARGET) ||
+ streq(rcnd_table[i+1], SPECIAL_RUNLEVEL6_TARGET))) {
+
+ /* We honour K links only for
+ * halt/reboot. For the normal
+ * runlevels we assume the
+ * stop jobs will be
+ * implicitly added by the
+ * core logic. */
+
if ((r = unit_add_dependency(runlevel, UNIT_CONFLICTS, service)) < 0)
goto finish;
pid_t main_pid, control_pid;
bool main_pid_known:1;
+ bool sysv_has_lsb:1;
+
bool failure:1; /* if we shut down, remember why */
Watch timer_watch;
#include "unit.h"
#include "socket.h"
#include "log.h"
+#include "load-dropin.h"
+#include "load-fragment.h"
static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = {
[SOCKET_DEAD] = UNIT_INACTIVE,
unit_unwatch_timer(u, &s->timer_watch);
}
-static int socket_init(Unit *u) {
+static int socket_init(Unit *u, UnitLoadState *new_state) {
Socket *s = SOCKET(u);
char *t;
int r;
s->socket_mode = 0666;
exec_context_init(&s->exec_context);
- if ((r = unit_load_fragment_and_dropin(u)) <= 0) {
- if (r == 0)
- r = -ENOENT;
- goto fail;
- }
+ if ((r = unit_load_fragment(u, new_state)) < 0)
+ return r;
- if (!(t = unit_name_change_suffix(unit_id(u), ".service"))) {
- r = -ENOMEM;
- goto fail;
- }
+ if (*new_state == UNIT_STUB)
+ return -ENOENT;
- r = manager_load_unit(u->meta.manager, t, (Unit**) &s->service);
- free(t);
+ if ((r = unit_load_dropin(unit_follow_merge(u))) < 0)
+ return r;
- if (r < 0)
- goto fail;
+ /* This is a new unit? Then let's add in some extras */
+ if (*new_state == UNIT_LOADED) {
- if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(s->service))) < 0)
- goto fail;
+ if (!(t = unit_name_change_suffix(unit_id(u), ".service")))
+ return -ENOMEM;
- /* Add default cgroup */
- if ((r = unit_add_default_cgroup(u)) < 0)
- goto fail;
+ r = manager_load_unit(u->meta.manager, t, (Unit**) &s->service);
+ free(t);
- return 0;
+ if (r < 0)
+ return r;
-fail:
- socket_done(u);
- return r;
+ if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(s->service))) < 0)
+ return r;
+
+ if ((r = unit_add_exec_dependencies(u, &s->exec_context)) < 0)
+ return r;
+
+ if ((r = unit_add_default_cgroup(u)) < 0)
+ return r;
+ }
+
+ return 0;
}
static const char* listen_lookup(int type) {
***/
#include <errno.h>
+#include <signal.h>
#include "unit.h"
#include "target.h"
[TARGET_ACTIVE] = "active"
};
-static int target_init(Unit *u) {
- int r;
- assert(u);
-
- /* Make sure this config file actually exists */
-
- if ((r = unit_load_fragment_and_dropin(u)) <= 0)
- return r < 0 ? r : -ENOENT;
-
- return 0;
-}
-
static void target_dump(Unit *u, FILE *f, const char *prefix) {
Target *t = TARGET(u);
const UnitVTable target_vtable = {
.suffix = ".target",
- .init = target_init,
+ .init = unit_load_fragment_and_dropin,
.dump = target_dump,
assert(t);
}
-static int timer_init(Unit *u) {
- int r;
-
- assert(u);
-
- /* Make sure this config file actually exists */
-
- if ((r = unit_load_fragment_and_dropin(u)) <= 0)
- return r < 0 ? r : -ENOENT;
-
- return 0;
-}
-
static UnitActiveState timer_active_state(Unit *u) {
static const UnitActiveState table[_TIMER_STATE_MAX] = {
const UnitVTable timer_vtable = {
.suffix = ".timer",
- .init = timer_init,
+ .init = unit_load_fragment_and_dropin,
.done = timer_done,
.active_state = timer_active_state
u->meta.in_load_queue = true;
}
+void unit_add_to_cleanup_queue(Unit *u) {
+ assert(u);
+
+ if (u->meta.in_cleanup_queue)
+ return;
+
+ LIST_PREPEND(Meta, cleanup_queue, u->meta.manager->cleanup_queue, &u->meta);
+ u->meta.in_cleanup_queue = true;
+}
+
void unit_add_to_dbus_queue(Unit *u) {
assert(u);
if (u->meta.in_dbus_queue)
LIST_REMOVE(Meta, dbus_queue, u->meta.manager->dbus_unit_queue, &u->meta);
- if (u->meta.load_state == UNIT_LOADED)
+ if (u->meta.in_cleanup_queue)
+ LIST_REMOVE(Meta, cleanup_queue, u->meta.manager->cleanup_queue, &u->meta);
+
+ if (u->meta.load_state != UNIT_STUB)
if (UNIT_VTABLE(u)->done)
UNIT_VTABLE(u)->done(u);
return UNIT_VTABLE(u)->active_state(u);
}
-static int ensure_merge(Set **s, Set *other) {
+static void complete_move(Set **s, Set **other) {
+ assert(s);
+ assert(other);
- if (!other)
- return 0;
+ if (!*other)
+ return;
if (*s)
- return set_merge(*s, other);
+ set_move(*s, *other);
+ else {
+ *s = *other;
+ *other = NULL;
+ }
+}
- if (!(*s = set_copy(other)))
- return -ENOMEM;
+static void merge_names(Unit *u, Unit *other) {
+ char *t;
+ Iterator i;
- return 0;
+ assert(u);
+ assert(other);
+
+ complete_move(&u->meta.names, &other->meta.names);
+
+ while ((t = set_steal_first(other->meta.names)))
+ free(t);
+
+ set_free(other->meta.names);
+ other->meta.names = NULL;
+ other->meta.id = NULL;
+
+ SET_FOREACH(t, u->meta.names, i)
+ assert_se(hashmap_replace(u->meta.manager->units, t, u) == 0);
}
-/* FIXME: Does not rollback on failure! Needs to fix special unit
- * pointers. Needs to merge names and dependencies properly.*/
-int unit_merge(Unit *u, Unit *other) {
+static void merge_dependencies(Unit *u, Unit *other, UnitDependency d) {
+ Iterator i;
+ Unit *back;
int r;
+
+ assert(u);
+ assert(other);
+ assert(d < _UNIT_DEPENDENCY_MAX);
+
+ SET_FOREACH(back, other->meta.dependencies[d], i) {
+ UnitDependency k;
+
+ for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++)
+ if ((r = set_remove_and_put(back->meta.dependencies[k], other, u)) < 0) {
+
+ if (r == -EEXIST)
+ set_remove(back->meta.dependencies[k], other);
+ else
+ assert(r == -ENOENT);
+ }
+ }
+
+ complete_move(&u->meta.dependencies[d], &other->meta.dependencies[d]);
+
+ set_free(other->meta.dependencies[d]);
+ other->meta.dependencies[d] = NULL;
+}
+
+int unit_merge(Unit *u, Unit *other) {
UnitDependency d;
assert(u);
assert(other);
assert(u->meta.manager == other->meta.manager);
+ if (other == u)
+ return 0;
+
/* This merges 'other' into 'unit'. FIXME: This does not
* rollback on failure. */
if (u->meta.type != u->meta.type)
return -EINVAL;
- if (u->meta.load_state != UNIT_STUB)
- return -EINVAL;
+ if (other->meta.load_state != UNIT_STUB)
+ return -EEXIST;
/* Merge names */
- if ((r = ensure_merge(&u->meta.names, other->meta.names)) < 0)
- return r;
+ merge_names(u, other);
/* Merge dependencies */
for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++)
- /* fixme, the inverse mapping is missing */
- if ((r = ensure_merge(&u->meta.dependencies[d], other->meta.dependencies[d])) < 0)
- return r;
+ merge_dependencies(u, other, d);
unit_add_to_dbus_queue(u);
+ other->meta.load_state = UNIT_MERGED;
+ other->meta.merged_into = u;
+
+ unit_add_to_cleanup_queue(other);
+
+ return 0;
+}
+
+int unit_merge_by_name(Unit *u, const char *name) {
+ Unit *other;
+
+ assert(u);
+ assert(name);
+
+ if (!(other = manager_get_unit(u->meta.manager, name)))
+ return unit_add_name(u, name);
+
+ return unit_merge(u, other);
+}
+
+Unit* unit_follow_merge(Unit *u) {
+ assert(u);
+
+ while (u->meta.load_state == UNIT_MERGED)
+ assert_se(u = u->meta.merged_into);
+
+ return u;
+}
+
+int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
+ int r;
+
+ assert(u);
+ assert(c);
+
+ if (c->output != EXEC_OUTPUT_KERNEL && c->output != EXEC_OUTPUT_SYSLOG)
+ return 0;
+
+ /* If syslog or kernel logging is requested, make sure our own
+ * logging daemon is run first. */
+
+ if ((r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_LOGGER_SOCKET)) < 0)
+ return r;
+
+ if (u->meta.manager->running_as != MANAGER_SESSION)
+ if ((r = unit_add_dependency_by_name(u, UNIT_REQUIRES, SPECIAL_LOGGER_SOCKET)) < 0)
+ return r;
+
return 0;
}
"%s→ Unit %s:\n"
"%s\tDescription: %s\n"
"%s\tUnit Load State: %s\n"
- "%s\tUnit Active State: %s\n"
- "%s\tRecursive Stop: %s\n"
- "%s\tStop When Unneeded: %s\n",
+ "%s\tUnit Active State: %s\n",
prefix, unit_id(u),
prefix, unit_description(u),
prefix, unit_load_state_to_string(u->meta.load_state),
- prefix, unit_active_state_to_string(unit_active_state(u)),
- prefix, yes_no(u->meta.recursive_stop),
- prefix, yes_no(u->meta.stop_when_unneeded));
-
- if (u->meta.fragment_path)
- fprintf(f, "%s\tFragment Path: %s\n", prefix, u->meta.fragment_path);
+ prefix, unit_active_state_to_string(unit_active_state(u)));
SET_FOREACH(t, u->meta.names, i)
fprintf(f, "%s\tName: %s\n", prefix, t);
+ if (u->meta.fragment_path)
+ fprintf(f, "%s\tFragment Path: %s\n", prefix, u->meta.fragment_path);
+
for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) {
Unit *other;
fprintf(f, "%s\t%s: %s\n", prefix, unit_dependency_to_string(d), unit_id(other));
}
- LIST_FOREACH(by_unit, b, u->meta.cgroup_bondings)
- fprintf(f, "%s\tControlGroup: %s:%s\n",
- prefix, b->controller, b->path);
+ fprintf(f,
+ "%s\tRecursive Stop: %s\n"
+ "%s\tStop When Unneeded: %s\n",
+ prefix, yes_no(u->meta.recursive_stop),
+ prefix, yes_no(u->meta.stop_when_unneeded));
+
+ if (u->meta.load_state == UNIT_LOADED) {
+ LIST_FOREACH(by_unit, b, u->meta.cgroup_bondings)
+ fprintf(f, "%s\tControlGroup: %s:%s\n",
+ prefix, b->controller, b->path);
- if (UNIT_VTABLE(u)->dump)
- UNIT_VTABLE(u)->dump(u, f, prefix2);
+ if (UNIT_VTABLE(u)->dump)
+ UNIT_VTABLE(u)->dump(u, f, prefix2);
+ }
if (u->meta.job)
job_dump(u->meta.job, f, prefix2);
}
/* Common implementation for multiple backends */
-int unit_load_fragment_and_dropin(Unit *u) {
- int r, ret;
+int unit_load_fragment_and_dropin(Unit *u, UnitLoadState *new_state) {
+ int r;
+
+ assert(u);
+ assert(new_state);
+ assert(*new_state == UNIT_STUB || *new_state == UNIT_LOADED);
+
+ /* Load a .service file */
+ if ((r = unit_load_fragment(u, new_state)) < 0)
+ return r;
+
+ if (*new_state == UNIT_STUB)
+ return -ENOENT;
+
+ /* Load drop-in directory data */
+ if ((r = unit_load_dropin(unit_follow_merge(u))) < 0)
+ return r;
+
+ return 0;
+}
+
+/* Common implementation for multiple backends */
+int unit_load_fragment_and_dropin_optional(Unit *u, UnitLoadState *new_state) {
+ int r;
assert(u);
+ assert(new_state);
+ assert(*new_state == UNIT_STUB || *new_state == UNIT_LOADED);
- /* Load a .socket file */
- if ((r = unit_load_fragment(u)) < 0)
+ /* Same as unit_load_fragment_and_dropin(), but whether
+ * something can be loaded or not doesn't matter. */
+
+ /* Load a .service file */
+ if ((r = unit_load_fragment(u, new_state)) < 0)
return r;
- ret = r > 0;
+ if (*new_state == UNIT_STUB)
+ *new_state = UNIT_LOADED;
/* Load drop-in directory data */
- if ((r = unit_load_dropin(u)) < 0)
+ if ((r = unit_load_dropin(unit_follow_merge(u))) < 0)
return r;
- return ret;
+ return 0;
}
int unit_load(Unit *u) {
int r;
+ UnitLoadState res;
assert(u);
if (u->meta.load_state != UNIT_STUB)
return 0;
- if (UNIT_VTABLE(u)->init)
- if ((r = UNIT_VTABLE(u)->init(u)) < 0)
+ if (UNIT_VTABLE(u)->init) {
+ res = UNIT_STUB;
+ if ((r = UNIT_VTABLE(u)->init(u, &res)) < 0)
goto fail;
+ }
+
+ if (res == UNIT_STUB) {
+ r = -ENOENT;
+ goto fail;
+ }
+
+ u->meta.load_state = res;
+ assert((u->meta.load_state != UNIT_MERGED) == !u->meta.merged_into);
+
+ unit_add_to_dbus_queue(unit_follow_merge(u));
- u->meta.load_state = UNIT_LOADED;
- unit_add_to_dbus_queue(u);
return 0;
fail:
u->meta.load_state = UNIT_FAILED;
unit_add_to_dbus_queue(u);
+
+ log_error("Failed to load configuration for %s: %s", unit_id(u), strerror(-r));
+
return r;
}
static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
[UNIT_STUB] = "stub",
[UNIT_LOADED] = "loaded",
- [UNIT_FAILED] = "failed"
+ [UNIT_FAILED] = "failed",
+ [UNIT_MERGED] = "merged"
};
DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
UNIT_STUB,
UNIT_LOADED,
UNIT_FAILED,
+ UNIT_MERGED,
_UNIT_LOAD_STATE_MAX,
_UNIT_LOAD_STATE_INVALID = -1
};
struct Meta {
Manager *manager;
+
UnitType type;
UnitLoadState load_state;
+ Unit *merged_into;
char *id; /* One name is special because we use it for identification. Points to an entry in the names set */
bool in_load_queue:1;
bool in_dbus_queue:1;
+ bool in_cleanup_queue:1;
bool sent_dbus_new_signal:1;
/* If we go down, pull down everything that depends on us, too */
/* D-Bus queue */
LIST_FIELDS(Meta, dbus_queue);
+
+ /* Cleanup queue */
+ LIST_FIELDS(Meta, cleanup_queue);
};
#include "service.h"
struct UnitVTable {
const char *suffix;
- int (*init)(Unit *u);
+ int (*init)(Unit *u, UnitLoadState *new_state);
void (*done)(Unit *u);
int (*coldplug)(Unit *u);
int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name);
int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name);
+int unit_add_exec_dependencies(Unit *u, ExecContext *c);
+
int unit_add_cgroup(Unit *u, CGroupBonding *b);
int unit_add_cgroup_from_text(Unit *u, const char *name);
int unit_add_default_cgroup(Unit *u);
void unit_add_to_load_queue(Unit *u);
void unit_add_to_dbus_queue(Unit *u);
+void unit_add_to_cleanup_queue(Unit *u);
int unit_merge(Unit *u, Unit *other);
+int unit_merge_by_name(Unit *u, const char *other);
+
+Unit *unit_follow_merge(Unit *u);
-int unit_load_fragment_and_dropin(Unit *u);
+int unit_load_fragment_and_dropin(Unit *u, UnitLoadState *new_state);
+int unit_load_fragment_and_dropin_optional(Unit *u, UnitLoadState *new_state);
int unit_load(Unit *unit);
const char* unit_id(Unit *u);