X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fcore%2Funit.c;h=948f8e21bd268e6b99f0ab9aa33a53d41c1666ef;hp=211704e230603882f78490ee314df5163a469252;hb=493657337ad8569e0998a3afa7d6fb357757364a;hpb=b42defe3b8ed3947d85db654a6cdb1b9999f394d diff --git a/src/core/unit.c b/src/core/unit.c index 211704e23..948f8e21b 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -29,8 +29,8 @@ #include #include -#include "systemd/sd-id128.h" -#include "systemd/sd-messages.h" +#include "sd-id128.h" +#include "sd-messages.h" #include "set.h" #include "unit.h" #include "macro.h" @@ -48,21 +48,29 @@ #include "label.h" #include "fileio-label.h" #include "bus-errors.h" +#include "dbus.h" +#include "execute.h" +#include "virt.h" +#include "dropin.h" const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = &service_vtable, - [UNIT_TIMER] = &timer_vtable, [UNIT_SOCKET] = &socket_vtable, + [UNIT_BUSNAME] = &busname_vtable, [UNIT_TARGET] = &target_vtable, + [UNIT_SNAPSHOT] = &snapshot_vtable, [UNIT_DEVICE] = &device_vtable, [UNIT_MOUNT] = &mount_vtable, [UNIT_AUTOMOUNT] = &automount_vtable, - [UNIT_SNAPSHOT] = &snapshot_vtable, [UNIT_SWAP] = &swap_vtable, + [UNIT_TIMER] = &timer_vtable, [UNIT_PATH] = &path_vtable, - [UNIT_SLICE] = &slice_vtable + [UNIT_SLICE] = &slice_vtable, + [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; @@ -73,7 +81,7 @@ Unit *unit_new(Manager *m, size_t size) { if (!u) return NULL; - u->names = set_new(string_hash_func, string_compare_func); + u->names = set_new(&string_hash_ops); if (!u->names) { free(u); return NULL; @@ -84,6 +92,7 @@ Unit *unit_new(Manager *m, size_t size) { u->deserialized_job = _JOB_TYPE_INVALID; u->default_dependencies = true; u->unit_file_state = _UNIT_FILE_STATE_INVALID; + u->on_failure_job_mode = JOB_REPLACE; return u; } @@ -95,100 +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, false)) { - 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; - if ((r = unit_name_to_instance(s, &i)) < 0) - goto fail; + r = unit_name_to_instance(s, &i); + if (r < 0) + 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; - if ((r = set_put(u->names, s)) < 0) { + r = set_put(u->names, s); + if (r < 0) { if (r == -EEXIST) - r = 0; - goto fail; + return 0; + + return r; } - if ((r = hashmap_put(u->manager->units, s, u)) < 0) { + 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(Unit, units_by_type, u->manager->units_by_type[t], u); + 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, *t = NULL, *i; + _cleanup_free_ char *t = NULL; + char *s, *i; int r; assert(u); @@ -199,7 +230,8 @@ int unit_choose_id(Unit *u, const char *name) { if (!u->instance) return -EINVAL; - if (!(t = unit_name_replace_instance(name, u->instance))) + t = unit_name_replace_instance(name, u->instance); + if (!t) return -ENOMEM; name = t; @@ -207,12 +239,11 @@ 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); - free(t); - if (!s) return -ENOENT; - if ((r = unit_name_to_instance(s, &i)) < 0) + r = unit_name_to_instance(s, &i); + if (r < 0) return r; u->id = s; @@ -230,8 +261,13 @@ int unit_set_description(Unit *u, const char *description) { assert(u); - if (!(s = strdup(description))) - return -ENOMEM; + if (isempty(description)) + s = NULL; + else { + s = strdup(description); + if (!s) + return -ENOMEM; + } free(u->description); u->description = s; @@ -243,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; @@ -278,7 +311,7 @@ void unit_add_to_load_queue(Unit *u) { if (u->load_state != UNIT_STUB || u->in_load_queue) return; - LIST_PREPEND(Unit, load_queue, u->manager->load_queue, u); + LIST_PREPEND(load_queue, u->manager->load_queue, u); u->in_load_queue = true; } @@ -288,7 +321,7 @@ void unit_add_to_cleanup_queue(Unit *u) { if (u->in_cleanup_queue) return; - LIST_PREPEND(Unit, cleanup_queue, u->manager->cleanup_queue, u); + LIST_PREPEND(cleanup_queue, u->manager->cleanup_queue, u); u->in_cleanup_queue = true; } @@ -301,13 +334,10 @@ void unit_add_to_gc_queue(Unit *u) { if (unit_check_gc(u)) return; - LIST_PREPEND(Unit, gc_queue, u->manager->gc_queue, u); + LIST_PREPEND(gc_queue, u->manager->gc_queue, u); u->in_gc_queue = true; u->manager->n_in_gc_queue ++; - - if (u->manager->gc_queue_timestamp <= 0) - u->manager->gc_queue_timestamp = now(CLOCK_MONOTONIC); } void unit_add_to_dbus_queue(Unit *u) { @@ -318,12 +348,13 @@ void unit_add_to_dbus_queue(Unit *u) { return; /* Shortcut things if nobody cares */ - if (!bus_has_subscriber(u->manager)) { + if (sd_bus_track_count(u->manager->subscribed) <= 0 && + set_isempty(u->manager->private_buses)) { u->sent_dbus_new_signal = true; return; } - LIST_PREPEND(Unit, dbus_queue, u->manager->dbus_unit_queue, u); + LIST_PREPEND(dbus_queue, u->manager->dbus_unit_queue, u); u->in_dbus_queue = true; } @@ -348,6 +379,78 @@ static void bidi_set_free(Unit *u, Set *s) { set_free(s); } +static void unit_remove_transient(Unit *u) { + char **i; + + assert(u); + + if (!u->transient) + return; + + if (u->fragment_path) + unlink(u->fragment_path); + + STRV_FOREACH(i, u->dropin_paths) { + _cleanup_free_ char *p = NULL; + int r; + + unlink(*i); + + r = path_get_parent(*i, &p); + if (r >= 0) + rmdir(p); + } +} + +static void unit_free_requires_mounts_for(Unit *u) { + char **j; + + STRV_FOREACH(j, u->requires_mounts_for) { + char s[strlen(*j) + 1]; + + PATH_FOREACH_PREFIX_MORE(s, *j) { + char *y; + Set *x; + + x = hashmap_get2(u->manager->units_requiring_mounts_for, s, (void**) &y); + if (!x) + continue; + + set_remove(x, u); + + if (set_isempty(x)) { + hashmap_remove(u->manager->units_requiring_mounts_for, y); + free(y); + set_free(x); + } + } + } + + strv_free(u->requires_mounts_for); + 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; @@ -355,11 +458,14 @@ void unit_free(Unit *u) { assert(u); + if (u->manager->n_reloading <= 0) + unit_remove_transient(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); SET_FOREACH(t, u->names, i) hashmap_remove_value(u->manager->units, t, u); @@ -379,32 +485,34 @@ void unit_free(Unit *u) { for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) bidi_set_free(u, u->dependencies[d]); - if (u->requires_mounts_for) { - LIST_REMOVE(Unit, has_requires_mounts_for, u->manager->has_requires_mounts_for, u); - strv_free(u->requires_mounts_for); - } - if (u->type != _UNIT_TYPE_INVALID) - LIST_REMOVE(Unit, units_by_type, u->manager->units_by_type[u->type], u); + LIST_REMOVE(units_by_type, u->manager->units_by_type[u->type], u); if (u->in_load_queue) - LIST_REMOVE(Unit, load_queue, u->manager->load_queue, u); + LIST_REMOVE(load_queue, u->manager->load_queue, u); if (u->in_dbus_queue) - LIST_REMOVE(Unit, dbus_queue, u->manager->dbus_unit_queue, u); + LIST_REMOVE(dbus_queue, u->manager->dbus_unit_queue, u); if (u->in_cleanup_queue) - LIST_REMOVE(Unit, cleanup_queue, u->manager->cleanup_queue, u); + LIST_REMOVE(cleanup_queue, u->manager->cleanup_queue, u); if (u->in_gc_queue) { - LIST_REMOVE(Unit, gc_queue, u->manager->gc_queue, u); + LIST_REMOVE(gc_queue, u->manager->gc_queue, u); u->manager->n_in_gc_queue--; } if (u->in_cgroup_queue) - LIST_REMOVE(Unit, cgroup_queue, u->manager->cgroup_queue, u); + LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u); + + if (u->cgroup_path) { + hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); + free(u->cgroup_path); + } + + set_remove(u->manager->failed_units, u); + set_remove(u->manager->startup_units, u); - free(u->cgroup_path); free(u->description); strv_free(u->documentation); free(u->fragment_path); @@ -412,9 +520,14 @@ void unit_free(Unit *u) { strv_free(u->dropin_paths); free(u->instance); + free(u->job_timeout_reboot_arg); + set_free_free(u->names); + unit_unwatch_all_pids(u); + condition_free_list(u->conditions); + condition_free_list(u->asserts); unit_ref_unset(&u->slice); @@ -443,29 +556,38 @@ const char* unit_sub_state_to_string(Unit *u) { return UNIT_VTABLE(u)->sub_state_to_string(u); } -static void complete_move(Set **s, Set **other) { +static int complete_move(Set **s, Set **other) { + int r; + assert(s); assert(other); if (!*other) - return; + return 0; - if (*s) - set_move(*s, *other); - else { + if (*s) { + r = set_move(*s, *other); + if (r < 0) + return r; + } else { *s = *other; *other = NULL; } + + return 0; } -static void merge_names(Unit *u, Unit *other) { +static int merge_names(Unit *u, Unit *other) { char *t; Iterator i; + int r; assert(u); assert(other); - complete_move(&u->names, &other->names); + r = complete_move(&u->names, &other->names); + if (r < 0) + return r; set_free_free(other->names); other->names = NULL; @@ -473,9 +595,32 @@ static void merge_names(Unit *u, Unit *other) { SET_FOREACH(t, u->names, i) assert_se(hashmap_replace(u->manager->units, t, u) == 0); + + return 0; +} + +static int reserve_dependencies(Unit *u, Unit *other, UnitDependency d) { + unsigned n_reserve; + + assert(u); + assert(other); + assert(d < _UNIT_DEPENDENCY_MAX); + + /* + * If u does not have this dependency set allocated, there is no need + * to reserve anything. In that case other's set will be transfered + * as a whole to u by complete_move(). + */ + if (!u->dependencies[d]) + return 0; + + /* merge_dependencies() will skip a u-on-u dependency */ + n_reserve = set_size(other->dependencies[d]) - !!set_get(other->dependencies[d], u); + + return set_reserve(u->dependencies[d], n_reserve); } -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; @@ -488,17 +633,28 @@ static void merge_dependencies(Unit *u, Unit *other, UnitDependency d) { SET_FOREACH(back, other->dependencies[d], i) { UnitDependency k; - for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++) - if ((r = set_remove_and_put(back->dependencies[k], other, u)) < 0) { - + for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++) { + /* 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 == -ENOENT); + assert(r >= 0 || r == -ENOENT); } + } } - complete_move(&u->dependencies[d], &other->dependencies[d]); + /* 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); + + /* The move cannot fail. The caller must have performed a reservation. */ + assert_se(complete_move(&u->dependencies[d], &other->dependencies[d]) == 0); set_free(other->dependencies[d]); other->dependencies[d] = NULL; @@ -506,6 +662,8 @@ static void merge_dependencies(Unit *u, Unit *other, UnitDependency d) { int unit_merge(Unit *u, Unit *other) { UnitDependency d; + const char *other_id = NULL; + int r; assert(u); assert(other); @@ -524,7 +682,7 @@ int unit_merge(Unit *u, Unit *other) { return -EINVAL; if (other->load_state != UNIT_STUB && - other->load_state != UNIT_ERROR) + other->load_state != UNIT_NOT_FOUND) return -EEXIST; if (other->job) @@ -536,8 +694,24 @@ 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); + + /* Make reservations to ensure merge_dependencies() won't fail */ + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + r = reserve_dependencies(u, other, d); + /* + * We don't rollback reservations if we fail. We don't have + * a way to undo reservations. A reservation is not a leak. + */ + if (r < 0) + return r; + } + /* Merge names */ - merge_names(u, other); + r = merge_names(u, other); + if (r < 0) + return r; /* Redirect all references */ while (other->refs) @@ -545,7 +719,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; @@ -565,7 +739,7 @@ int unit_merge(Unit *u, Unit *other) { int unit_merge_by_name(Unit *u, const char *name) { Unit *other; int r; - char *s = NULL; + _cleanup_free_ char *s = NULL; assert(u); assert(name); @@ -574,18 +748,19 @@ int unit_merge_by_name(Unit *u, const char *name) { if (!u->instance) return -EINVAL; - if (!(s = unit_name_replace_instance(name, u->instance))) + s = unit_name_replace_instance(name, u->instance); + if (!s) return -ENOMEM; name = s; } - if (!(other = manager_get_unit(u->manager, name))) + other = manager_get_unit(u->manager, name); + if (!other) r = unit_add_name(u, name); else r = unit_merge(u, other); - free(s); return r; } @@ -604,6 +779,31 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { assert(u); assert(c); + if (c->working_directory) { + r = unit_require_mounts_for(u, c->working_directory); + if (r < 0) + return r; + } + + if (c->root_directory) { + r = unit_require_mounts_for(u, c->root_directory); + if (r < 0) + 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 && @@ -621,11 +821,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; } @@ -643,7 +841,6 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { char *t, **j; UnitDependency d; Iterator i; - char *p2; const char *prefix2; char timestamp1[FORMAT_TIMESTAMP_MAX], @@ -652,14 +849,14 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { timestamp4[FORMAT_TIMESTAMP_MAX], timespan[FORMAT_TIMESPAN_MAX]; Unit *following; + _cleanup_set_free_ Set *following_set = NULL; + int r; 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" @@ -673,10 +870,12 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tInactive Enter Timestamp: %s\n" "%s\tGC Check Good: %s\n" "%s\tNeed Daemon Reload: %s\n" + "%s\tTransient: %s\n" "%s\tSlice: %s\n" "%s\tCGroup: %s\n" "%s\tCGroup realized: %s\n" - "%s\tCGroup mask: 0x%x\n", + "%s\tCGroup mask: 0x%x\n" + "%s\tCGroup members mask: 0x%x\n", prefix, u->id, prefix, unit_description(u), prefix, strna(u->instance), @@ -688,10 +887,12 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->inactive_enter_timestamp.realtime)), prefix, yes_no(unit_check_gc(u)), prefix, yes_no(unit_need_daemon_reload(u)), + prefix, yes_no(u->transient), 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) fprintf(f, "%s\tName: %s\n", prefix, t); @@ -699,9 +900,18 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { STRV_FOREACH(j, u->documentation) fprintf(f, "%s\tDocumentation: %s\n", prefix, *j); - if ((following = unit_following(u))) + following = unit_following(u); + if (following) fprintf(f, "%s\tFollowing: %s\n", prefix, following->id); + r = unit_following_set(u, &following_set); + if (r >= 0) { + Unit *other; + + SET_FOREACH(other, following_set, i) + fprintf(f, "%s\tFollowing Set Member: %s\n", prefix, other->id); + } + if (u->fragment_path) fprintf(f, "%s\tFragment Path: %s\n", prefix, u->fragment_path); @@ -714,7 +924,14 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { if (u->job_timeout > 0) fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0)); - condition_dump_list(u->conditions, f, prefix); + if (u->job_timeout_action != FAILURE_ACTION_NONE) + fprintf(f, "%s\tJob Timeout Action: %s\n", prefix, failure_action_to_string(u->job_timeout_action)); + + if (u->job_timeout_reboot_arg) + fprintf(f, "%s\tJob Timeout Reboot Argument: %s\n", prefix, u->job_timeout_reboot_arg); + + condition_dump_list(u->conditions, f, prefix, condition_type_to_string); + condition_dump_list(u->asserts, f, prefix, assert_type_to_string); if (dual_timestamp_is_set(&u->condition_timestamp)) fprintf(f, @@ -723,6 +940,13 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->condition_timestamp.realtime)), prefix, yes_no(u->condition_result)); + if (dual_timestamp_is_set(&u->assert_timestamp)) + fprintf(f, + "%s\tAssert Timestamp: %s\n" + "%s\tAssert Result: %s\n", + prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->assert_timestamp.realtime)), + prefix, yes_no(u->assert_result)); + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { Unit *other; @@ -747,14 +971,14 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tRefuseManualStart: %s\n" "%s\tRefuseManualStop: %s\n" "%s\tDefaultDependencies: %s\n" - "%s\tOnFailureIsolate: %s\n" + "%s\tOnFailureJobMode: %s\n" "%s\tIgnoreOnIsolate: %s\n" "%s\tIgnoreOnSnapshot: %s\n", prefix, yes_no(u->stop_when_unneeded), prefix, yes_no(u->refuse_manual_start), prefix, yes_no(u->refuse_manual_stop), prefix, yes_no(u->default_dependencies), - prefix, yes_no(u->on_failure_isolate), + prefix, job_mode_to_string(u->on_failure_job_mode), prefix, yes_no(u->ignore_on_isolate), prefix, yes_no(u->ignore_on_snapshot)); @@ -775,7 +999,6 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { if (u->nop_job) job_dump(u->nop_job, f, prefix2); - free(p2); } /* Common implementation for multiple backends */ @@ -784,7 +1007,7 @@ int unit_load_fragment_and_dropin(Unit *u) { assert(u); - /* Load a .service file */ + /* Load a .{service,socket,...} file */ r = unit_load_fragment(u); if (r < 0) return r; @@ -851,7 +1074,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, @@ -862,8 +1085,8 @@ static int unit_add_default_dependencies(Unit *u) { Unit *target; Iterator i; - int r; unsigned k; + int r = 0; assert(u); @@ -874,26 +1097,88 @@ 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); + + 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_dependencies(Unit *u) { + char **i; + int r; + + assert(u); + + STRV_FOREACH(i, u->requires_mounts_for) { + char prefix[strlen(*i) + 1]; + + PATH_FOREACH_PREFIX_MORE(prefix, *i) { + Unit *m; + + r = manager_get_unit_by_path(u->manager, prefix, ".mount", &m); + if (r < 0) + return r; + if (r == 0) + continue; + if (m == u) + continue; + + if (m->load_state != UNIT_LOADED) + continue; + + r = unit_add_dependency(u, UNIT_AFTER, m, true); + if (r < 0) + return r; + + if (m->fragment_path) { + r = unit_add_dependency(u, UNIT_REQUIRES, m, true); + if (r < 0) + return r; + } + } } 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; assert(u); if (u->in_load_queue) { - LIST_REMOVE(Unit, load_queue, u->manager->load_queue, u); + LIST_REMOVE(load_queue, u->manager->load_queue, u); u->in_load_queue = false; } @@ -903,34 +1188,42 @@ int unit_load(Unit *u) { if (u->load_state != UNIT_STUB) return 0; - if (UNIT_VTABLE(u)->load) - if ((r = UNIT_VTABLE(u)->load(u)) < 0) + if (UNIT_VTABLE(u)->load) { + r = UNIT_VTABLE(u)->load(u); + if (r < 0) goto fail; + } if (u->load_state == UNIT_STUB) { r = -ENOENT; goto fail; } - if (u->load_state == UNIT_LOADED && - u->default_dependencies) - if ((r = unit_add_default_dependencies(u)) < 0) + if (u->load_state == UNIT_LOADED) { + + r = unit_add_target_dependencies(u); + if (r < 0) goto fail; - if (u->load_state == UNIT_LOADED) { - r = unit_add_mount_links(u); + r = unit_add_slice_dependencies(u); if (r < 0) - return r; - } + goto fail; + + r = unit_add_mount_dependencies(u); + if (r < 0) + goto fail; - if (u->on_failure_isolate && - set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + r = unit_add_startup_units(u); + if (r < 0) + goto fail; - log_error_unit(u->id, - "More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.", u->id); + 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; + } - r = -EINVAL; - goto fail; + unit_update_cgroup_members_masks(u); } assert((u->load_state != UNIT_MERGED) == !u->merged_into); @@ -941,7 +1234,7 @@ int unit_load(Unit *u) { return 0; fail: - u->load_state = UNIT_ERROR; + u->load_state = u->load_state == UNIT_STUB ? UNIT_NOT_FOUND : UNIT_ERROR; u->load_error = r; unit_add_to_dbus_queue(u); unit_add_to_gc_queue(u); @@ -952,17 +1245,73 @@ fail: return r; } -bool unit_condition_test(Unit *u) { - assert(u); +static bool unit_condition_test_list(Unit *u, Condition *first, const char *(*to_string)(ConditionType t)) { + Condition *c; + int triggered = -1; - dual_timestamp_get(&u->condition_timestamp); - u->condition_result = condition_test_list(u->conditions); + assert(u); + assert(to_string); - return u->condition_result; -} + /* If the condition list is empty, then it is true */ + if (!first) + return true; -_pure_ static const char* unit_get_status_message_format(Unit *u, JobType t) { - const UnitStatusMessageFormats *format_table; + /* Otherwise, if all of the non-trigger conditions apply and + * if any of the trigger conditions apply (unless there are + * none) we return true */ + LIST_FOREACH(conditions, c, first) { + int r; + + r = condition_test(c); + if (r < 0) + log_warning_unit(u->id, + "Couldn't determine result for %s=%s%s%s for %s, assuming failed: %s", + to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter, + u->id, + strerror(-r)); + else + log_debug_unit(u->id, + "%s=%s%s%s %s for %s.", + to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter, + condition_result_to_string(c->result), + u->id); + + if (!c->trigger && r <= 0) + return false; + + if (c->trigger && triggered <= 0) + triggered = r > 0; + } + + return triggered != 0; +} + +static bool unit_condition_test(Unit *u) { + assert(u); + + dual_timestamp_get(&u->condition_timestamp); + u->condition_result = unit_condition_test_list(u, u->conditions, condition_type_to_string); + + return u->condition_result; +} + +static bool unit_assert_test(Unit *u) { + assert(u); + + dual_timestamp_get(&u->assert_timestamp); + u->assert_result = unit_condition_test_list(u, u->asserts, assert_type_to_string); + + return u->assert_result; +} + +_pure_ static const char* unit_get_status_message_format(Unit *u, JobType t) { + const UnitStatusMessageFormats *format_table; assert(u); assert(t >= 0); @@ -1012,11 +1361,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 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]; @@ -1036,8 +1385,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 : @@ -1049,13 +1400,13 @@ 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. * -EALREADY: Unit is already started. * -EAGAIN: An operation is already in progress. Retry later. * -ECANCELED: Too many requests for now. + * -EPROTO: Assert failed */ int unit_start(Unit *u) { UnitActiveState state; @@ -1080,14 +1431,21 @@ int unit_start(Unit *u) { * but we don't want to recheck the condition in that case. */ if (state != UNIT_ACTIVATING && !unit_condition_test(u)) { - log_debug_unit(u->id, "Starting of %s requested but condition failed. Ignoring.", u->id); + log_debug_unit(u->id, "Starting of %s requested but condition failed. Not starting unit.", u->id); return -EALREADY; } + /* If the asserts failed, fail the entire job */ + if (state != UNIT_ACTIVATING && + !unit_assert_test(u)) { + log_debug_unit(u->id, "Starting of %s requested but asserts failed.", u->id); + return -EPROTO; + } + /* Forward to the main object, if we aren't it. */ - if ((following = unit_following(u))) { - log_debug_unit(u->id, "Redirecting start request from %s to %s.", - u->id, following->id); + following = unit_following(u); + if (following) { + log_debug_unit(u->id, "Redirecting start request from %s to %s.", u->id, following->id); return unit_start(following); } @@ -1175,10 +1533,14 @@ int unit_reload(Unit *u) { if (state == UNIT_RELOADING) return -EALREADY; - if (state != UNIT_ACTIVE) + if (state != UNIT_ACTIVE) { + log_warning_unit(u->id, "Unit %s cannot be reloaded because it is inactive.", + u->id); return -ENOEXEC; + } - if ((following = unit_following(u))) { + following = unit_following(u); + if (following) { log_debug_unit(u->id, "Redirecting reload request from %s to %s.", u->id, following->id); return unit_reload(following); @@ -1233,12 +1595,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; @@ -1330,7 +1724,7 @@ void unit_start_on_failure(Unit *u) { SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) { int r; - r = manager_add_job(u->manager, JOB_START, other, u->on_failure_isolate ? JOB_ISOLATE : JOB_REPLACE, true, NULL, NULL); + r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, true, NULL, NULL); if (r < 0) log_error_unit(u->id, "Failed to enqueue OnFailure= job: %s", strerror(-r)); } @@ -1357,12 +1751,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; @@ -1379,16 +1774,32 @@ 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 + * 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--; - else - m->n_on_console++; + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) { + m->n_on_console --; + + if (m->n_on_console == 0) + /* unset no_console_output flag, since the console is free */ + m->no_console_output = false; + } else + m->n_on_console ++; } } @@ -1475,12 +1886,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); } } @@ -1489,7 +1899,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); @@ -1534,154 +1944,154 @@ 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); } -int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w) { - struct epoll_event ev = { - .data.ptr = w, - .events = events, - }; +int unit_watch_pid(Unit *u, pid_t pid) { + int q, r; assert(u); - assert(fd >= 0); - assert(w); - assert(w->type == WATCH_INVALID || (w->type == WATCH_FD && w->fd == fd && w->data.unit == u)); + assert(pid >= 1); - if (epoll_ctl(u->manager->epoll_fd, - w->type == WATCH_INVALID ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, - fd, - &ev) < 0) - return -errno; + /* Watch a specific PID. We only support one or two units + * watching each PID for now, not more. */ - w->fd = fd; - w->type = WATCH_FD; - w->data.unit = u; + r = set_ensure_allocated(&u->pids, NULL); + if (r < 0) + return r; - return 0; -} + r = hashmap_ensure_allocated(&u->manager->watch_pids1, NULL); + if (r < 0) + return r; -void unit_unwatch_fd(Unit *u, Watch *w) { - assert(u); - assert(w); + r = hashmap_put(u->manager->watch_pids1, LONG_TO_PTR(pid), u); + if (r == -EEXIST) { + r = hashmap_ensure_allocated(&u->manager->watch_pids2, NULL); + if (r < 0) + return r; - if (w->type == WATCH_INVALID) - return; + r = hashmap_put(u->manager->watch_pids2, LONG_TO_PTR(pid), u); + } - assert(w->type == WATCH_FD); - assert(w->data.unit == u); - assert_se(epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); + q = set_put(u->pids, LONG_TO_PTR(pid)); + if (q < 0) + return q; - w->fd = -1; - w->type = WATCH_INVALID; - w->data.unit = NULL; + return r; } -int unit_watch_pid(Unit *u, pid_t pid) { +void unit_unwatch_pid(Unit *u, pid_t pid) { assert(u); assert(pid >= 1); - /* Watch a specific PID. We only support one unit watching - * each PID for now. */ - - return hashmap_put(u->manager->watch_pids, LONG_TO_PTR(pid), u); + hashmap_remove_value(u->manager->watch_pids1, LONG_TO_PTR(pid), u); + hashmap_remove_value(u->manager->watch_pids2, LONG_TO_PTR(pid), u); + set_remove(u->pids, LONG_TO_PTR(pid)); } -void unit_unwatch_pid(Unit *u, pid_t pid) { +void unit_unwatch_all_pids(Unit *u) { assert(u); - assert(pid >= 1); - hashmap_remove_value(u->manager->watch_pids, LONG_TO_PTR(pid), u); + while (!set_isempty(u->pids)) + unit_unwatch_pid(u, PTR_TO_LONG(set_first(u->pids))); + + set_free(u->pids); + u->pids = NULL; } -int unit_watch_timer(Unit *u, clockid_t clock_id, bool relative, usec_t usec, Watch *w) { - struct itimerspec its = {}; - int flags, fd; - bool ours; +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; assert(u); - assert(w); - assert(w->type == WATCH_INVALID || (w->type == WATCH_UNIT_TIMER && w->data.unit == u)); + assert(path); - /* This will try to reuse the old timer if there is one */ + /* Adds all PIDs from a specific cgroup path to the set of PIDs we watch. */ - if (w->type == WATCH_UNIT_TIMER) { - assert(w->data.unit == u); - assert(w->fd >= 0); + r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f); + if (r >= 0) { + pid_t pid; - ours = false; - fd = w->fd; - } else if (w->type == WATCH_INVALID) { + while ((r = cg_read_pid(f, &pid)) > 0) { + r = unit_watch_pid(u, pid); + if (r < 0 && ret >= 0) + ret = r; + } + if (r < 0 && ret >= 0) + ret = r; - ours = true; - fd = timerfd_create(clock_id, TFD_NONBLOCK|TFD_CLOEXEC); - if (fd < 0) - return -errno; - } else - assert_not_reached("Invalid watch type"); + } else if (ret >= 0) + ret = r; - if (usec <= 0) { - /* Set absolute time in the past, but not 0, since we - * don't want to disarm the timer */ - its.it_value.tv_sec = 0; - its.it_value.tv_nsec = 1; + r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d); + if (r >= 0) { + char *fn; - flags = TFD_TIMER_ABSTIME; - } else { - timespec_store(&its.it_value, usec); - flags = relative ? 0 : TFD_TIMER_ABSTIME; - } + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; - /* This will also flush the elapse counter */ - if (timerfd_settime(fd, flags, &its, NULL) < 0) - goto fail; + p = strjoin(path, "/", fn, NULL); + free(fn); - if (w->type == WATCH_INVALID) { - struct epoll_event ev = { - .data.ptr = w, - .events = EPOLLIN, - }; + if (!p) + return -ENOMEM; - if (epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) - goto fail; - } + r = unit_watch_pids_in_path(u, p); + if (r < 0 && ret >= 0) + ret = r; + } + if (r < 0 && ret >= 0) + ret = r; - w->type = WATCH_UNIT_TIMER; - w->fd = fd; - w->data.unit = u; + } else if (ret >= 0) + ret = r; - return 0; + return ret; +} -fail: - if (ours) - close_nointr_nofail(fd); +int unit_watch_all_pids(Unit *u) { + assert(u); - return -errno; + /* Adds all PIDs from our cgroup to the set of PIDs we watch */ + + if (!u->cgroup_path) + return -ENOENT; + + return unit_watch_pids_in_path(u, u->cgroup_path); } -void unit_unwatch_timer(Unit *u, Watch *w) { +void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2) { + Iterator i; + void *e; + assert(u); - assert(w); - if (w->type == WATCH_INVALID) - return; + /* Cleans dead PIDs from our list */ - assert(w->type == WATCH_UNIT_TIMER); - assert(w->data.unit == u); - assert(w->fd >= 0); + SET_FOREACH(e, u->pids, i) { + pid_t pid = PTR_TO_LONG(e); - assert_se(epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); - close_nointr_nofail(w->fd); + if (pid == except1 || pid == except2) + continue; - w->fd = -1; - w->type = WATCH_INVALID; - w->data.unit = NULL; + if (!pid_is_unwaited(pid)) + unit_unwatch_pid(u, pid); + } } bool unit_job_is_applicable(Unit *u, JobType j) { @@ -1711,6 +2121,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] = { @@ -1737,8 +2194,10 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen [UNIT_TRIGGERED_BY] = UNIT_TRIGGERS, [UNIT_PROPAGATES_RELOAD_TO] = UNIT_RELOAD_PROPAGATED_FROM, [UNIT_RELOAD_PROPAGATED_FROM] = UNIT_PROPAGATES_RELOAD_TO, + [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); @@ -1749,37 +2208,52 @@ 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; + } - if ((r = set_ensure_allocated(&u->dependencies[d], trivial_hash_func, trivial_compare_func)) < 0) + r = set_ensure_allocated(&u->dependencies[d], NULL); + if (r < 0) return r; - if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) - if ((r = set_ensure_allocated(&other->dependencies[inverse_table[d]], trivial_hash_func, trivial_compare_func)) < 0) + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) { + r = set_ensure_allocated(&other->dependencies[inverse_table[d]], NULL); + if (r < 0) + return r; + } + + if (add_reference) { + r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], NULL); + if (r < 0) return r; - if (add_reference) - if ((r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], trivial_hash_func, trivial_compare_func)) < 0 || - (r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], trivial_hash_func, trivial_compare_func)) < 0) + r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], NULL); + if (r < 0) return r; + } - if ((q = set_put(u->dependencies[d], other)) < 0) + q = set_put(u->dependencies[d], other); + if (q < 0) return q; - if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) - if ((v = set_put(other->dependencies[inverse_table[d]], u)) < 0) { + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID && inverse_table[d] != d) { + v = set_put(other->dependencies[inverse_table[d]], u); + if (v < 0) { r = v; goto fail; } + } if (add_reference) { - if ((w = set_put(u->dependencies[UNIT_REFERENCES], other)) < 0) { + w = set_put(u->dependencies[UNIT_REFERENCES], other); + if (w < 0) { r = w; goto fail; } - if ((r = set_put(other->dependencies[UNIT_REFERENCED_BY], u)) < 0) + r = set_put(other->dependencies[UNIT_REFERENCED_BY], u); + if (r < 0) goto fail; } @@ -1804,10 +2278,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; @@ -1821,7 +2297,7 @@ static const char *resolve_template(Unit *u, const char *name, const char*path, assert(p); if (!name) - name = path_get_file_name(path); + name = basename(path); if (!unit_name_is_template(name)) { *p = NULL; @@ -1867,75 +2343,69 @@ 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; - char *s; 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) - goto finish; - - r = unit_add_two_dependencies(u, d, e, other, add_reference); + r = manager_load_unit(u->manager, name, path, NULL, &other); + if (r < 0) + return r; -finish: - free(s); - 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) { Unit *other; int r; - char *s; + _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) - goto finish; - - r = unit_add_dependency(other, d, u, add_reference); + r = manager_load_unit(u->manager, name, path, NULL, &other); + if (r < 0) + return r; -finish: - free(s); - 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) { Unit *other; int r; - char *s; + _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) - goto finish; + 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) - goto finish; + r = unit_add_two_dependencies(other, d, e, u, add_reference); + if (r < 0) + return r; -finish: - free(s); 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; @@ -1951,7 +2421,7 @@ char *unit_dbus_path(Unit *u) { } char *unit_default_cgroup_path(Unit *u) { - _cleanup_free_ char *escaped_instance = NULL, *slice = NULL; + _cleanup_free_ char *escaped = NULL, *slice = NULL; int r; assert(u); @@ -1965,43 +2435,60 @@ char *unit_default_cgroup_path(Unit *u) { return NULL; } - escaped_instance = cg_escape(u->id); - if (!escaped_instance) + escaped = cg_escape(u->id); + if (!escaped) return NULL; - if (u->instance) { - _cleanup_free_ char *t = NULL, *escaped_template = NULL; - - t = unit_name_template(u->id); - if (!t) - return NULL; - - escaped_template = cg_escape(t); - if (!escaped_template) - return NULL; - - return strjoin(u->manager->cgroup_root, "/", - slice ? slice : "", slice ? "/" : "", - escaped_template, "/", escaped_instance, NULL); - } else - return strjoin(u->manager->cgroup_root, "/", - slice ? slice : "", slice ? "/" : "", - escaped_instance, NULL); + if (slice) + return strjoin(u->manager->cgroup_root, "/", slice, "/", escaped, NULL); + else + 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; + + /* Implicitly place all instantiated units in their + * own per-template slice */ + + prefix = unit_name_to_prefix(u->id); + if (!prefix) + return -ENOMEM; + + /* The prefix is already escaped, but it might include + * "-" which has a special meaning for slice units, + * hence escape it here extra. */ + escaped = strreplace(prefix, "-", "\\x2d"); + if (!escaped) + return -ENOMEM; + + if (u->manager->running_as == SYSTEMD_SYSTEM) + b = strjoin("system-", escaped, ".slice", NULL); + else + b = strappend(escaped, ".slice"); + if (!b) + return -ENOMEM; - r = manager_load_unit(u->manager, u->manager->running_as == SYSTEMD_SYSTEM ? SPECIAL_SYSTEM_SLICE : SPECIAL_ROOT_SLICE, NULL, NULL, &slice); + slice_name = b; + } else + slice_name = + u->manager->running_as == SYSTEMD_SYSTEM + ? SPECIAL_SYSTEM_SLICE + : SPECIAL_ROOT_SLICE; + + r = manager_load_unit(u->manager, slice_name, NULL, NULL, &slice); if (r < 0) return r; @@ -2037,28 +2524,6 @@ int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { return r; } -int unit_get_related_unit(Unit *u, const char *type, Unit **_found) { - _cleanup_free_ char *t = NULL; - Unit *found; - - assert(u); - assert(type); - assert(_found); - - t = unit_name_change_suffix(u->id, type); - if (!t) - return -ENOMEM; - - assert(!unit_has_name(u, t)); - - found = manager_get_unit(u->manager, t); - if (!found) - return -ENOENT; - - *_found = found; - return 0; -} - int unit_watch_bus_name(Unit *u, const char *name) { assert(u); assert(name); @@ -2089,12 +2554,38 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { assert(f); assert(fds); - if (!unit_can_serialize(u)) - return 0; + if (unit_can_serialize(u)) { + ExecRuntime *rt; - if ((r = UNIT_VTABLE(u)->serialize(u, f, fds)) < 0) - return r; + 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); + dual_timestamp_serialize(f, "active-enter-timestamp", &u->active_enter_timestamp); + dual_timestamp_serialize(f, "active-exit-timestamp", &u->active_exit_timestamp); + dual_timestamp_serialize(f, "inactive-enter-timestamp", &u->inactive_enter_timestamp); + dual_timestamp_serialize(f, "condition-timestamp", &u->condition_timestamp); + dual_timestamp_serialize(f, "assert-timestamp", &u->assert_timestamp); + + if (dual_timestamp_is_set(&u->condition_timestamp)) + unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); + + if (dual_timestamp_is_set(&u->assert_timestamp)) + unit_serialize_item(u, f, "assert-result", yes_no(u->assert_result)); + + unit_serialize_item(u, f, "transient", yes_no(u->transient)); + + if (u->cgroup_path) + unit_serialize_item(u, f, "cgroup", u->cgroup_path); if (serialize_jobs) { if (u->job) { @@ -2108,15 +2599,6 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { } } - dual_timestamp_serialize(f, "inactive-exit-timestamp", &u->inactive_exit_timestamp); - dual_timestamp_serialize(f, "active-enter-timestamp", &u->active_enter_timestamp); - dual_timestamp_serialize(f, "active-exit-timestamp", &u->active_exit_timestamp); - dual_timestamp_serialize(f, "inactive-enter-timestamp", &u->inactive_enter_timestamp); - dual_timestamp_serialize(f, "condition-timestamp", &u->condition_timestamp); - - if (dual_timestamp_is_set(&u->condition_timestamp)) - unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); - /* End marker */ fputc('\n', f); return 0; @@ -2150,14 +2632,17 @@ void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { } int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { + 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); for (;;) { char line[LINE_MAX], *l, *v; @@ -2236,44 +2721,101 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { } else if (streq(l, "condition-timestamp")) { dual_timestamp_deserialize(v, &u->condition_timestamp); continue; + } else if (streq(l, "assert-timestamp")) { + dual_timestamp_deserialize(v, &u->assert_timestamp); + continue; } else if (streq(l, "condition-result")) { int b; - if ((b = parse_boolean(v)) < 0) + b = parse_boolean(v); + if (b < 0) log_debug("Failed to parse condition result value %s", v); else u->condition_result = b; continue; - } - if ((r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds)) < 0) - return r; - } -} + } else if (streq(l, "assert-result")) { + int b; -int unit_add_node_link(Unit *u, const char *what, bool wants) { - Unit *device; - char *e; - int r; + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse assert result value %s", v); + else + u->assert_result = b; - assert(u); + continue; - if (!what) - return 0; + } else if (streq(l, "transient")) { + int b; - /* Adds in links to the device node that this unit is based on */ + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse transient bool %s", v); + else + u->transient = b; - if (!is_device_path(what)) - return 0; + continue; + } else if (streq(l, "cgroup")) { + char *s; - e = unit_name_from_path(what, ".device"); - if (!e) - return -ENOMEM; + s = strdup(v); + if (!s) + return -ENOMEM; - r = manager_load_unit(u->manager, e, NULL, NULL, &device); - free(e); - if (r < 0) + 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 (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; + } + } +} + +int unit_add_node_link(Unit *u, const char *what, bool wants) { + Unit *device; + _cleanup_free_ char *e = NULL; + int r; + + assert(u); + + if (!what) + return 0; + + /* Adds in links to the device node that this unit is based on */ + + if (!is_device_path(what)) + return 0; + + e = unit_name_from_path(what, ".device"); + if (!e) + return -ENOMEM; + + r = manager_load_unit(u->manager, e, NULL, NULL, &device); + + if (r < 0) return r; r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_BINDS_TO, device, true); @@ -2314,12 +2856,12 @@ 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) { - manager_status_printf(u->manager, false, status, unit_status_msg_format, unit_description(u)); + DISABLE_WARNING_FORMAT_NONLITERAL; + manager_status_printf(u->manager, STATUS_TYPE_NORMAL, + 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; @@ -2435,7 +2977,7 @@ bool unit_active_or_pending(Unit *u) { return false; } -int unit_kill(Unit *u, KillWho w, int signo, DBusError *error) { +int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error) { assert(u); assert(w >= 0 && w < _KILL_WHO_MAX); assert(signo > 0); @@ -2447,30 +2989,56 @@ int unit_kill(Unit *u, KillWho w, int signo, DBusError *error) { return UNIT_VTABLE(u)->kill(u, w, signo, error); } +static Set *unit_pid_set(pid_t main_pid, pid_t control_pid) { + Set *pid_set; + int r; + + pid_set = set_new(NULL); + if (!pid_set) + return NULL; + + /* Exclude the main/control pids from being killed via the cgroup */ + if (main_pid > 0) { + r = set_put(pid_set, LONG_TO_PTR(main_pid)); + if (r < 0) + goto fail; + } + + if (control_pid > 0) { + r = set_put(pid_set, LONG_TO_PTR(control_pid)); + if (r < 0) + goto fail; + } + + return pid_set; + +fail: + set_free(pid_set); + return NULL; +} + int unit_kill_common( Unit *u, KillWho who, int signo, pid_t main_pid, pid_t control_pid, - DBusError *error) { + sd_bus_error *error) { int r = 0; if (who == KILL_MAIN && main_pid <= 0) { if (main_pid < 0) - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type)); + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type)); else - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); - return -ESRCH; + return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); } if (who == KILL_CONTROL && control_pid <= 0) { if (control_pid < 0) - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no control processes", unit_type_to_string(u->type)); + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no control processes", unit_type_to_string(u->type)); else - dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); - return -ESRCH; + return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); } if (who == KILL_CONTROL || who == KILL_ALL) @@ -2487,23 +3055,11 @@ int unit_kill_common( _cleanup_set_free_ Set *pid_set = NULL; int q; - pid_set = set_new(trivial_hash_func, trivial_compare_func); + /* Exclude the main/control pids from being killed via the cgroup */ + pid_set = unit_pid_set(main_pid, control_pid); if (!pid_set) return -ENOMEM; - /* Exclude the control/main pid from being killed via the cgroup */ - if (control_pid > 0) { - q = set_put(pid_set, LONG_TO_PTR(control_pid)); - if (q < 0) - return q; - } - - if (main_pid > 0) { - q = set_put(pid_set, LONG_TO_PTR(main_pid)); - if (q < 0) - return q; - } - q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, true, false, pid_set); if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT) r = q; @@ -2529,7 +3085,7 @@ UnitFileState unit_get_unit_file_state(Unit *u) { if (u->unit_file_state < 0 && u->fragment_path) u->unit_file_state = unit_file_get_state( u->manager->running_as == SYSTEMD_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, - NULL, path_get_file_name(u->fragment_path)); + NULL, basename(u->fragment_path)); return u->unit_file_state; } @@ -2542,7 +3098,7 @@ Unit* unit_ref_set(UnitRef *ref, Unit *u) { unit_ref_unset(ref); ref->unit = u; - LIST_PREPEND(UnitRef, refs, u->refs, ref); + LIST_PREPEND(refs, u->refs, ref); return u; } @@ -2552,89 +3108,98 @@ void unit_ref_unset(UnitRef *ref) { if (!ref->unit) return; - LIST_REMOVE(UnitRef, refs, ref->unit->refs, ref); + LIST_REMOVE(refs, ref->unit->refs, ref); ref->unit = NULL; } -int unit_add_one_mount_link(Unit *u, Mount *m) { - char **i; +int unit_patch_contexts(Unit *u) { + CGroupContext *cc; + ExecContext *ec; + unsigned i; + int r; assert(u); - assert(m); - if (u->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; + /* Patch in the manager defaults into the exec and cgroup + * contexts, _after_ the rest of the settings have been + * initialized */ - STRV_FOREACH(i, u->requires_mounts_for) { - - if (UNIT(m) == u) - continue; + 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 (!path_startswith(*i, m->where)) - continue; + if (u->manager->running_as == SYSTEMD_USER && + !ec->working_directory) { - return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true); - } + r = get_home_dir(&ec->working_directory); + if (r < 0) + return r; + } - return 0; -} + 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; -int unit_add_mount_links(Unit *u) { - Unit *other; - int r; + if (ec->private_devices) + ec->capability_bounding_set_drop |= (uint64_t) 1ULL << (uint64_t) CAP_MKNOD; + } - assert(u); + cc = unit_get_cgroup_context(u); + if (cc) { - LIST_FOREACH(units_by_type, other, u->manager->units_by_type[UNIT_MOUNT]) { - r = unit_add_one_mount_link(u, MOUNT(other)); - if (r < 0) - return r; + if (ec && + ec->private_devices && + cc->device_policy == CGROUP_AUTO) + cc->device_policy = CGROUP_CLOSED; } return 0; } -int unit_exec_context_defaults(Unit *u, ExecContext *c) { - unsigned i; - int r; - +ExecContext *unit_get_exec_context(Unit *u) { + size_t offset; 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; - } - - if (u->manager->running_as == SYSTEMD_USER && - !c->working_directory) { + if (u->type < 0) + return NULL; - r = get_home_dir(&c->working_directory); - if (r < 0) - return r; - } + offset = UNIT_VTABLE(u)->exec_context_offset; + if (offset <= 0) + return NULL; - return 0; + return (ExecContext*) ((uint8_t*) u + offset); } -ExecContext *unit_get_exec_context(Unit *u) { +KillContext *unit_get_kill_context(Unit *u) { size_t offset; assert(u); - offset = UNIT_VTABLE(u)->exec_context_offset; + if (u->type < 0) + return NULL; + + offset = UNIT_VTABLE(u)->kill_context_offset; if (offset <= 0) return NULL; - return (ExecContext*) ((uint8_t*) u + offset); + return (KillContext*) ((uint8_t*) u + offset); } 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; @@ -2642,70 +3207,97 @@ CGroupContext *unit_get_cgroup_context(Unit *u) { return (CGroupContext*) ((uint8_t*) u + offset); } -static int drop_in_file(Unit *u, UnitSetPropertiesMode mode, const char *name, char **_p, char **_q) { - char *p, *q; - int r; +ExecRuntime *unit_get_exec_runtime(Unit *u) { + size_t offset; - assert(u); - assert(name); - assert(_p); - assert(_q); - assert(mode & (UNIT_PERSISTENT|UNIT_RUNTIME)); + if (u->type < 0) + return NULL; - if (u->manager->running_as == SYSTEMD_USER && !(mode & UNIT_PERSISTENT)) - return -ENOTSUP; + offset = UNIT_VTABLE(u)->exec_runtime_offset; + if (offset <= 0) + return NULL; - if (!filename_is_safe(name)) - return -EINVAL; + return *(ExecRuntime**) ((uint8_t*) u + offset); +} +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; + + if (mode == UNIT_PERSISTENT && !transient) + r = user_config_home(dir); + else + r = user_runtime_dir(dir); - r = user_config_home(&c); - if (r < 0) - return r; 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, "/50-", name, ".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, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(u); + assert(name); + assert(format); + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return unit_write_drop_in(u, mode, name, p); } -int unit_write_drop_in_private_section(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { +int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { _cleanup_free_ char *ndata = NULL; assert(u); @@ -2715,6 +3307,9 @@ int unit_write_drop_in_private_section(Unit *u, UnitSetPropertiesMode mode, cons if (!UNIT_VTABLE(u)->private_section) return -EINVAL; + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + ndata = strjoin("[", UNIT_VTABLE(u)->private_section, "]\n", data, NULL); if (!ndata) return -ENOMEM; @@ -2722,34 +3317,96 @@ int unit_write_drop_in_private_section(Unit *u, UnitSetPropertiesMode mode, cons return unit_write_drop_in(u, mode, name, ndata); } +int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(u); + assert(name); + assert(format); + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return unit_write_drop_in_private(u, mode, name, p); +} + int unit_remove_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name) { _cleanup_free_ char *p = NULL, *q = NULL; int r; 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; + if (unlink(q) < 0) - r = -errno; + r = errno == ENOENT ? 0 : -errno; else - r = 0; + r = 1; rmdir(p); return r; } +int unit_make_transient(Unit *u) { + int r; + + assert(u); + + u->load_state = UNIT_STUB; + u->load_error = 0; + u->transient = true; + + free(u->fragment_path); + u->fragment_path = NULL; + + if (u->manager->running_as == SYSTEMD_USER) { + _cleanup_free_ char *c = NULL; + + r = user_runtime_dir(&c); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + u->fragment_path = strjoin(c, "/", u->id, NULL); + if (!u->fragment_path) + return -ENOMEM; + + mkdir_p(c, 0755); + } else { + u->fragment_path = strappend("/run/systemd/system/", u->id); + if (!u->fragment_path) + return -ENOMEM; + + mkdir_p("/run/systemd/system", 0755); + } + + return write_string_file_atomic_label(u->fragment_path, "# Transient stub"); +} + int unit_kill_context( Unit *u, KillContext *c, - bool sigkill, + KillOperation k, pid_t main_pid, pid_t control_pid, bool main_pid_alien) { - int sig, wait_for_exit = 0, r; + int sig, wait_for_exit = false, r; assert(u); assert(c); @@ -2757,7 +3414,19 @@ int unit_kill_context( if (c->kill_mode == KILL_NONE) return 0; - sig = sigkill ? SIGKILL : c->kill_signal; + switch (k) { + case KILL_KILL: + sig = SIGKILL; + break; + case KILL_ABORT: + sig = SIGABRT; + break; + case KILL_TERMINATE: + sig = c->kill_signal; + break; + default: + assert_not_reached("KillOperation unknown"); + } if (main_pid > 0) { r = kill_and_sigcont(main_pid, sig); @@ -2766,10 +3435,14 @@ int unit_kill_context( _cleanup_free_ char *comm = NULL; get_process_comm(main_pid, &comm); - log_warning_unit(u->id, "Failed to kill main process %li (%s): %s", - (long) main_pid, strna(comm), strerror(-r)); - } else - wait_for_exit = !main_pid_alien; + log_warning_unit(u->id, "Failed to kill main process " PID_FMT " (%s): %s", main_pid, strna(comm), strerror(-r)); + } else { + if (!main_pid_alien) + wait_for_exit = true; + + if (c->send_sighup && k != KILL_KILL) + kill(main_pid, SIGHUP); + } } if (control_pid > 0) { @@ -2779,44 +3452,157 @@ int unit_kill_context( _cleanup_free_ char *comm = NULL; get_process_comm(control_pid, &comm); - log_warning_unit(u->id, - "Failed to kill control process %li (%s): %s", - (long) control_pid, strna(comm), strerror(-r)); - } else + log_warning_unit(u->id, "Failed to kill control process " PID_FMT " (%s): %s", control_pid, strna(comm), strerror(-r)); + } else { wait_for_exit = true; + + if (c->send_sighup && k != KILL_KILL) + kill(control_pid, SIGHUP); + } } - if (c->kill_mode == KILL_CONTROL_GROUP && u->cgroup_path) { + if ((c->kill_mode == KILL_CONTROL_GROUP || (c->kill_mode == KILL_MIXED && k == KILL_KILL)) && u->cgroup_path) { _cleanup_set_free_ Set *pid_set = NULL; - pid_set = set_new(trivial_hash_func, trivial_compare_func); + /* Exclude the main/control pids from being killed via the cgroup */ + pid_set = unit_pid_set(main_pid, control_pid); if (!pid_set) return -ENOMEM; - /* Exclude the main/control pids from being killed via the cgroup */ - if (main_pid > 0) { - r = set_put(pid_set, LONG_TO_PTR(main_pid)); - if (r < 0) - return r; - } - - if (control_pid > 0) { - r = set_put(pid_set, LONG_TO_PTR(control_pid)); - if (r < 0) - return r; - } - r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, sig, true, true, false, pid_set); if (r < 0) { if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) log_warning_unit(u->id, "Failed to kill control group: %s", strerror(-r)); - } else if (r > 0) - wait_for_exit = true; + } else if (r > 0) { + + /* 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 && k != KILL_KILL) { + set_free(pid_set); + + pid_set = unit_pid_set(main_pid, control_pid); + if (!pid_set) + return -ENOMEM; + + cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, SIGHUP, false, true, false, pid_set); + } + } } return wait_for_exit; } +int unit_require_mounts_for(Unit *u, const char *path) { + char prefix[strlen(path) + 1], *p; + int r; + + assert(u); + assert(path); + + /* Registers a unit for requiring a certain path and all its + * prefixes. We keep a simple array of these paths in the + * unit, since its usually short. However, we build a prefix + * table for all possible prefixes so that new appearing mount + * units can easily determine which units to make themselves a + * dependency of. */ + + if (!path_is_absolute(path)) + return -EINVAL; + + p = strdup(path); + if (!p) + return -ENOMEM; + + path_kill_slashes(p); + + if (!path_is_safe(p)) { + free(p); + return -EPERM; + } + + if (strv_contains(u->requires_mounts_for, p)) { + free(p); + return 0; + } + + r = strv_consume(&u->requires_mounts_for, p); + if (r < 0) + return r; + + PATH_FOREACH_PREFIX_MORE(prefix, p) { + Set *x; + + x = hashmap_get(u->manager->units_requiring_mounts_for, prefix); + if (!x) { + char *q; + + if (!u->manager->units_requiring_mounts_for) { + u->manager->units_requiring_mounts_for = hashmap_new(&string_hash_ops); + if (!u->manager->units_requiring_mounts_for) + return -ENOMEM; + } + + q = strdup(prefix); + if (!q) + return -ENOMEM; + + x = set_new(NULL); + if (!x) { + free(q); + return -ENOMEM; + } + + r = hashmap_put(u->manager->units_requiring_mounts_for, q, x); + if (r < 0) { + free(q); + set_free(x); + return r; + } + } + + r = set_put(x, u); + if (r < 0) + return r; + } + + return 0; +} + +int unit_setup_exec_runtime(Unit *u) { + ExecRuntime **rt; + size_t offset; + Iterator i; + Unit *other; + + offset = UNIT_VTABLE(u)->exec_runtime_offset; + assert(offset > 0); + + /* Check if there already is an ExecRuntime for this unit? */ + rt = (ExecRuntime**) ((uint8_t*) u + offset); + if (*rt) + return 0; + + /* Try to get it from somebody else */ + SET_FOREACH(other, u->dependencies[UNIT_JOINS_NAMESPACE_OF], i) { + + *rt = unit_get_exec_runtime(other); + if (*rt) { + exec_runtime_ref(*rt); + return 0; + } + } + + return exec_runtime_make(rt, unit_get_exec_context(u), u->id); +} + static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { [UNIT_ACTIVE] = "active", [UNIT_RELOADING] = "reloading", @@ -2827,31 +3613,3 @@ static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); - -static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { - [UNIT_REQUIRES] = "Requires", - [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable", - [UNIT_REQUISITE] = "Requisite", - [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable", - [UNIT_WANTS] = "Wants", - [UNIT_BINDS_TO] = "BindsTo", - [UNIT_PART_OF] = "PartOf", - [UNIT_REQUIRED_BY] = "RequiredBy", - [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable", - [UNIT_WANTED_BY] = "WantedBy", - [UNIT_BOUND_BY] = "BoundBy", - [UNIT_CONSISTS_OF] = "ConsistsOf", - [UNIT_CONFLICTS] = "Conflicts", - [UNIT_CONFLICTED_BY] = "ConflictedBy", - [UNIT_BEFORE] = "Before", - [UNIT_AFTER] = "After", - [UNIT_ON_FAILURE] = "OnFailure", - [UNIT_TRIGGERS] = "Triggers", - [UNIT_TRIGGERED_BY] = "TriggeredBy", - [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo", - [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom", - [UNIT_REFERENCES] = "References", - [UNIT_REFERENCED_BY] = "ReferencedBy", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);