diff --git a/automount.c b/automount.c index a45cd965055..c28472da769 100644 --- a/automount.c +++ b/automount.c @@ -26,7 +26,7 @@ #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); @@ -35,13 +35,28 @@ static int automount_init(Unit *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; } diff --git a/device.c b/device.c index 6de7d518c71..32995fd159a 100644 --- a/device.c +++ b/device.c @@ -381,7 +381,7 @@ fail: 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, diff --git a/job.c b/job.c index c4d2e7a11e5..6e04683fdd8 100644 --- a/job.c +++ b/job.c @@ -65,7 +65,7 @@ void job_free(Job *j) { } /* 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); diff --git a/load-fragment.c b/load-fragment.c index 2eb8279f402..c0972d04fb6 100644 --- a/load-fragment.c +++ b/load-fragment.c @@ -95,36 +95,15 @@ static int config_parse_names( 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; @@ -1070,7 +1049,48 @@ static int open_follow(char **filename, FILE **_f, Set *names, char **_id) { 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", @@ -1184,8 +1204,12 @@ static int load_from_path(Unit *u, const char *path) { 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]; @@ -1243,90 +1267,79 @@ static int load_from_path(Unit *u, const char *path) { } if (!filename) { - r = 0; /* returning 0 means: no suitable config file found */ + r = 0; + goto finish; + } + + merged = u; + if ((r = merge_by_names(&merged, symlink_names, id)) < 0) + goto finish; + + if (merged != u) { + *new_state = UNIT_MERGED; + r = 0; goto finish; } /* Now, parse the file contents */ - r = config_parse(filename, f, sections, items, u); - if (r < 0) + if ((r = config_parse(filename, f, sections, items, u)) < 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); - } - - 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) - r = load_from_path(u, u->meta.fragment_path); - else { + if (u->meta.fragment_path) { + + if ((r = load_from_path(u, u->meta.fragment_path, new_state)) < 0) + return r; + + } 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) + if (*new_state == UNIT_STUB) + SET_FOREACH(t, u->meta.names, i) { + + if (unit_id(u) == t) + continue; + + if ((r = load_from_path(u, t, new_state)) < 0) + return r; + + if (*new_state != UNIT_STUB) break; + } } - if (r >= 0) { - ExecContext *c; - - 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 (u->meta.manager->running_as != MANAGER_SESSION) - if ((k = unit_add_dependency_by_name(u, UNIT_REQUIRES, SPECIAL_LOGGER_SOCKET)) < 0) - return k; - } - } - - return r; + return 0; } diff --git a/load-fragment.h b/load-fragment.h index cc7f6c590e0..709859522d0 100644 --- a/load-fragment.h +++ b/load-fragment.h @@ -26,6 +26,6 @@ /* 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 diff --git a/manager.c b/manager.c index e9bd3669041..42059a50071 100644 --- a/manager.c +++ b/manager.c @@ -194,6 +194,7 @@ fail: 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 @@ -326,6 +327,22 @@ fail: 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; @@ -339,6 +356,8 @@ void manager_free(Manager *m) { 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); @@ -402,13 +421,13 @@ int manager_coldplug(Manager *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); @@ -421,7 +440,7 @@ static void transaction_delete_unit(Manager *m, Unit *u) { * 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) { @@ -449,7 +468,7 @@ static void transaction_abort(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); @@ -541,7 +560,7 @@ static void transaction_merge_and_delete_job(Manager *m, Job *j, Job *other, Job /* 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) { @@ -574,8 +593,8 @@ 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; } @@ -643,6 +662,46 @@ static int transaction_merge_jobs(Manager *m) { 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); @@ -680,11 +739,11 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned * 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)) { @@ -772,7 +831,7 @@ static void transaction_collect_garbage(Manager *m) { 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; } @@ -847,7 +906,7 @@ static void transaction_minimize_impact(Manager *m) { /* 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; } @@ -932,12 +991,15 @@ static int transaction_activate(Manager *m, JobMode mode) { * 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; @@ -952,7 +1014,7 @@ static int transaction_activate(Manager *m, JobMode mode) { } 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) @@ -963,7 +1025,7 @@ static int transaction_activate(Manager *m, JobMode mode) { 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); @@ -971,14 +1033,17 @@ static int transaction_activate(Manager *m, JobMode mode) { * 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; @@ -1037,10 +1102,12 @@ static Job* transaction_add_one_job(Manager *m, JobType type, Unit *unit, bool f 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); @@ -1064,11 +1131,11 @@ void manager_transaction_unlink_job(Manager *m, Job *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); } } } @@ -1244,7 +1311,7 @@ int manager_load_unit(Manager *m, const char *path, Unit **_ret) { manager_dispatch_load_queue(m); - *_ret = ret; + *_ret = unit_follow_merge(ret); return 0; } @@ -1317,10 +1384,9 @@ unsigned manager_dispatch_dbus_queue(Manager *m) { 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++; } @@ -1521,7 +1587,7 @@ int manager_loop(Manager *m) { assert(m); - for (;;) { + do { struct epoll_event event; int n; @@ -1531,6 +1597,9 @@ int manager_loop(Manager *m) { sleep(1); } + if (manager_dispatch_cleanup_queue(m) > 0) + continue; + if (manager_dispatch_load_queue(m) > 0) continue; @@ -1555,10 +1624,9 @@ int manager_loop(Manager *m) { 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) { diff --git a/manager.h b/manager.h index a361748374a..312ed879e68 100644 --- a/manager.h +++ b/manager.h @@ -125,6 +125,8 @@ struct Manager { 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; @@ -181,7 +183,7 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool for 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); diff --git a/mount.c b/mount.c index 74094cc5149..325f2d51e56 100644 --- a/mount.c +++ b/mount.c @@ -472,7 +472,7 @@ void mount_fd_event(Manager *m, int events) { 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, diff --git a/service.c b/service.c index 53d5505c2ea..caef844056a 100644 --- a/service.c +++ b/service.c @@ -35,6 +35,17 @@ #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, @@ -128,8 +139,8 @@ static int sysv_chkconfig_order(Service *s) { 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; @@ -143,6 +154,9 @@ static int sysv_chkconfig_order(Service *s) { 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) @@ -200,7 +214,67 @@ static int sysv_exec_commands(Service *s) { 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; @@ -211,7 +285,10 @@ static int service_load_sysv_path(Service *s, const char *path) { LSB, LSB_DESCRIPTION } state = NORMAL; - bool has_lsb = false; + + assert(s); + assert(path); + assert(new_state); u = UNIT(s); @@ -249,7 +326,7 @@ static int service_load_sysv_path(Service *s, const char *path) { if (state == NORMAL && streq(t, "### BEGIN INIT INFO")) { state = LSB; - has_lsb = true; + s->sysv_has_lsb = true; continue; } @@ -470,14 +547,21 @@ static int service_load_sysv_path(Service *s, const char *path) { } } - /* 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; @@ -485,7 +569,8 @@ static int service_load_sysv_path(Service *s, const char *path) { (r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_SYSINIT_SERVICE)) < 0) goto finish; - r = 1; + *new_state = UNIT_LOADED; + r = 0; finish: @@ -495,7 +580,7 @@ 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); @@ -511,22 +596,26 @@ static int service_load_sysv_name(Service *s, const char *name) { 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 ... */ @@ -535,21 +624,28 @@ static int service_load_sysv(Service *s) { 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 */ @@ -570,31 +666,38 @@ static int service_init(Unit *u) { 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; @@ -645,14 +748,17 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { 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); } @@ -1638,17 +1744,6 @@ static void service_cgroup_notify_event(Unit *u) { } 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; @@ -1658,12 +1753,12 @@ static int service_enumerate(Manager *m) { 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; } @@ -1692,7 +1787,7 @@ static int service_enumerate(Manager *m) { 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; } @@ -1715,7 +1810,7 @@ static int service_enumerate(Manager *m) { 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') { @@ -1724,7 +1819,18 @@ static int service_enumerate(Manager *m) { 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; diff --git a/service.h b/service.h index 5ffdde19cb4..0a258a3774d 100644 --- a/service.h +++ b/service.h @@ -100,6 +100,8 @@ struct Service { 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; diff --git a/socket.c b/socket.c index 2a19e97b7bc..a1f3ef8af75 100644 --- a/socket.c +++ b/socket.c @@ -30,6 +30,8 @@ #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, @@ -92,7 +94,7 @@ static void socket_done(Unit *u) { 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; @@ -109,35 +111,38 @@ static int socket_init(Unit *u) { 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 (*new_state == UNIT_STUB) + return -ENOENT; + + 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 (!(t = unit_name_change_suffix(unit_id(u), ".service"))) + return -ENOMEM; + + r = manager_load_unit(u->meta.manager, t, (Unit**) &s->service); + free(t); + + if (r < 0) + 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; } - if (!(t = unit_name_change_suffix(unit_id(u), ".service"))) { - r = -ENOMEM; - goto fail; - } - - r = manager_load_unit(u->meta.manager, t, (Unit**) &s->service); - free(t); - - if (r < 0) - goto fail; - - if ((r = unit_add_dependency(u, UNIT_BEFORE, UNIT(s->service))) < 0) - goto fail; - - /* Add default cgroup */ - if ((r = unit_add_default_cgroup(u)) < 0) - goto fail; - return 0; - -fail: - socket_done(u); - return r; } static const char* listen_lookup(int type) { diff --git a/target.c b/target.c index b18e50fe5bd..b8a7442f1c4 100644 --- a/target.c +++ b/target.c @@ -20,6 +20,7 @@ ***/ #include +#include #include "unit.h" #include "target.h" @@ -36,18 +37,6 @@ static const char* const state_string_table[_TARGET_STATE_MAX] = { [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); @@ -100,7 +89,7 @@ static UnitActiveState target_active_state(Unit *u) { const UnitVTable target_vtable = { .suffix = ".target", - .init = target_init, + .init = unit_load_fragment_and_dropin, .dump = target_dump, diff --git a/timer.c b/timer.c index 65e74fe807e..89a8ab07371 100644 --- a/timer.c +++ b/timer.c @@ -30,19 +30,6 @@ static void timer_done(Unit *u) { 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] = { @@ -57,7 +44,7 @@ static UnitActiveState timer_active_state(Unit *u) { const UnitVTable timer_vtable = { .suffix = ".timer", - .init = timer_init, + .init = unit_load_fragment_and_dropin, .done = timer_done, .active_state = timer_active_state diff --git a/unit.c b/unit.c index 036c01648c6..10efd14c6a3 100644 --- a/unit.c +++ b/unit.c @@ -220,6 +220,16 @@ void unit_add_to_load_queue(Unit *u) { 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); @@ -274,7 +284,10 @@ void unit_free(Unit *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); @@ -304,51 +317,145 @@ UnitActiveState unit_active_state(Unit *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); - - if (!(*s = set_copy(other))) - return -ENOMEM; - - return 0; + set_move(*s, *other); + else { + *s = *other; + *other = NULL; + } } -/* 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_names(Unit *u, Unit *other) { + char *t; + Iterator i; + + 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); +} + +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; } @@ -389,22 +496,18 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%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; @@ -415,12 +518,20 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { 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 (UNIT_VTABLE(u)->dump) - UNIT_VTABLE(u)->dump(u, f, prefix2); + 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 (u->meta.job) job_dump(u->meta.job, f, prefix2); @@ -429,26 +540,55 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { } /* 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 .socket file */ - if ((r = unit_load_fragment(u)) < 0) + /* Load a .service file */ + if ((r = unit_load_fragment(u, new_state)) < 0) return r; - ret = r > 0; + if (*new_state == UNIT_STUB) + return -ENOENT; /* 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; +} + +/* 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); + + /* 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; + + if (*new_state == UNIT_STUB) + *new_state = UNIT_LOADED; + + /* Load drop-in directory data */ + if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) + return r; + + return 0; } int unit_load(Unit *u) { int r; + UnitLoadState res; assert(u); @@ -460,17 +600,30 @@ int unit_load(Unit *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; } @@ -1227,7 +1380,8 @@ DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); 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); diff --git a/unit.h b/unit.h index df8f2a208a6..b05efe016b7 100644 --- a/unit.h +++ b/unit.h @@ -60,6 +60,7 @@ enum UnitLoadState { UNIT_STUB, UNIT_LOADED, UNIT_FAILED, + UNIT_MERGED, _UNIT_LOAD_STATE_MAX, _UNIT_LOAD_STATE_INVALID = -1 }; @@ -116,8 +117,10 @@ enum UnitDependency { 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 */ @@ -133,6 +136,7 @@ struct Meta { 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 */ @@ -155,6 +159,9 @@ struct Meta { /* D-Bus queue */ LIST_FIELDS(Meta, dbus_queue); + + /* Cleanup queue */ + LIST_FIELDS(Meta, cleanup_queue); }; #include "service.h" @@ -181,7 +188,7 @@ union Unit { struct UnitVTable { const char *suffix; - int (*init)(Unit *u); + int (*init)(Unit *u, UnitLoadState *new_state); void (*done)(Unit *u); int (*coldplug)(Unit *u); @@ -251,6 +258,8 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other); 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); @@ -261,10 +270,15 @@ int unit_set_description(Unit *u, const char *description); 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); -int unit_load_fragment_and_dropin(Unit *u); +Unit *unit_follow_merge(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);