X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=unit.c;h=93c0d8d61a29bb442550d419e2331cbb5afb12c1;hp=bdbcb45c506fcce282696e3926ade9e6ceee2e9a;hb=f170852aea19408cfefcd1b9df4aa8942dbbca98;hpb=a7334b0952ab66c17ee787e36e6d2c5ceb387de6 diff --git a/unit.c b/unit.c index bdbcb45c5..93c0d8d61 100644 --- a/unit.c +++ b/unit.c @@ -131,6 +131,13 @@ Unit *unit_new(Manager *m) { return u; } +bool unit_has_name(Unit *u, const char *name) { + assert(u); + assert(name); + + return !!set_get(u->meta.names, (char*) name); +} + int unit_add_name(Unit *u, const char *text) { UnitType t; char *s; @@ -174,6 +181,7 @@ int unit_add_name(Unit *u, const char *text) { if (!u->meta.id) u->meta.id = s; + unit_add_to_dbus_queue(u); return 0; } @@ -189,6 +197,8 @@ int unit_choose_id(Unit *u, const char *name) { return -ENOENT; u->meta.id = s; + + unit_add_to_dbus_queue(u); return 0; } @@ -202,6 +212,8 @@ int unit_set_description(Unit *u, const char *description) { free(u->meta.description); u->meta.description = s; + + unit_add_to_dbus_queue(u); return 0; } @@ -215,6 +227,26 @@ 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); + + if (u->meta.load_state == UNIT_STUB || u->meta.in_dbus_queue || set_isempty(u->meta.manager->subscribed)) + return; + + LIST_PREPEND(Meta, dbus_queue, u->meta.manager->dbus_unit_queue, &u->meta); + u->meta.in_dbus_queue = true; +} + static void bidi_set_free(Unit *u, Set *s) { Iterator i; Unit *other; @@ -241,8 +273,12 @@ void unit_free(Unit *u) { assert(u); + bus_unit_send_removed_signal(u); + /* Detach from next 'bigger' objects */ + cgroup_bonding_free_list(u->meta.cgroup_bondings); + SET_FOREACH(t, u->meta.names, i) hashmap_remove_value(u->meta.manager->units, t, u); @@ -252,7 +288,13 @@ void unit_free(Unit *u) { if (u->meta.in_load_queue) LIST_REMOVE(Meta, load_queue, u->meta.manager->load_queue, &u->meta); - if (u->meta.load_state == UNIT_LOADED) + if (u->meta.in_dbus_queue) + LIST_REMOVE(Meta, dbus_queue, u->meta.manager->dbus_unit_queue, &u->meta); + + 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); @@ -264,7 +306,7 @@ void unit_free(Unit *u) { bidi_set_free(u, u->meta.dependencies[d]); free(u->meta.description); - free(u->meta.load_path); + free(u->meta.fragment_path); while ((t = set_steal_first(u->meta.names))) free(t); @@ -282,47 +324,143 @@ 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); + 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) + 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; @@ -347,40 +485,36 @@ const char *unit_description(Unit *u) { } void unit_dump(Unit *u, FILE *f, const char *prefix) { - char *t; UnitDependency d; Iterator i; - char *prefix2; + char *p2; + const char *prefix2; + CGroupBonding *b; assert(u); if (!prefix) prefix = ""; - prefix2 = strappend(prefix, "\t"); - if (!prefix2) - prefix2 = ""; + p2 = strappend(prefix, "\t"); + prefix2 = p2 ? p2 : prefix; fprintf(f, "%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.load_path) - fprintf(f, "%s\tLoad Path: %s\n", prefix, u->meta.load_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; @@ -391,36 +525,77 @@ 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)); } - if (UNIT_VTABLE(u)->dump) - UNIT_VTABLE(u)->dump(u, f, prefix2); + 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 (u->meta.job) job_dump(u->meta.job, f, prefix2); - free(prefix2); + free(p2); } /* 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); @@ -432,15 +607,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; 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; } @@ -454,19 +644,25 @@ int unit_start(Unit *u) { assert(u); - if (!UNIT_VTABLE(u)->start) - return -EBADR; - + /* If this is already (being) started, then this will + * succeed. Note that this will even succeed if this unit is + * not startable by the user. This is relied on to detect when + * we need to wait for units and when waiting is finished. */ state = unit_active_state(u); if (UNIT_IS_ACTIVE_OR_RELOADING(state)) return -EALREADY; + /* If it is stopped, but we cannot start it, then fail */ + if (!UNIT_VTABLE(u)->start) + return -EBADR; + /* We don't suppress calls to ->start() here when we are * already starting, to allow this request to be used as a * "hurry up" call, for example when the unit is in some "auto * restart" state where it waits for a holdoff timer to elapse * before it will start again. */ + unit_add_to_dbus_queue(u); return UNIT_VTABLE(u)->start(u); } @@ -486,16 +682,17 @@ int unit_stop(Unit *u) { assert(u); - if (!UNIT_VTABLE(u)->stop) - return -EBADR; - state = unit_active_state(u); if (state == UNIT_INACTIVE) return -EALREADY; + if (!UNIT_VTABLE(u)->stop) + return -EBADR; + if (state == UNIT_DEACTIVATING) return 0; + unit_add_to_dbus_queue(u); return UNIT_VTABLE(u)->stop(u); } @@ -519,6 +716,7 @@ int unit_reload(Unit *u) { if (unit_active_state(u) != UNIT_ACTIVE) return -ENOEXEC; + unit_add_to_dbus_queue(u); return UNIT_VTABLE(u)->reload(u); } @@ -649,7 +847,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) { /* So we reached a different state for this * job. Let's see if we can run it now if it * failed previously due to EAGAIN. */ - job_schedule_run(u->meta.job); + job_add_to_run_queue(u->meta.job); else { assert(u->meta.job->state == JOB_RUNNING); @@ -715,9 +913,33 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) { else if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) retroactively_stop_dependencies(u); + if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) { + + if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) { + /* The bus just got started, hence try to connect to it. */ + bus_init_system(u->meta.manager); + bus_init_api(u->meta.manager); + } + + if (unit_has_name(u, SPECIAL_SYSLOG_SERVICE)) + /* The syslog daemon just got started, hence try to connect to it. */ + log_open_syslog(); + + } else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) { + + if (unit_has_name(u, SPECIAL_SYSLOG_SERVICE)) + /* The syslog daemon just got terminated, hence try to disconnect from it. */ + log_close_syslog(); + + /* We don't care about D-Bus here, since we'll get an + * asynchronous notification for it anyway. */ + } + /* Maybe we finished startup and are now ready for being * stopped because unneeded? */ unit_check_uneeded(u); + + unit_add_to_dbus_queue(u); } int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w) { @@ -921,6 +1143,7 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other) { return r; } + unit_add_to_dbus_queue(u); return 0; } @@ -937,14 +1160,17 @@ int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name) { return 0; } -const char *unit_path(void) { - char *e; +int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name) { + Unit *other; + int r; - if ((e = getenv("UNIT_PATH"))) - if (path_is_absolute(e)) - return e; + if ((r = manager_load_unit(u->meta.manager, name, &other)) < 0) + return r; - return UNIT_PATH; + if ((r = unit_add_dependency(other, d, u)) < 0) + return r; + + return 0; } int set_unit_path(const char *p) { @@ -967,7 +1193,7 @@ int set_unit_path(const char *p) { return -ENOMEM; } - if (setenv("UNIT_PATH", c, 0) < 0) { + if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) { r = -errno; free(c); return r; @@ -976,10 +1202,10 @@ int set_unit_path(const char *p) { return 0; } -char *unit_name_escape_path(const char *prefix, const char *path, const char *suffix) { +char *unit_name_escape_path(const char *path, const char *suffix) { char *r, *t; const char *f; - size_t a, b, c; + size_t a, b; assert(path); @@ -992,22 +1218,16 @@ char *unit_name_escape_path(const char *prefix, const char *path, const char *su * escaping is hence reversible. */ - if (!prefix) - prefix = ""; - if (!suffix) suffix = ""; - a = strlen(prefix); - b = strlen(path); - c = strlen(suffix); + a = strlen(path); + b = strlen(suffix); - if (!(r = new(char, a+b*4+c+1))) + if (!(r = new(char, a*4+b+1))) return NULL; - memcpy(r, prefix, a); - - for (f = path, t = r+a; *f; f++) { + for (f = path, t = r; *f; f++) { if (*f == '/') *(t++) = '.'; else if (*f == '.' || *f == '\\' || !strchr(VALID_CHARS, *f)) { @@ -1019,7 +1239,7 @@ char *unit_name_escape_path(const char *prefix, const char *path, const char *su *(t++) = *f; } - memcpy(t, suffix, c+1); + memcpy(t, suffix, b+1); return r; } @@ -1041,6 +1261,138 @@ char *unit_dbus_path(Unit *u) { return p; } +int unit_add_cgroup(Unit *u, CGroupBonding *b) { + CGroupBonding *l; + int r; + + assert(u); + assert(b); + assert(b->path); + + /* Ensure this hasn't been added yet */ + assert(!b->unit); + + l = hashmap_get(u->meta.manager->cgroup_bondings, b->path); + LIST_PREPEND(CGroupBonding, by_path, l, b); + + if ((r = hashmap_replace(u->meta.manager->cgroup_bondings, b->path, l)) < 0) { + LIST_REMOVE(CGroupBonding, by_path, l, b); + return r; + } + + LIST_PREPEND(CGroupBonding, by_unit, u->meta.cgroup_bondings, b); + b->unit = u; + + return 0; +} + +int unit_add_cgroup_from_text(Unit *u, const char *name) { + size_t n; + const char *p; + char *controller; + CGroupBonding *b; + int r; + + assert(u); + assert(name); + + /* Detect controller name */ + n = strcspn(name, ":/"); + + /* Only controller name, no path? No path? */ + if (name[n] == 0) + return -EINVAL; + + if (n > 0) { + if (name[n] != ':') + return -EINVAL; + + p = name+n+1; + } else + p = name; + + /* Insist in absolute paths */ + if (p[0] != '/') + return -EINVAL; + + if (!(controller = strndup(name, n))) + return -ENOMEM; + + if (cgroup_bonding_find_list(u->meta.cgroup_bondings, controller)) { + free(controller); + return -EEXIST; + } + + if (!(b = new0(CGroupBonding, 1))) { + free(controller); + return -ENOMEM; + } + + b->controller = controller; + + if (!(b->path = strdup(p))) { + r = -ENOMEM; + goto fail; + } + + b->only_us = false; + b->clean_up = false; + + if ((r = unit_add_cgroup(u, b)) < 0) + goto fail; + + return 0; + +fail: + free(b->path); + free(b->controller); + free(b); + + return r; +} + +int unit_add_default_cgroup(Unit *u) { + CGroupBonding *b; + int r = -ENOMEM; + + assert(u); + + /* Adds in the default cgroup data, if it wasn't specified yet */ + + if (unit_get_default_cgroup(u)) + return 0; + + if (!(b = new0(CGroupBonding, 1))) + return -ENOMEM; + + if (!(b->controller = strdup(u->meta.manager->cgroup_controller))) + goto fail; + + if (asprintf(&b->path, "%s/%s", u->meta.manager->cgroup_hierarchy, unit_id(u)) < 0) + goto fail; + + b->clean_up = true; + b->only_us = true; + + if ((r = unit_add_cgroup(u, b)) < 0) + goto fail; + + return 0; + +fail: + free(b->path); + free(b->controller); + free(b); + + return r; +} + +CGroupBonding* unit_get_default_cgroup(Unit *u) { + assert(u); + + return cgroup_bonding_find_list(u->meta.cgroup_bondings, u->meta.manager->cgroup_controller); +} + static const char* const unit_type_table[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = "service", [UNIT_TIMER] = "timer", @@ -1057,7 +1409,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);