X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fcore%2Funit.c;h=b5c3182940505a0831e8826d54f74c5ce495e5b2;hp=b0bb0260bcbec7f3f5a9ff1f80bee4a3e4d058c4;hb=06b643e7f5a3b79005dd57497897ab7255fe3659;hpb=5ba6985b6c8ef85a8bcfeb1b65239c863436e75b diff --git a/src/core/unit.c b/src/core/unit.c index b0bb0260b..b5c318294 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -51,6 +51,7 @@ #include "dbus.h" #include "execute.h" #include "virt.h" +#include "dropin.h" const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = &service_vtable, @@ -68,6 +69,8 @@ const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_SCOPE] = &scope_vtable }; +static int maybe_warn_about_dependency(const char *id, const char *other, UnitDependency dependency); + Unit *unit_new(Manager *m, size_t size) { Unit *u; @@ -101,104 +104,122 @@ bool unit_has_name(Unit *u, const char *name) { return !!set_get(u->names, (char*) name); } +static void unit_init(Unit *u) { + CGroupContext *cc; + ExecContext *ec; + KillContext *kc; + + assert(u); + assert(u->manager); + assert(u->type >= 0); + + cc = unit_get_cgroup_context(u); + if (cc) { + cgroup_context_init(cc); + + /* Copy in the manager defaults into the cgroup + * context, _before_ the rest of the settings have + * been initialized */ + + cc->cpu_accounting = u->manager->default_cpu_accounting; + cc->blockio_accounting = u->manager->default_blockio_accounting; + cc->memory_accounting = u->manager->default_memory_accounting; + } + + ec = unit_get_exec_context(u); + if (ec) + exec_context_init(ec); + + kc = unit_get_kill_context(u); + if (kc) + kill_context_init(kc); + + if (UNIT_VTABLE(u)->init) + UNIT_VTABLE(u)->init(u); +} + int unit_add_name(Unit *u, const char *text) { + _cleanup_free_ char *s = NULL, *i = NULL; UnitType t; - char *s, *i = NULL; int r; assert(u); assert(text); if (unit_name_is_template(text)) { + if (!u->instance) return -EINVAL; s = unit_name_replace_instance(text, u->instance); } else s = strdup(text); - if (!s) return -ENOMEM; - if (!unit_name_is_valid(s, TEMPLATE_INVALID)) { - r = -EINVAL; - goto fail; - } + if (!unit_name_is_valid(s, TEMPLATE_INVALID)) + return -EINVAL; assert_se((t = unit_name_to_type(s)) >= 0); - if (u->type != _UNIT_TYPE_INVALID && t != u->type) { - r = -EINVAL; - goto fail; - } + if (u->type != _UNIT_TYPE_INVALID && t != u->type) + return -EINVAL; r = unit_name_to_instance(s, &i); if (r < 0) - goto fail; + return r; - if (i && unit_vtable[t]->no_instances) { - r = -EINVAL; - goto fail; - } + if (i && unit_vtable[t]->no_instances) + return -EINVAL; /* Ensure that this unit is either instanced or not instanced, * but not both. */ - if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) { - r = -EINVAL; - goto fail; - } + if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) + return -EINVAL; if (unit_vtable[t]->no_alias && !set_isempty(u->names) && - !set_get(u->names, s)) { - r = -EEXIST; - goto fail; - } + !set_get(u->names, s)) + return -EEXIST; - if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) { - r = -E2BIG; - goto fail; - } + if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) + return -E2BIG; r = set_put(u->names, s); if (r < 0) { if (r == -EEXIST) - r = 0; - goto fail; + return 0; + + return r; } r = hashmap_put(u->manager->units, s, u); if (r < 0) { set_remove(u->names, s); - goto fail; + return r; } if (u->type == _UNIT_TYPE_INVALID) { - u->type = t; u->id = s; u->instance = i; LIST_PREPEND(units_by_type, u->manager->units_by_type[t], u); - if (UNIT_VTABLE(u)->init) - UNIT_VTABLE(u)->init(u); - } else - free(i); + unit_init(u); - unit_add_to_dbus_queue(u); - return 0; + i = NULL; + } -fail: - free(s); - free(i); + s = NULL; - return r; + unit_add_to_dbus_queue(u); + return 0; } int unit_choose_id(Unit *u, const char *name) { - char *s, *i; _cleanup_free_ char *t = NULL; + char *s, *i; int r; assert(u); @@ -218,7 +239,6 @@ int unit_choose_id(Unit *u, const char *name) { /* Selects one of the names of this unit as the id */ s = set_get(u->names, (char*) name); - if (!s) return -ENOENT; @@ -259,9 +279,6 @@ int unit_set_description(Unit *u, const char *description) { bool unit_check_gc(Unit *u) { assert(u); - if (u->load_state == UNIT_STUB) - return true; - if (UNIT_VTABLE(u)->no_gc) return true; @@ -331,7 +348,8 @@ void unit_add_to_dbus_queue(Unit *u) { return; /* Shortcut things if nobody cares */ - if (set_isempty(u->manager->subscribed)) { + if (sd_bus_track_count(u->manager->subscribed) <= 0 && + set_isempty(u->manager->private_buses)) { u->sent_dbus_new_signal = true; return; } @@ -412,6 +430,27 @@ static void unit_free_requires_mounts_for(Unit *u) { u->requires_mounts_for = NULL; } +static void unit_done(Unit *u) { + ExecContext *ec; + CGroupContext *cc; + + assert(u); + + if (u->type < 0) + return; + + if (UNIT_VTABLE(u)->done) + UNIT_VTABLE(u)->done(u); + + ec = unit_get_exec_context(u); + if (ec) + exec_context_done(ec); + + cc = unit_get_cgroup_context(u); + if (cc) + cgroup_context_done(cc); +} + void unit_free(Unit *u) { UnitDependency d; Iterator i; @@ -424,9 +463,7 @@ void unit_free(Unit *u) { bus_unit_send_removed_signal(u); - if (u->load_state != UNIT_STUB) - if (UNIT_VTABLE(u)->done) - UNIT_VTABLE(u)->done(u); + unit_done(u); unit_free_requires_mounts_for(u); @@ -473,6 +510,9 @@ void unit_free(Unit *u) { free(u->cgroup_path); } + set_remove(u->manager->failed_units, u); + set_remove(u->manager->startup_units, u); + free(u->description); strv_free(u->documentation); free(u->fragment_path); @@ -545,7 +585,7 @@ static void merge_names(Unit *u, Unit *other) { assert_se(hashmap_replace(u->manager->units, t, u) == 0); } -static void merge_dependencies(Unit *u, Unit *other, UnitDependency d) { +static void merge_dependencies(Unit *u, Unit *other, const char *other_id, UnitDependency d) { Iterator i; Unit *back; int r; @@ -559,14 +599,25 @@ static void merge_dependencies(Unit *u, Unit *other, UnitDependency d) { UnitDependency k; for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++) { - r = set_remove_and_put(back->dependencies[k], other, u); - if (r == -EEXIST) - set_remove(back->dependencies[k], other); - else - assert(r >= 0 || r == -ENOENT); + /* Do not add dependencies between u and itself */ + if (back == u) { + if (set_remove(back->dependencies[k], other)) + maybe_warn_about_dependency(u->id, other_id, k); + } else { + r = set_remove_and_put(back->dependencies[k], other, u); + if (r == -EEXIST) + set_remove(back->dependencies[k], other); + else + assert(r >= 0 || r == -ENOENT); + } } } + /* Also do not move dependencies on u to itself */ + back = set_remove(other->dependencies[d], u); + if (back) + maybe_warn_about_dependency(u->id, other_id, d); + complete_move(&u->dependencies[d], &other->dependencies[d]); set_free(other->dependencies[d]); @@ -575,6 +626,7 @@ static void merge_dependencies(Unit *u, Unit *other, UnitDependency d) { int unit_merge(Unit *u, Unit *other) { UnitDependency d; + const char *other_id = NULL; assert(u); assert(other); @@ -605,6 +657,9 @@ int unit_merge(Unit *u, Unit *other) { if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) return -EEXIST; + if (other->id) + other_id = strdupa(other->id); + /* Merge names */ merge_names(u, other); @@ -614,7 +669,7 @@ int unit_merge(Unit *u, Unit *other) { /* Merge dependencies */ for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - merge_dependencies(u, other, d); + merge_dependencies(u, other, other_id, d); other->load_state = UNIT_MERGED; other->merged_into = u; @@ -686,6 +741,19 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { return r; } + if (u->manager->running_as != SYSTEMD_SYSTEM) + return 0; + + if (c->private_tmp) { + r = unit_require_mounts_for(u, "/tmp"); + if (r < 0) + return r; + + r = unit_require_mounts_for(u, "/var/tmp"); + if (r < 0) + return r; + } + if (c->std_output != EXEC_OUTPUT_KMSG && c->std_output != EXEC_OUTPUT_SYSLOG && c->std_output != EXEC_OUTPUT_JOURNAL && @@ -703,11 +771,9 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { /* If syslog or kernel logging is requested, make sure our own * logging daemon is run first. */ - if (u->manager->running_as == SYSTEMD_SYSTEM) { - r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true); - if (r < 0) - return r; - } + r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true); + if (r < 0) + return r; return 0; } @@ -725,7 +791,6 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { char *t, **j; UnitDependency d; Iterator i; - _cleanup_free_ char *p2 = NULL; const char *prefix2; char timestamp1[FORMAT_TIMESTAMP_MAX], @@ -740,10 +805,8 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { assert(u); assert(u->type >= 0); - if (!prefix) - prefix = ""; - p2 = strappend(prefix, "\t"); - prefix2 = p2 ? p2 : prefix; + prefix = strempty(prefix); + prefix2 = strappenda(prefix, "\t"); fprintf(f, "%s-> Unit %s:\n" @@ -778,7 +841,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, strna(unit_slice_name(u)), prefix, strna(u->cgroup_path), prefix, yes_no(u->cgroup_realized), - prefix, u->cgroup_mask, + prefix, u->cgroup_realized_mask, prefix, u->cgroup_members_mask); SET_FOREACH(t, u->names, i) @@ -947,7 +1010,7 @@ int unit_add_default_target_dependency(Unit *u, Unit *target) { return unit_add_dependency(target, UNIT_AFTER, u, true); } -static int unit_add_default_dependencies(Unit *u) { +static int unit_add_target_dependencies(Unit *u) { static const UnitDependency deps[] = { UNIT_REQUIRED_BY, @@ -958,8 +1021,8 @@ static int unit_add_default_dependencies(Unit *u) { Unit *target; Iterator i; - int r; unsigned k; + int r = 0; assert(u); @@ -970,20 +1033,25 @@ static int unit_add_default_dependencies(Unit *u) { return r; } - if (u->default_dependencies && unit_get_cgroup_context(u)) { - if (UNIT_ISSET(u->slice)) - r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_WANTS, UNIT_DEREF(u->slice), true); - else - r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, SPECIAL_ROOT_SLICE, NULL, true); + return r; +} - if (r < 0) - return r; - } +static int unit_add_slice_dependencies(Unit *u) { + assert(u); - return 0; + if (!unit_get_cgroup_context(u)) + return 0; + + if (UNIT_ISSET(u->slice)) + return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_WANTS, UNIT_DEREF(u->slice), true); + + if (streq(u->id, SPECIAL_ROOT_SLICE)) + return 0; + + return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, SPECIAL_ROOT_SLICE, NULL, true); } -static int unit_add_mount_links(Unit *u) { +static int unit_add_mount_dependencies(Unit *u) { char **i; int r; @@ -1021,6 +1089,25 @@ static int unit_add_mount_links(Unit *u) { return 0; } +static int unit_add_startup_units(Unit *u) { + CGroupContext *c; + int r = 0; + + c = unit_get_cgroup_context(u); + if (!c) + return 0; + + if (c->startup_cpu_shares == (unsigned long) -1 && + c->startup_blockio_weight == (unsigned long) -1) + return 0; + + r = set_put(u->manager->startup_units, u); + if (r == -EEXIST) + return 0; + + return r; +} + int unit_load(Unit *u) { int r; @@ -1050,27 +1137,29 @@ int unit_load(Unit *u) { if (u->load_state == UNIT_LOADED) { - if (u->default_dependencies) { - r = unit_add_default_dependencies(u); - if (r < 0) - goto fail; - } - - unit_update_member_masks(u); + r = unit_add_target_dependencies(u); + if (r < 0) + goto fail; - r = unit_add_mount_links(u); + r = unit_add_slice_dependencies(u); if (r < 0) goto fail; - if (u->on_failure_job_mode == JOB_ISOLATE && - set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + r = unit_add_mount_dependencies(u); + if (r < 0) + goto fail; - log_error_unit(u->id, - "More than one OnFailure= dependencies specified for %s but OnFailureJobMode=isolate set. Refusing.", u->id); + r = unit_add_startup_units(u); + if (r < 0) + goto fail; + if (u->on_failure_job_mode == JOB_ISOLATE && set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + log_error_unit(u->id, "More than one OnFailure= dependencies specified for %s but OnFailureJobMode=isolate set. Refusing.", u->id); r = -EINVAL; goto fail; } + + unit_update_cgroup_members_masks(u); } assert((u->load_state != UNIT_MERGED) == !u->merged_into); @@ -1140,8 +1229,6 @@ _pure_ static const char *unit_get_status_message_format_try_harder(Unit *u, Job return NULL; } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" static void unit_status_print_starting_stopping(Unit *u, JobType t) { const char *format; @@ -1154,12 +1241,11 @@ static void unit_status_print_starting_stopping(Unit *u, JobType t) { if (!format) return; + DISABLE_WARNING_FORMAT_NONLITERAL; unit_status_printf(u, "", format); + REENABLE_WARNING; } -#pragma GCC diagnostic pop -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { const char *format; char buf[LINE_MAX]; @@ -1179,8 +1265,10 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { if (!format) return; + DISABLE_WARNING_FORMAT_NONLITERAL; snprintf(buf, sizeof(buf), format, unit_description(u)); char_array_0(buf); + REENABLE_WARNING; mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING : t == JOB_STOP ? SD_MESSAGE_UNIT_STOPPING : @@ -1192,7 +1280,6 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { "MESSAGE=%s", buf, NULL); } -#pragma GCC diagnostic pop /* Errors: * -EBADR: This unit type does not support starting. @@ -1381,12 +1468,44 @@ static void unit_check_unneeded(Unit *u) { if (unit_active_or_pending(other)) return; - log_info_unit(u->id, "Service %s is not needed anymore. Stopping.", u->id); + log_info_unit(u->id, "Unit %s is not needed anymore. Stopping.", u->id); /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, NULL, NULL); } +static void unit_check_binds_to(Unit *u) { + bool stop = false; + Unit *other; + Iterator i; + + assert(u); + + if (u->job) + return; + + if (unit_active_state(u) != UNIT_ACTIVE) + return; + + SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) { + if (other->job) + continue; + + if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) + continue; + + stop = true; + } + + if (!stop) + return; + + log_info_unit(u->id, "Unit %s is bound to inactive service. Stopping, too.", u->id); + + /* A unit we need to run is gone. Sniff. Let's stop this. */ + manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, NULL, NULL); +} + static void retroactively_start_dependencies(Unit *u) { Iterator i; Unit *other; @@ -1505,12 +1624,13 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su /* Note that this is called for all low-level state changes, * even if they might map to the same high-level - * UnitActiveState! That means that ns == os is OK an expected + * UnitActiveState! That means that ns == os is an expected * behavior here. For example: if a mount point is remounted * this function will be called too! */ m = u->manager; + /* Update timestamps for state changes */ if (m->n_reloading <= 0) { dual_timestamp ts; @@ -1527,14 +1647,23 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su u->active_exit_timestamp = ts; } + /* Keep track of failed units */ + if (ns == UNIT_FAILED) + set_put(u->manager->failed_units, u); + else + set_remove(u->manager->failed_units, u); + + /* Make sure the cgroup is always removed when we become inactive */ if (UNIT_IS_INACTIVE_OR_FAILED(ns)) unit_destroy_cgroup(u); /* Note that this doesn't apply to RemainAfterExit services exiting - * sucessfully, since there's no change of state in that case. Which is + * successfully, since there's no change of state in that case. Which is * why it is handled in service_set_state() */ if (UNIT_IS_INACTIVE_OR_FAILED(os) != UNIT_IS_INACTIVE_OR_FAILED(ns)) { - ExecContext *ec = unit_get_exec_context(u); + ExecContext *ec; + + ec = unit_get_exec_context(u); if (ec && exec_context_may_touch_console(ec)) { if (UNIT_IS_INACTIVE_OR_FAILED(ns)) { m->n_on_console --; @@ -1630,12 +1759,11 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su } /* stop unneeded units regardless if going down was expected or not */ - if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) check_unneeded_dependencies(u); if (ns != os && ns == UNIT_FAILED) { - log_notice_unit(u->id, - "Unit %s entered failed state.", u->id); + log_notice_unit(u->id, "Unit %s entered failed state.", u->id); unit_start_on_failure(u); } } @@ -1644,7 +1772,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) - /* The bus just might have become available, + /* The bus might have just become available, * hence try to connect to it, if we aren't * yet connected. */ bus_init(m, true); @@ -1689,11 +1817,19 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su manager_recheck_journal(m); unit_trigger_notify(u); - /* Maybe we finished startup and are now ready for being - * stopped because unneeded? */ - if (u->manager->n_reloading <= 0) + if (u->manager->n_reloading <= 0) { + /* Maybe we finished startup and are now ready for + * being stopped because unneeded? */ unit_check_unneeded(u); + /* Maybe we finished startup, but something we needed + * has vanished? Let's die then. (This happens when + * something BindsTo= to a Type=oneshot unit, as these + * units go directly from starting to inactive, + * without ever entering started.) */ + unit_check_binds_to(u); + } + unit_add_to_dbus_queue(u); unit_add_to_gc_queue(u); } @@ -1707,11 +1843,11 @@ int unit_watch_pid(Unit *u, pid_t pid) { /* Watch a specific PID. We only support one or two units * watching each PID for now, not more. */ - r = hashmap_ensure_allocated(&u->manager->watch_pids1, trivial_hash_func, trivial_compare_func); + r = set_ensure_allocated(&u->pids, trivial_hash_func, trivial_compare_func); if (r < 0) return r; - r = set_ensure_allocated(&u->pids, trivial_hash_func, trivial_compare_func); + r = hashmap_ensure_allocated(&u->manager->watch_pids1, trivial_hash_func, trivial_compare_func); if (r < 0) return r; @@ -1740,7 +1876,17 @@ void unit_unwatch_pid(Unit *u, pid_t pid) { set_remove(u->pids, LONG_TO_PTR(pid)); } -static int watch_pids_in_path(Unit *u, const char *path) { +void unit_unwatch_all_pids(Unit *u) { + assert(u); + + while (!set_isempty(u->pids)) + unit_unwatch_pid(u, PTR_TO_LONG(set_first(u->pids))); + + set_free(u->pids); + u->pids = NULL; +} + +static int unit_watch_pids_in_path(Unit *u, const char *path) { _cleanup_closedir_ DIR *d = NULL; _cleanup_fclose_ FILE *f = NULL; int ret = 0, r; @@ -1778,7 +1924,7 @@ static int watch_pids_in_path(Unit *u, const char *path) { if (!p) return -ENOMEM; - r = watch_pids_in_path(u, p); + r = unit_watch_pids_in_path(u, p); if (r < 0 && ret >= 0) ret = r; } @@ -1791,31 +1937,15 @@ static int watch_pids_in_path(Unit *u, const char *path) { return ret; } - int unit_watch_all_pids(Unit *u) { assert(u); - if (!u->cgroup_path) - return -ENOENT; - /* Adds all PIDs from our cgroup to the set of PIDs we watch */ - return watch_pids_in_path(u, u->cgroup_path); -} - -void unit_unwatch_all_pids(Unit *u) { - Iterator i; - void *e; - - assert(u); - - SET_FOREACH(e, u->pids, i) { - hashmap_remove_value(u->manager->watch_pids1, e, u); - hashmap_remove_value(u->manager->watch_pids2, e, u); - } + if (!u->cgroup_path) + return -ENOENT; - set_free(u->pids); - u->pids = NULL; + return unit_watch_pids_in_path(u, u->cgroup_path); } void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2) { @@ -1832,8 +1962,8 @@ void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2) { if (pid == except1 || pid == except2) continue; - if (kill(pid, 0) < 0 && errno == ESRCH) - set_remove(u->pids, e); + if (!pid_is_unwaited(pid)) + unit_unwatch_pid(u, pid); } } @@ -1864,6 +1994,53 @@ bool unit_job_is_applicable(Unit *u, JobType j) { } } +static int maybe_warn_about_dependency(const char *id, const char *other, UnitDependency dependency) { + assert(id); + + switch (dependency) { + case UNIT_REQUIRES: + case UNIT_REQUIRES_OVERRIDABLE: + case UNIT_WANTS: + case UNIT_REQUISITE: + case UNIT_REQUISITE_OVERRIDABLE: + case UNIT_BINDS_TO: + case UNIT_PART_OF: + case UNIT_REQUIRED_BY: + case UNIT_REQUIRED_BY_OVERRIDABLE: + case UNIT_WANTED_BY: + case UNIT_BOUND_BY: + case UNIT_CONSISTS_OF: + case UNIT_REFERENCES: + case UNIT_REFERENCED_BY: + case UNIT_PROPAGATES_RELOAD_TO: + case UNIT_RELOAD_PROPAGATED_FROM: + case UNIT_JOINS_NAMESPACE_OF: + return 0; + + case UNIT_CONFLICTS: + case UNIT_CONFLICTED_BY: + case UNIT_BEFORE: + case UNIT_AFTER: + case UNIT_ON_FAILURE: + case UNIT_TRIGGERS: + case UNIT_TRIGGERED_BY: + if (streq_ptr(id, other)) + log_warning_unit(id, "Dependency %s=%s dropped from unit %s", + unit_dependency_to_string(dependency), id, other); + else + log_warning_unit(id, "Dependency %s=%s dropped from unit %s merged into %s", + unit_dependency_to_string(dependency), id, + strna(other), id); + return -EINVAL; + + case _UNIT_DEPENDENCY_MAX: + case _UNIT_DEPENDENCY_INVALID: + break; + } + + assert_not_reached("Invalid dependency type"); +} + int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference) { static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = { @@ -1893,6 +2070,7 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen [UNIT_JOINS_NAMESPACE_OF] = UNIT_JOINS_NAMESPACE_OF, }; int r, q = 0, v = 0, w = 0; + Unit *orig_u = u, *orig_other = other; assert(u); assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); @@ -1903,8 +2081,10 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen /* We won't allow dependencies on ourselves. We will not * consider them an error however. */ - if (u == other) + if (u == other) { + maybe_warn_about_dependency(orig_u->id, orig_other->id, d); return 0; + } r = set_ensure_allocated(&u->dependencies[d], trivial_hash_func, trivial_compare_func); if (r < 0) @@ -1971,10 +2151,12 @@ int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit assert(u); - if ((r = unit_add_dependency(u, d, other, add_reference)) < 0) + r = unit_add_dependency(u, d, other, add_reference); + if (r < 0) return r; - if ((r = unit_add_dependency(u, e, other, add_reference)) < 0) + r = unit_add_dependency(u, e, other, add_reference); + if (r < 0) return r; return 0; @@ -2034,22 +2216,22 @@ int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, con } int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { + _cleanup_free_ char *s = NULL; Unit *other; int r; - _cleanup_free_ char *s = NULL; assert(u); assert(name || path); - if (!(name = resolve_template(u, name, path, &s))) + name = resolve_template(u, name, path, &s); + if (!name) return -ENOMEM; - if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + r = manager_load_unit(u->manager, name, path, NULL, &other); + if (r < 0) return r; - r = unit_add_two_dependencies(u, d, e, other, add_reference); - - return r; + return unit_add_two_dependencies(u, d, e, other, add_reference); } int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { @@ -2060,15 +2242,15 @@ int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *n assert(u); assert(name || path); - if (!(name = resolve_template(u, name, path, &s))) + name = resolve_template(u, name, path, &s); + if (!name) return -ENOMEM; - if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + r = manager_load_unit(u->manager, name, path, NULL, &other); + if (r < 0) return r; - r = unit_add_dependency(other, d, u, add_reference); - - return r; + return unit_add_dependency(other, d, u, add_reference); } int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { @@ -2079,24 +2261,24 @@ int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDep assert(u); assert(name || path); - if (!(name = resolve_template(u, name, path, &s))) + name = resolve_template(u, name, path, &s); + if (!name) return -ENOMEM; - if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) + r = manager_load_unit(u->manager, name, path, NULL, &other); + if (r < 0) return r; - if ((r = unit_add_two_dependencies(other, d, e, u, add_reference)) < 0) + r = unit_add_two_dependencies(other, d, e, u, add_reference); + if (r < 0) return r; return r; } int set_unit_path(const char *p) { - _cleanup_free_ char *c = NULL; - /* This is mostly for debug purposes */ - c = path_make_absolute_cwd(p); - if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) + if (setenv("SYSTEMD_UNIT_PATH", p, 0) < 0) return -errno; return 0; @@ -2136,20 +2318,18 @@ char *unit_default_cgroup_path(Unit *u) { return strjoin(u->manager->cgroup_root, "/", escaped, NULL); } -int unit_add_default_slice(Unit *u) { +int unit_add_default_slice(Unit *u, CGroupContext *c) { _cleanup_free_ char *b = NULL; const char *slice_name; Unit *slice; int r; assert(u); + assert(c); if (UNIT_ISSET(u->slice)) return 0; - if (!unit_get_cgroup_context(u)) - return 0; - if (u->instance) { _cleanup_free_ char *prefix = NULL, *escaped = NULL; @@ -2241,25 +2421,25 @@ bool unit_can_serialize(Unit *u) { } int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { - ExecRuntime *rt; int r; assert(u); assert(f); assert(fds); - if (!unit_can_serialize(u)) - return 0; - - r = UNIT_VTABLE(u)->serialize(u, f, fds); - if (r < 0) - return r; + if (unit_can_serialize(u)) { + ExecRuntime *rt; - rt = unit_get_exec_runtime(u); - if (rt) { - r = exec_runtime_serialize(rt, u, f, fds); + r = UNIT_VTABLE(u)->serialize(u, f, fds); if (r < 0) return r; + + rt = unit_get_exec_runtime(u); + if (rt) { + r = exec_runtime_serialize(rt, u, f, fds); + if (r < 0) + return r; + } } dual_timestamp_serialize(f, "inactive-exit-timestamp", &u->inactive_exit_timestamp); @@ -2321,17 +2501,14 @@ void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { } int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { - size_t offset; ExecRuntime **rt = NULL; + size_t offset; int r; assert(u); assert(f); assert(fds); - if (!unit_can_serialize(u)) - return 0; - offset = UNIT_VTABLE(u)->exec_runtime_offset; if (offset > 0) rt = (ExecRuntime**) ((uint8_t*) u + offset); @@ -2441,24 +2618,34 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { if (!s) return -ENOMEM; - free(u->cgroup_path); - u->cgroup_path = s; + if (u->cgroup_path) { + void *p; + p = hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); + log_info("Removing cgroup_path %s from hashmap (%p)", + u->cgroup_path, p); + free(u->cgroup_path); + } + + u->cgroup_path = s; assert(hashmap_put(u->manager->cgroup_unit, s, u) == 1); + continue; } - if (rt) { - r = exec_runtime_deserialize_item(rt, u, l, v, fds); + if (unit_can_serialize(u)) { + if (rt) { + r = exec_runtime_deserialize_item(rt, u, l, v, fds); + if (r < 0) + return r; + if (r > 0) + continue; + } + + r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds); if (r < 0) return r; - if (r > 0) - continue; } - - r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds); - if (r < 0) - return r; } } @@ -2524,12 +2711,11 @@ int unit_coldplug(Unit *u) { return 0; } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) { + DISABLE_WARNING_FORMAT_NONLITERAL; manager_status_printf(u->manager, false, status, unit_status_msg_format, unit_description(u)); + REENABLE_WARNING; } -#pragma GCC diagnostic pop bool unit_need_daemon_reload(Unit *u) { _cleanup_strv_free_ char **t = NULL; @@ -2782,27 +2968,55 @@ void unit_ref_unset(UnitRef *ref) { ref->unit = NULL; } -int unit_exec_context_defaults(Unit *u, ExecContext *c) { +int unit_patch_contexts(Unit *u) { + CGroupContext *cc; + ExecContext *ec; unsigned i; int r; assert(u); - assert(c); - /* This only copies in the ones that need memory */ - for (i = 0; i < RLIMIT_NLIMITS; i++) - if (u->manager->rlimit[i] && !c->rlimit[i]) { - c->rlimit[i] = newdup(struct rlimit, u->manager->rlimit[i], 1); - if (!c->rlimit[i]) - return -ENOMEM; + /* Patch in the manager defaults into the exec and cgroup + * contexts, _after_ the rest of the settings have been + * initialized */ + + ec = unit_get_exec_context(u); + if (ec) { + /* This only copies in the ones that need memory */ + for (i = 0; i < _RLIMIT_MAX; i++) + if (u->manager->rlimit[i] && !ec->rlimit[i]) { + ec->rlimit[i] = newdup(struct rlimit, u->manager->rlimit[i], 1); + if (!ec->rlimit[i]) + return -ENOMEM; + } + + if (u->manager->running_as == SYSTEMD_USER && + !ec->working_directory) { + + r = get_home_dir(&ec->working_directory); + if (r < 0) + return r; } - if (u->manager->running_as == SYSTEMD_USER && - !c->working_directory) { + if (u->manager->running_as == SYSTEMD_USER && + (ec->syscall_whitelist || + !set_isempty(ec->syscall_filter) || + !set_isempty(ec->syscall_archs) || + ec->address_families_whitelist || + !set_isempty(ec->address_families))) + ec->no_new_privileges = true; - r = get_home_dir(&c->working_directory); - if (r < 0) - return r; + if (ec->private_devices) + ec->capability_bounding_set_drop |= (uint64_t) 1ULL << (uint64_t) CAP_MKNOD; + } + + cc = unit_get_cgroup_context(u); + if (cc) { + + if (ec && + ec->private_devices && + cc->device_policy == CGROUP_AUTO) + cc->device_policy = CGROUP_CLOSED; } return 0; @@ -2812,6 +3026,9 @@ ExecContext *unit_get_exec_context(Unit *u) { size_t offset; assert(u); + if (u->type < 0) + return NULL; + offset = UNIT_VTABLE(u)->exec_context_offset; if (offset <= 0) return NULL; @@ -2823,6 +3040,9 @@ KillContext *unit_get_kill_context(Unit *u) { size_t offset; assert(u); + if (u->type < 0) + return NULL; + offset = UNIT_VTABLE(u)->kill_context_offset; if (offset <= 0) return NULL; @@ -2833,6 +3053,9 @@ KillContext *unit_get_kill_context(Unit *u) { CGroupContext *unit_get_cgroup_context(Unit *u) { size_t offset; + if (u->type < 0) + return NULL; + offset = UNIT_VTABLE(u)->cgroup_context_offset; if (offset <= 0) return NULL; @@ -2843,6 +3066,9 @@ CGroupContext *unit_get_cgroup_context(Unit *u) { ExecRuntime *unit_get_exec_runtime(Unit *u) { size_t offset; + if (u->type < 0) + return NULL; + offset = UNIT_VTABLE(u)->exec_runtime_offset; if (offset <= 0) return NULL; @@ -2850,69 +3076,55 @@ ExecRuntime *unit_get_exec_runtime(Unit *u) { return *(ExecRuntime**) ((uint8_t*) u + offset); } -static int drop_in_file(Unit *u, UnitSetPropertiesMode mode, const char *name, char **_p, char **_q) { - _cleanup_free_ char *b = NULL; - char *p, *q; - int r; - - assert(u); - assert(name); - assert(_p); - assert(_q); - assert(mode & (UNIT_PERSISTENT|UNIT_RUNTIME)); - - b = xescape(name, "/."); - if (!b) - return -ENOMEM; - - if (!filename_is_safe(b)) - return -EINVAL; - +static int unit_drop_in_dir(Unit *u, UnitSetPropertiesMode mode, bool transient, char **dir) { if (u->manager->running_as == SYSTEMD_USER) { - _cleanup_free_ char *c = NULL; + int r; - r = user_config_home(&c); - if (r < 0) - return r; + r = user_config_home(dir); if (r == 0) return -ENOENT; + return r; + } - p = strjoin(c, "/", u->id, ".d", NULL); - } else if (mode & UNIT_PERSISTENT) - p = strjoin("/etc/systemd/system/", u->id, ".d", NULL); + if (mode == UNIT_PERSISTENT && !transient) + *dir = strdup("/etc/systemd/system"); else - p = strjoin("/run/systemd/system/", u->id, ".d", NULL); - if (!p) + *dir = strdup("/run/systemd/system"); + if (!*dir) return -ENOMEM; - q = strjoin(p, "/90-", b, ".conf", NULL); - if (!q) { - free(p); - return -ENOMEM; - } - - *_p = p; - *_q = q; return 0; } +static int unit_drop_in_file(Unit *u, + UnitSetPropertiesMode mode, const char *name, char **p, char **q) { + _cleanup_free_ char *dir = NULL; + int r; + + assert(u); + + r = unit_drop_in_dir(u, mode, u->transient, &dir); + if (r < 0) + return r; + + return drop_in_file(dir, u->id, 50, name, p, q); +} + int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { - _cleanup_free_ char *p = NULL, *q = NULL; + + _cleanup_free_ char *dir = NULL; int r; assert(u); - assert(name); - assert(data); - if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) return 0; - r = drop_in_file(u, mode, name, &p, &q); + r = unit_drop_in_dir(u, mode, u->transient, &dir); if (r < 0) return r; - mkdir_p(p, 0755); - return write_string_file_atomic_label(q, data); + return write_drop_in(dir, u->id, 50, name, data); } int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { @@ -2924,7 +3136,7 @@ int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *n assert(name); assert(format); - if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) return 0; va_start(ap, format); @@ -2947,7 +3159,7 @@ int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char * if (!UNIT_VTABLE(u)->private_section) return -EINVAL; - if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) return 0; ndata = strjoin("[", UNIT_VTABLE(u)->private_section, "]\n", data, NULL); @@ -2966,7 +3178,7 @@ int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const assert(name); assert(format); - if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) return 0; va_start(ap, format); @@ -2985,10 +3197,10 @@ int unit_remove_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name) { assert(u); - if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) return 0; - r = drop_in_file(u, mode, name, &p, &q); + r = unit_drop_in_file(u, mode, name, &p, &q); if (r < 0) return r; @@ -3103,7 +3315,15 @@ int unit_kill_context( log_warning_unit(u->id, "Failed to kill control group: %s", strerror(-r)); } else if (r > 0) { - wait_for_exit = true; + /* FIXME: For now, we will not wait for the + * cgroup members to die, simply because + * cgroup notification is unreliable. It + * doesn't work at all in containers, and + * outside of containers it can be confused + * easily by leaving directories in the + * cgroup. */ + + /* wait_for_exit = true; */ if (c->send_sighup && !sigkill) { set_free(pid_set); @@ -3153,11 +3373,9 @@ int unit_require_mounts_for(Unit *u, const char *path) { return 0; } - r = strv_push(&u->requires_mounts_for, p); - if (r < 0) { - free(p); + r = strv_consume(&u->requires_mounts_for, p); + if (r < 0) return r; - } PATH_FOREACH_PREFIX_MORE(prefix, p) { Set *x; @@ -3207,7 +3425,7 @@ int unit_setup_exec_runtime(Unit *u) { offset = UNIT_VTABLE(u)->exec_runtime_offset; assert(offset > 0); - /* Check if ther already is an ExecRuntime for this unit? */ + /* Check if there already is an ExecRuntime for this unit? */ rt = (ExecRuntime**) ((uint8_t*) u + offset); if (*rt) return 0; @@ -3258,9 +3476,9 @@ static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { [UNIT_TRIGGERED_BY] = "TriggeredBy", [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo", [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom", + [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf", [UNIT_REFERENCES] = "References", [UNIT_REFERENCED_BY] = "ReferencedBy", - [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf", }; DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);