X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=src%2Fcore%2Funit.c;h=921971c1c0d3a303614fab74636e8ba130d27788;hb=53e1b683907c2f12330f00feb9630150196f064d;hp=9eda9643f6039f182e73efa774f921186aa15d0d;hpb=21f0669163ca163de814fb887098dca6d17c705a;p=thirdparty%2Fsystemd.git diff --git a/src/core/unit.c b/src/core/unit.c index 9eda9643f60..921971c1c0d 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ /*** This file is part of systemd. @@ -35,9 +36,12 @@ #include "dropin.h" #include "escape.h" #include "execute.h" +#include "fd-util.h" #include "fileio-label.h" #include "format-util.h" +#include "fs-util.h" #include "id128-util.h" +#include "io-util.h" #include "load-dropin.h" #include "load-fragment.h" #include "log.h" @@ -49,9 +53,11 @@ #include "process-util.h" #include "set.h" #include "signal-util.h" +#include "sparse-endian.h" #include "special.h" #include "stat-util.h" #include "stdio-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "umask-util.h" @@ -71,7 +77,7 @@ const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_TIMER] = &timer_vtable, [UNIT_PATH] = &path_vtable, [UNIT_SLICE] = &slice_vtable, - [UNIT_SCOPE] = &scope_vtable + [UNIT_SCOPE] = &scope_vtable, }; static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency); @@ -103,6 +109,13 @@ Unit *unit_new(Manager *m, size_t size) { u->ref_gid = GID_INVALID; u->cpu_usage_last = NSEC_INFINITY; + u->ip_accounting_ingress_map_fd = -1; + u->ip_accounting_egress_map_fd = -1; + u->ipv4_allow_map_fd = -1; + u->ipv6_allow_map_fd = -1; + u->ipv4_deny_map_fd = -1; + u->ipv6_deny_map_fd = -1; + RATELIMIT_INIT(u->start_limit, m->default_start_limit_interval, m->default_start_limit_burst); RATELIMIT_INIT(u->auto_stop_ratelimit, 10 * USEC_PER_SEC, 16); @@ -153,18 +166,24 @@ static void unit_init(Unit *u) { cc->cpu_accounting = u->manager->default_cpu_accounting; cc->io_accounting = u->manager->default_io_accounting; + cc->ip_accounting = u->manager->default_ip_accounting; cc->blockio_accounting = u->manager->default_blockio_accounting; cc->memory_accounting = u->manager->default_memory_accounting; cc->tasks_accounting = u->manager->default_tasks_accounting; + cc->ip_accounting = u->manager->default_ip_accounting; if (u->type != UNIT_SLICE) cc->tasks_max = u->manager->default_tasks_max; } ec = unit_get_exec_context(u); - if (ec) + if (ec) { exec_context_init(ec); + ec->keyring_mode = MANAGER_IS_SYSTEM(u->manager) ? + EXEC_KEYRING_PRIVATE : EXEC_KEYRING_INHERIT; + } + kc = unit_get_kill_context(u); if (kc) kill_context_init(kc); @@ -299,30 +318,27 @@ int unit_choose_id(Unit *u, const char *name) { } int unit_set_description(Unit *u, const char *description) { - char *s; + int r; assert(u); - if (isempty(description)) - s = NULL; - else { - s = strdup(description); - if (!s) - return -ENOMEM; - } - - free(u->description); - u->description = s; + r = free_and_strdup(&u->description, empty_to_null(description)); + if (r < 0) + return r; + if (r > 0) + unit_add_to_dbus_queue(u); - unit_add_to_dbus_queue(u); return 0; } bool unit_check_gc(Unit *u) { UnitActiveState state; - bool inactive; + assert(u); + /* Checks whether the unit is ready to be unloaded for garbage collection. Returns true, when the unit shall + * stay around, false if there's no reason to keep it loaded. */ + if (u->job) return true; @@ -330,18 +346,11 @@ bool unit_check_gc(Unit *u) { return true; state = unit_active_state(u); - inactive = state == UNIT_INACTIVE; - /* If the unit is inactive and failed and no job is queued for - * it, then release its runtime resources */ + /* If the unit is inactive and failed and no job is queued for it, then release its runtime resources */ if (UNIT_IS_INACTIVE_OR_FAILED(state) && UNIT_VTABLE(u)->release_resources) - UNIT_VTABLE(u)->release_resources(u, inactive); - - /* But we keep the unit object around for longer when it is - * referenced or configured to not be gc'ed */ - if (!inactive) - return true; + UNIT_VTABLE(u)->release_resources(u); if (u->perpetual) return true; @@ -352,6 +361,25 @@ bool unit_check_gc(Unit *u) { if (sd_bus_track_count(u->bus_track) > 0) return true; + /* But we keep the unit object around for longer when it is referenced or configured to not be gc'ed */ + switch (u->collect_mode) { + + case COLLECT_INACTIVE: + if (state != UNIT_INACTIVE) + return true; + + break; + + case COLLECT_INACTIVE_OR_FAILED: + if (!IN_SET(state, UNIT_INACTIVE, UNIT_FAILED)) + return true; + + break; + + default: + assert_not_reached("Unknown garbage collection mode"); + } + if (UNIT_VTABLE(u)->check_gc) if (UNIT_VTABLE(u)->check_gc(u)) return true; @@ -412,25 +440,25 @@ void unit_add_to_dbus_queue(Unit *u) { u->in_dbus_queue = true; } -static void bidi_set_free(Unit *u, Set *s) { - Iterator i; +static void bidi_set_free(Unit *u, Hashmap *h) { Unit *other; + Iterator i; + void *v; assert(u); - /* Frees the set and makes sure we are dropped from the - * inverse pointers */ + /* Frees the hashmap and makes sure we are dropped from the inverse pointers */ - SET_FOREACH(other, s, i) { + HASHMAP_FOREACH_KEY(v, other, h, i) { UnitDependency d; for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - set_remove(other->dependencies[d], u); + hashmap_remove(other->dependencies[d], u); unit_add_to_gc_queue(other); } - set_free(s); + hashmap_free(h); } static void unit_remove_transient(Unit *u) { @@ -465,30 +493,37 @@ static void unit_remove_transient(Unit *u) { } static void unit_free_requires_mounts_for(Unit *u) { - char **j; + assert(u); - STRV_FOREACH(j, u->requires_mounts_for) { - char s[strlen(*j) + 1]; + for (;;) { + _cleanup_free_ char *path; - PATH_FOREACH_PREFIX_MORE(s, *j) { - char *y; - Set *x; + path = hashmap_steal_first_key(u->requires_mounts_for); + if (!path) + break; + else { + char s[strlen(path) + 1]; - x = hashmap_get2(u->manager->units_requiring_mounts_for, s, (void**) &y); - if (!x) - continue; + PATH_FOREACH_PREFIX_MORE(s, path) { + char *y; + Set *x; - set_remove(x, u); + x = hashmap_get2(u->manager->units_requiring_mounts_for, s, (void**) &y); + if (!x) + continue; - if (set_isempty(x)) { - hashmap_remove(u->manager->units_requiring_mounts_for, y); - free(y); - set_free(x); + (void) set_remove(x, u); + + if (set_isempty(x)) { + (void) hashmap_remove(u->manager->units_requiring_mounts_for, y); + free(y); + set_free(x); + } } } } - u->requires_mounts_for = strv_free(u->requires_mounts_for); + u->requires_mounts_for = hashmap_free(u->requires_mounts_for); } static void unit_done(Unit *u) { @@ -573,11 +608,17 @@ void unit_free(Unit *u) { if (u->in_gc_queue) LIST_REMOVE(gc_queue, u->manager->gc_unit_queue, u); - if (u->in_cgroup_queue) - LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u); + if (u->in_cgroup_realize_queue) + LIST_REMOVE(cgroup_realize_queue, u->manager->cgroup_realize_queue, u); + + if (u->in_cgroup_empty_queue) + LIST_REMOVE(cgroup_empty_queue, u->manager->cgroup_empty_queue, u); unit_release_cgroup(u); + if (!MANAGER_IS_RELOADING(u->manager)) + unit_unlink_state_files(u); + unit_unref_uid_gid(u, false); (void) manager_update_failed_units(u->manager, u, false); @@ -606,6 +647,17 @@ void unit_free(Unit *u) { while (u->refs) unit_ref_unset(u->refs); + safe_close(u->ip_accounting_ingress_map_fd); + safe_close(u->ip_accounting_egress_map_fd); + + safe_close(u->ipv4_allow_map_fd); + safe_close(u->ipv6_allow_map_fd); + safe_close(u->ipv4_deny_map_fd); + safe_close(u->ipv6_deny_map_fd); + + bpf_program_unref(u->ip_bpf_ingress); + bpf_program_unref(u->ip_bpf_egress); + free(u); } @@ -628,20 +680,33 @@ const char* unit_sub_state_to_string(Unit *u) { return UNIT_VTABLE(u)->sub_state_to_string(u); } -static int complete_move(Set **s, Set **other) { - int r; +static int set_complete_move(Set **s, Set **other) { + assert(s); + assert(other); + + if (!other) + return 0; + if (*s) + return set_move(*s, *other); + else { + *s = *other; + *other = NULL; + } + + return 0; +} + +static int hashmap_complete_move(Hashmap **s, Hashmap **other) { assert(s); assert(other); if (!*other) return 0; - if (*s) { - r = set_move(*s, *other); - if (r < 0) - return r; - } else { + if (*s) + return hashmap_move(*s, *other); + else { *s = *other; *other = NULL; } @@ -657,7 +722,7 @@ static int merge_names(Unit *u, Unit *other) { assert(u); assert(other); - r = complete_move(&u->names, &other->names); + r = set_complete_move(&u->names, &other->names); if (r < 0) return r; @@ -687,48 +752,73 @@ static int reserve_dependencies(Unit *u, Unit *other, UnitDependency 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); + n_reserve = hashmap_size(other->dependencies[d]) - !!hashmap_get(other->dependencies[d], u); - return set_reserve(u->dependencies[d], n_reserve); + return hashmap_reserve(u->dependencies[d], n_reserve); } static void merge_dependencies(Unit *u, Unit *other, const char *other_id, UnitDependency d) { Iterator i; Unit *back; + void *v; int r; + /* Merges all dependencies of type 'd' of the unit 'other' into the deps of the unit 'u' */ + assert(u); assert(other); assert(d < _UNIT_DEPENDENCY_MAX); - /* Fix backwards pointers */ - SET_FOREACH(back, other->dependencies[d], i) { + /* Fix backwards pointers. Let's iterate through all dependendent units of the other unit. */ + HASHMAP_FOREACH_KEY(v, back, other->dependencies[d], i) { UnitDependency k; + /* Let's now iterate through the dependencies of that dependencies of the other units, looking for + * pointers back, and let's fix them up, to instead point to 'u'. */ + 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)) + /* Do not add dependencies between u and itself. */ + if (hashmap_remove(back->dependencies[k], other)) maybe_warn_about_dependency(u, 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); + UnitDependencyInfo di_u, di_other, di_merged; + + /* Let's drop this dependency between "back" and "other", and let's create it between + * "back" and "u" instead. Let's merge the bit masks of the dependency we are moving, + * and any such dependency which might already exist */ + + di_other.data = hashmap_get(back->dependencies[k], other); + if (!di_other.data) + continue; /* dependency isn't set, let's try the next one */ + + di_u.data = hashmap_get(back->dependencies[k], u); + + di_merged = (UnitDependencyInfo) { + .origin_mask = di_u.origin_mask | di_other.origin_mask, + .destination_mask = di_u.destination_mask | di_other.destination_mask, + }; + + r = hashmap_remove_and_replace(back->dependencies[k], other, u, di_merged.data); + if (r < 0) + log_warning_errno(r, "Failed to remove/replace: back=%s other=%s u=%s: %m", back->id, other_id, u->id); + assert(r >= 0); + + /* assert_se(hashmap_remove_and_replace(back->dependencies[k], other, u, di_merged.data) >= 0); */ } } + } /* Also do not move dependencies on u to itself */ - back = set_remove(other->dependencies[d], u); + back = hashmap_remove(other->dependencies[d], u); if (back) maybe_warn_about_dependency(u, 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); + assert_se(hashmap_complete_move(&u->dependencies[d], &other->dependencies[d]) == 0); - other->dependencies[d] = set_free(other->dependencies[d]); + other->dependencies[d] = hashmap_free(other->dependencies[d]); } int unit_merge(Unit *u, Unit *other) { @@ -755,8 +845,7 @@ int unit_merge(Unit *u, Unit *other) { if (!unit_type_may_alias(u->type)) /* Merging only applies to unit names that support aliases */ return -EEXIST; - if (other->load_state != UNIT_STUB && - other->load_state != UNIT_NOT_FOUND) + if (!IN_SET(other->load_state, UNIT_STUB, UNIT_NOT_FOUND)) return -EEXIST; if (other->job) @@ -854,24 +943,24 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { assert(c); if (c->working_directory) { - r = unit_require_mounts_for(u, c->working_directory); + r = unit_require_mounts_for(u, c->working_directory, UNIT_DEPENDENCY_FILE); if (r < 0) return r; } if (c->root_directory) { - r = unit_require_mounts_for(u, c->root_directory); + r = unit_require_mounts_for(u, c->root_directory, UNIT_DEPENDENCY_FILE); if (r < 0) return r; } if (c->root_image) { - r = unit_require_mounts_for(u, c->root_image); + r = unit_require_mounts_for(u, c->root_image, UNIT_DEPENDENCY_FILE); if (r < 0) return r; } - for (dt = 0; dt < _EXEC_DIRECTORY_MAX; dt++) { + for (dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) { if (!u->manager->prefix[dt]) continue; @@ -882,7 +971,7 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { if (!p) return -ENOMEM; - r = unit_require_mounts_for(u, p); + r = unit_require_mounts_for(u, p, UNIT_DEPENDENCY_FILE); if (r < 0) return r; } @@ -895,12 +984,12 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { const char *p; FOREACH_STRING(p, "/tmp", "/var/tmp") { - r = unit_require_mounts_for(u, p); + r = unit_require_mounts_for(u, p, UNIT_DEPENDENCY_FILE); if (r < 0) return r; } - r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_TMPFILES_SETUP_SERVICE, NULL, true); + r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_TMPFILES_SETUP_SERVICE, NULL, true, UNIT_DEPENDENCY_FILE); if (r < 0) return r; } @@ -918,7 +1007,7 @@ 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. */ - r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true); + r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true, UNIT_DEPENDENCY_FILE); if (r < 0) return r; @@ -934,6 +1023,48 @@ const char *unit_description(Unit *u) { return strna(u->id); } +static void print_unit_dependency_mask(FILE *f, const char *kind, UnitDependencyMask mask, bool *space) { + const struct { + UnitDependencyMask mask; + const char *name; + } table[] = { + { UNIT_DEPENDENCY_FILE, "file" }, + { UNIT_DEPENDENCY_IMPLICIT, "implicit" }, + { UNIT_DEPENDENCY_DEFAULT, "default" }, + { UNIT_DEPENDENCY_UDEV, "udev" }, + { UNIT_DEPENDENCY_PATH, "path" }, + { UNIT_DEPENDENCY_MOUNTINFO_IMPLICIT, "mountinfo-implicit" }, + { UNIT_DEPENDENCY_MOUNTINFO_DEFAULT, "mountinfo-default" }, + { UNIT_DEPENDENCY_PROC_SWAP, "proc-swap" }, + }; + size_t i; + + assert(f); + assert(kind); + assert(space); + + for (i = 0; i < ELEMENTSOF(table); i++) { + + if (mask == 0) + break; + + if ((mask & table[i].mask) == table[i].mask) { + if (*space) + fputc(' ', f); + else + *space = true; + + fputs(kind, f); + fputs("-", f); + fputs(table[i].name, f); + + mask &= ~table[i].mask; + } + } + + assert(mask == 0); +} + void unit_dump(Unit *u, FILE *f, const char *prefix) { char *t, **j; UnitDependency d; @@ -948,8 +1079,9 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { timespan[FORMAT_TIMESPAN_MAX]; Unit *following; _cleanup_set_free_ Set *following_set = NULL; - int r; const char *n; + CGroupMask m; + int r; assert(u); assert(u->type >= 0); @@ -972,6 +1104,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tNeed Daemon Reload: %s\n" "%s\tTransient: %s\n" "%s\tPerpetual: %s\n" + "%s\tGarbage Collection Mode: %s\n" "%s\tSlice: %s\n" "%s\tCGroup: %s\n" "%s\tCGroup realized: %s\n", @@ -989,6 +1122,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, yes_no(unit_need_daemon_reload(u)), prefix, yes_no(u->transient), prefix, yes_no(u->perpetual), + prefix, collect_mode_to_string(u->collect_mode), prefix, strna(unit_slice_name(u)), prefix, strna(u->cgroup_path), prefix, yes_no(u->cgroup_realized)); @@ -996,11 +1130,23 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { if (u->cgroup_realized_mask != 0) { _cleanup_free_ char *s = NULL; (void) cg_mask_to_string(u->cgroup_realized_mask, &s); - fprintf(f, "%s\tCGroup mask: %s\n", prefix, strnull(s)); + fprintf(f, "%s\tCGroup realized mask: %s\n", prefix, strnull(s)); } - if (u->cgroup_members_mask != 0) { + if (u->cgroup_enabled_mask != 0) { _cleanup_free_ char *s = NULL; - (void) cg_mask_to_string(u->cgroup_members_mask, &s); + (void) cg_mask_to_string(u->cgroup_enabled_mask, &s); + fprintf(f, "%s\tCGroup enabled mask: %s\n", prefix, strnull(s)); + } + m = unit_get_own_mask(u); + if (m != 0) { + _cleanup_free_ char *s = NULL; + (void) cg_mask_to_string(m, &s); + fprintf(f, "%s\tCGroup own mask: %s\n", prefix, strnull(s)); + } + m = unit_get_members_mask(u); + if (m != 0) { + _cleanup_free_ char *s = NULL; + (void) cg_mask_to_string(m, &s); fprintf(f, "%s\tCGroup members mask: %s\n", prefix, strnull(s)); } @@ -1062,20 +1208,35 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, yes_no(u->assert_result)); for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + UnitDependencyInfo di; Unit *other; - SET_FOREACH(other, u->dependencies[d], i) - fprintf(f, "%s\t%s: %s\n", prefix, unit_dependency_to_string(d), other->id); + HASHMAP_FOREACH_KEY(di.data, other, u->dependencies[d], i) { + bool space = false; + + fprintf(f, "%s\t%s: %s (", prefix, unit_dependency_to_string(d), other->id); + + print_unit_dependency_mask(f, "origin", di.origin_mask, &space); + print_unit_dependency_mask(f, "destination", di.destination_mask, &space); + + fputs(")\n", f); + } } - if (!strv_isempty(u->requires_mounts_for)) { - fprintf(f, - "%s\tRequiresMountsFor:", prefix); + if (!hashmap_isempty(u->requires_mounts_for)) { + UnitDependencyInfo di; + const char *path; + + HASHMAP_FOREACH_KEY(di.data, path, u->requires_mounts_for, i) { + bool space = false; - STRV_FOREACH(j, u->requires_mounts_for) - fprintf(f, " %s", *j); + fprintf(f, "%s\tRequiresMountsFor: %s (", prefix, path); - fputs("\n", f); + print_unit_dependency_mask(f, "origin", di.origin_mask, &space); + print_unit_dependency_mask(f, "destination", di.destination_mask, &space); + + fputs(")\n", f); + } } if (u->load_state == UNIT_LOADED) { @@ -1176,10 +1337,10 @@ int unit_add_default_target_dependency(Unit *u, Unit *target) { return 0; /* Don't create loops */ - if (set_get(target->dependencies[UNIT_BEFORE], u)) + if (hashmap_get(target->dependencies[UNIT_BEFORE], u)) return 0; - return unit_add_dependency(target, UNIT_AFTER, u, true); + return unit_add_dependency(target, UNIT_AFTER, u, true, UNIT_DEPENDENCY_DEFAULT); } static int unit_add_target_dependencies(Unit *u) { @@ -1191,48 +1352,59 @@ static int unit_add_target_dependencies(Unit *u) { UNIT_BOUND_BY }; - Unit *target; - Iterator i; unsigned k; int r = 0; assert(u); - for (k = 0; k < ELEMENTSOF(deps); k++) - SET_FOREACH(target, u->dependencies[deps[k]], i) { + for (k = 0; k < ELEMENTSOF(deps); k++) { + Unit *target; + Iterator i; + void *v; + + HASHMAP_FOREACH_KEY(v, target, u->dependencies[deps[k]], i) { r = unit_add_default_target_dependency(u, target); if (r < 0) return r; } + } return r; } static int unit_add_slice_dependencies(Unit *u) { + UnitDependencyMask mask; assert(u); if (!UNIT_HAS_CGROUP_CONTEXT(u)) return 0; + /* Slice units are implicitly ordered against their parent slices (as this relationship is encoded in the + name), while all other units are ordered based on configuration (as in their case Slice= configures the + relationship). */ + mask = u->type == UNIT_SLICE ? UNIT_DEPENDENCY_IMPLICIT : UNIT_DEPENDENCY_FILE; + if (UNIT_ISSET(u->slice)) - return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT_DEREF(u->slice), true); + return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT_DEREF(u->slice), true, mask); if (unit_has_name(u, SPECIAL_ROOT_SLICE)) return 0; - return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_ROOT_SLICE, NULL, true); + return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_ROOT_SLICE, NULL, true, mask); } static int unit_add_mount_dependencies(Unit *u) { - char **i; + UnitDependencyInfo di; + const char *path; + Iterator i; int r; assert(u); - STRV_FOREACH(i, u->requires_mounts_for) { - char prefix[strlen(*i) + 1]; + HASHMAP_FOREACH_KEY(di.data, path, u->requires_mounts_for, i) { + char prefix[strlen(path) + 1]; - PATH_FOREACH_PREFIX_MORE(prefix, *i) { + PATH_FOREACH_PREFIX_MORE(prefix, path) { _cleanup_free_ char *p = NULL; Unit *m; @@ -1256,12 +1428,12 @@ static int unit_add_mount_dependencies(Unit *u) { if (m->load_state != UNIT_LOADED) continue; - r = unit_add_dependency(u, UNIT_AFTER, m, true); + r = unit_add_dependency(u, UNIT_AFTER, m, true, di.origin_mask); if (r < 0) return r; if (m->fragment_path) { - r = unit_add_dependency(u, UNIT_REQUIRES, m, true); + r = unit_add_dependency(u, UNIT_REQUIRES, m, true, di.origin_mask); if (r < 0) return r; } @@ -1347,7 +1519,7 @@ int unit_load(Unit *u) { if (r < 0) goto fail; - if (u->on_failure_job_mode == JOB_ISOLATE && set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + if (u->on_failure_job_mode == JOB_ISOLATE && hashmap_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { log_unit_error(u, "More than one OnFailure= dependencies specified but OnFailureJobMode=isolate set. Refusing."); r = -EINVAL; goto fail; @@ -1519,6 +1691,7 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { log_struct(LOG_INFO, LOG_MESSAGE("%s", buf), LOG_UNIT_ID(u), + LOG_UNIT_INVOCATION_ID(u), mid, NULL); } @@ -1561,6 +1734,7 @@ bool unit_shall_confirm_spawn(Unit *u) { static bool unit_verify_deps(Unit *u) { Unit *other; Iterator j; + void *v; assert(u); @@ -1569,9 +1743,9 @@ static bool unit_verify_deps(Unit *u) { * processing, but do not have any effect afterwards. We don't check BindsTo= dependencies that are not used in * conjunction with After= as for them any such check would make things entirely racy. */ - SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], j) { + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO], j) { - if (!set_contains(u->dependencies[UNIT_AFTER], other)) + if (!hashmap_contains(u->dependencies[UNIT_AFTER], other)) continue; if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(other))) { @@ -1774,7 +1948,7 @@ bool unit_can_reload(Unit *u) { if (UNIT_VTABLE(u)->can_reload) return UNIT_VTABLE(u)->can_reload(u); - if (!set_isempty(u->dependencies[UNIT_PROPAGATES_RELOAD_TO])) + if (!hashmap_isempty(u->dependencies[UNIT_PROPAGATES_RELOAD_TO])) return true; return UNIT_VTABLE(u)->reload; @@ -1791,8 +1965,6 @@ static void unit_check_unneeded(Unit *u) { UNIT_BOUND_BY, }; - Unit *other; - Iterator i; unsigned j; int r; @@ -1807,10 +1979,15 @@ static void unit_check_unneeded(Unit *u) { if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) return; - for (j = 0; j < ELEMENTSOF(needed_dependencies); j++) - SET_FOREACH(other, u->dependencies[needed_dependencies[j]], i) + for (j = 0; j < ELEMENTSOF(needed_dependencies); j++) { + Unit *other; + Iterator i; + void *v; + + HASHMAP_FOREACH_KEY(v, other, u->dependencies[needed_dependencies[j]], i) if (unit_active_or_pending(other)) return; + } /* If stopping a unit fails continuously we might enter a stop * loop here, hence stop acting on the service being @@ -1833,6 +2010,7 @@ static void unit_check_binds_to(Unit *u) { bool stop = false; Unit *other; Iterator i; + void *v; int r; assert(u); @@ -1843,7 +2021,7 @@ static void unit_check_binds_to(Unit *u) { if (unit_active_state(u) != UNIT_ACTIVE) return; - SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) { + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO], i) { if (other->job) continue; @@ -1881,65 +2059,68 @@ static void unit_check_binds_to(Unit *u) { static void retroactively_start_dependencies(Unit *u) { Iterator i; Unit *other; + void *v; assert(u); assert(UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))); - SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) - if (!set_get(u->dependencies[UNIT_AFTER], other) && + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUIRES], i) + if (!hashmap_get(u->dependencies[UNIT_AFTER], other) && !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL); - SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) - if (!set_get(u->dependencies[UNIT_AFTER], other) && + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO], i) + if (!hashmap_get(u->dependencies[UNIT_AFTER], other) && !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL); - SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) - if (!set_get(u->dependencies[UNIT_AFTER], other) && + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_WANTS], i) + if (!hashmap_get(u->dependencies[UNIT_AFTER], other) && !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL); - SET_FOREACH(other, u->dependencies[UNIT_CONFLICTS], i) + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_CONFLICTS], i) if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); - SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i) + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_CONFLICTED_BY], i) if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); } static void retroactively_stop_dependencies(Unit *u) { - Iterator i; Unit *other; + Iterator i; + void *v; assert(u); assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); /* Pull down units which are bound to us recursively if enabled */ - SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BOUND_BY], i) if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); } static void check_unneeded_dependencies(Unit *u) { - Iterator i; Unit *other; + Iterator i; + void *v; assert(u); assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); /* Garbage collect services that might not be needed anymore, if enabled */ - SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUIRES], i) if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_WANTS], i) if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], i) + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUISITE], i) if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO], i) if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) unit_check_unneeded(other); } @@ -1947,15 +2128,16 @@ static void check_unneeded_dependencies(Unit *u) { void unit_start_on_failure(Unit *u) { Unit *other; Iterator i; + void *v; assert(u); - if (set_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) + if (hashmap_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) return; log_unit_info(u, "Triggering OnFailure= dependencies."); - SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) { + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_ON_FAILURE], i) { int r; r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, NULL, NULL); @@ -1967,14 +2149,143 @@ void unit_start_on_failure(Unit *u) { void unit_trigger_notify(Unit *u) { Unit *other; Iterator i; + void *v; assert(u); - SET_FOREACH(other, u->dependencies[UNIT_TRIGGERED_BY], i) + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_TRIGGERED_BY], i) if (UNIT_VTABLE(other)->trigger_notify) UNIT_VTABLE(other)->trigger_notify(other, u); } +static int unit_log_resources(Unit *u) { + + struct iovec iovec[1 + _CGROUP_IP_ACCOUNTING_METRIC_MAX + 4]; + size_t n_message_parts = 0, n_iovec = 0; + char* message_parts[3 + 1], *t; + nsec_t nsec = NSEC_INFINITY; + CGroupIPAccountingMetric m; + size_t i; + int r; + const char* const ip_fields[_CGROUP_IP_ACCOUNTING_METRIC_MAX] = { + [CGROUP_IP_INGRESS_BYTES] = "IP_METRIC_INGRESS_BYTES", + [CGROUP_IP_INGRESS_PACKETS] = "IP_METRIC_INGRESS_PACKETS", + [CGROUP_IP_EGRESS_BYTES] = "IP_METRIC_EGRESS_BYTES", + [CGROUP_IP_EGRESS_PACKETS] = "IP_METRIC_EGRESS_PACKETS", + }; + + assert(u); + + /* Invoked whenever a unit enters failed or dead state. Logs information about consumed resources if resource + * accounting was enabled for a unit. It does this in two ways: a friendly human readable string with reduced + * information and the complete data in structured fields. */ + + (void) unit_get_cpu_usage(u, &nsec); + if (nsec != NSEC_INFINITY) { + char buf[FORMAT_TIMESPAN_MAX] = ""; + + /* Format the CPU time for inclusion in the structured log message */ + if (asprintf(&t, "CPU_USAGE_NSEC=%" PRIu64, nsec) < 0) { + r = log_oom(); + goto finish; + } + iovec[n_iovec++] = IOVEC_MAKE_STRING(t); + + /* Format the CPU time for inclusion in the human language message string */ + format_timespan(buf, sizeof(buf), nsec / NSEC_PER_USEC, USEC_PER_MSEC); + t = strjoin(n_message_parts > 0 ? "consumed " : "Consumed ", buf, " CPU time"); + if (!t) { + r = log_oom(); + goto finish; + } + + message_parts[n_message_parts++] = t; + } + + for (m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) { + char buf[FORMAT_BYTES_MAX] = ""; + uint64_t value = UINT64_MAX; + + assert(ip_fields[m]); + + (void) unit_get_ip_accounting(u, m, &value); + if (value == UINT64_MAX) + continue; + + /* Format IP accounting data for inclusion in the structured log message */ + if (asprintf(&t, "%s=%" PRIu64, ip_fields[m], value) < 0) { + r = log_oom(); + goto finish; + } + iovec[n_iovec++] = IOVEC_MAKE_STRING(t); + + /* Format the IP accounting data for inclusion in the human language message string, but only for the + * bytes counters (and not for the packets counters) */ + if (m == CGROUP_IP_INGRESS_BYTES) + t = strjoin(n_message_parts > 0 ? "received " : "Received ", + format_bytes(buf, sizeof(buf), value), + " IP traffic"); + else if (m == CGROUP_IP_EGRESS_BYTES) + t = strjoin(n_message_parts > 0 ? "sent " : "Sent ", + format_bytes(buf, sizeof(buf), value), + " IP traffic"); + else + continue; + if (!t) { + r = log_oom(); + goto finish; + } + + message_parts[n_message_parts++] = t; + } + + /* Is there any accounting data available at all? */ + if (n_iovec == 0) { + r = 0; + goto finish; + } + + if (n_message_parts == 0) + t = strjoina("MESSAGE=", u->id, ": Completed"); + else { + _cleanup_free_ char *joined; + + message_parts[n_message_parts] = NULL; + + joined = strv_join(message_parts, ", "); + if (!joined) { + r = log_oom(); + goto finish; + } + + t = strjoina("MESSAGE=", u->id, ": ", joined); + } + + /* The following four fields we allocate on the stack or are static strings, we hence don't want to free them, + * and hence don't increase n_iovec for them */ + iovec[n_iovec] = IOVEC_MAKE_STRING(t); + iovec[n_iovec + 1] = IOVEC_MAKE_STRING("MESSAGE_ID=" SD_MESSAGE_UNIT_RESOURCES_STR); + + t = strjoina(u->manager->unit_log_field, u->id); + iovec[n_iovec + 2] = IOVEC_MAKE_STRING(t); + + t = strjoina(u->manager->invocation_log_field, u->invocation_id_string); + iovec[n_iovec + 3] = IOVEC_MAKE_STRING(t); + + log_struct_iovec(LOG_INFO, iovec, n_iovec + 4); + r = 0; + +finish: + for (i = 0; i < n_message_parts; i++) + free(message_parts[i]); + + for (i = 0; i < n_iovec; i++) + free(iovec[i].iov_base); + + return r; + +} + void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) { Manager *m; bool unexpected; @@ -2009,9 +2320,11 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su /* Keep track of failed units */ (void) manager_update_failed_units(u->manager, u, ns == UNIT_FAILED); - /* Make sure the cgroup is always removed when we become inactive */ - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + /* Make sure the cgroup and state files are always removed when we become inactive */ + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) { unit_prune_cgroup(u); + unit_unlink_state_files(u); + } /* Note that this doesn't apply to RemainAfterExit services exiting * successfully, since there's no change of state in that case. Which is @@ -2069,7 +2382,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su if (u->job->state == JOB_RUNNING) { if (ns == UNIT_ACTIVE) job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED, true, false); - else if (ns != UNIT_ACTIVATING && ns != UNIT_RELOADING) { + else if (!IN_SET(ns, UNIT_ACTIVATING, UNIT_RELOADING)) { unexpected = true; if (UNIT_IS_INACTIVE_OR_FAILED(ns)) @@ -2120,7 +2433,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su check_unneeded_dependencies(u); if (ns != os && ns == UNIT_FAILED) { - log_unit_notice(u, "Unit entered failed state."); + log_unit_debug(u, "Unit entered failed state."); unit_start_on_failure(u); } } @@ -2146,28 +2459,33 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su manager_send_unit_plymouth(m, u); } else { + /* We don't care about D-Bus going down here, since we'll get an asynchronous notification for it + * anyway. */ - /* We don't care about D-Bus here, since we'll get an - * asynchronous notification for it anyway. */ + if (UNIT_IS_INACTIVE_OR_FAILED(ns) && + !UNIT_IS_INACTIVE_OR_FAILED(os) + && !MANAGER_IS_RELOADING(m)) { - if (u->type == UNIT_SERVICE && - UNIT_IS_INACTIVE_OR_FAILED(ns) && - !UNIT_IS_INACTIVE_OR_FAILED(os) && - !MANAGER_IS_RELOADING(m)) { + /* This unit just stopped/failed. */ + if (u->type == UNIT_SERVICE) { - /* Hmm, if there was no start record written - * write it now, so that we always have a nice - * pair */ - if (!u->in_audit) { - manager_send_unit_audit(m, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); + /* Hmm, if there was no start record written + * write it now, so that we always have a nice + * pair */ + if (!u->in_audit) { + manager_send_unit_audit(m, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); - if (ns == UNIT_INACTIVE) - manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, true); - } else - /* Write audit record if we have just finished shutting down */ - manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); + if (ns == UNIT_INACTIVE) + manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, true); + } else + /* Write audit record if we have just finished shutting down */ + manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); - u->in_audit = false; + u->in_audit = false; + } + + /* Write a log message about consumed resources */ + unit_log_resources(u); } } @@ -2309,7 +2627,59 @@ static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependen log_unit_warning(u, "Dependency %s=%s dropped, merged into %s", unit_dependency_to_string(dependency), strna(other), u->id); } -int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference) { +static int unit_add_dependency_hashmap( + Hashmap **h, + Unit *other, + UnitDependencyMask origin_mask, + UnitDependencyMask destination_mask) { + + UnitDependencyInfo info; + int r; + + assert(h); + assert(other); + assert(origin_mask < _UNIT_DEPENDENCY_MASK_FULL); + assert(destination_mask < _UNIT_DEPENDENCY_MASK_FULL); + assert(origin_mask > 0 || destination_mask > 0); + + r = hashmap_ensure_allocated(h, NULL); + if (r < 0) + return r; + + assert_cc(sizeof(void*) == sizeof(info)); + + info.data = hashmap_get(*h, other); + if (info.data) { + /* Entry already exists. Add in our mask. */ + + if ((info.origin_mask & origin_mask) == info.origin_mask && + (info.destination_mask & destination_mask) == info.destination_mask) + return 0; /* NOP */ + + info.origin_mask |= origin_mask; + info.destination_mask |= destination_mask; + + r = hashmap_update(*h, other, info.data); + } else { + info = (UnitDependencyInfo) { + .origin_mask = origin_mask, + .destination_mask = destination_mask, + }; + + r = hashmap_put(*h, other, info.data); + } + if (r < 0) + return r; + + return 1; +} + +int unit_add_dependency( + Unit *u, + UnitDependency d, + Unit *other, + bool add_reference, + UnitDependencyMask mask) { static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = { [UNIT_REQUIRES] = UNIT_REQUIRED_BY, @@ -2335,8 +2705,8 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen [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; + Unit *original_u = u, *original_other = other; + int r; assert(u); assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); @@ -2348,85 +2718,50 @@ 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) { - maybe_warn_about_dependency(orig_u, orig_other->id, d); + maybe_warn_about_dependency(original_u, original_other->id, d); return 0; } - if (d == UNIT_BEFORE && other->type == UNIT_DEVICE) { + if ((d == UNIT_BEFORE && other->type == UNIT_DEVICE) || + (d == UNIT_AFTER && u->type == UNIT_DEVICE)) { log_unit_warning(u, "Dependency Before=%s ignored (.device units cannot be delayed)", other->id); return 0; } - r = set_ensure_allocated(&u->dependencies[d], NULL); + r = unit_add_dependency_hashmap(u->dependencies + d, other, mask, 0); if (r < 0) return r; - if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) { - r = set_ensure_allocated(&other->dependencies[inverse_table[d]], NULL); + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID && inverse_table[d] != d) { + r = unit_add_dependency_hashmap(other->dependencies + inverse_table[d], u, 0, mask); if (r < 0) return r; } if (add_reference) { - r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], NULL); + r = unit_add_dependency_hashmap(u->dependencies + UNIT_REFERENCES, other, mask, 0); if (r < 0) return r; - r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], NULL); + r = unit_add_dependency_hashmap(other->dependencies + UNIT_REFERENCED_BY, u, 0, mask); if (r < 0) return r; } - q = set_put(u->dependencies[d], other); - if (q < 0) - return q; - - 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) { - w = set_put(u->dependencies[UNIT_REFERENCES], other); - if (w < 0) { - r = w; - goto fail; - } - - r = set_put(other->dependencies[UNIT_REFERENCED_BY], u); - if (r < 0) - goto fail; - } - unit_add_to_dbus_queue(u); return 0; - -fail: - if (q > 0) - set_remove(u->dependencies[d], other); - - if (v > 0) - set_remove(other->dependencies[inverse_table[d]], u); - - if (w > 0) - set_remove(u->dependencies[UNIT_REFERENCES], other); - - return r; } -int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference) { +int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference, UnitDependencyMask mask) { int r; assert(u); - r = unit_add_dependency(u, d, other, add_reference); + r = unit_add_dependency(u, d, other, add_reference, mask); if (r < 0) return r; - return unit_add_dependency(u, e, other, add_reference); + return unit_add_dependency(u, e, other, add_reference, mask); } static int resolve_template(Unit *u, const char *name, const char*path, char **buf, const char **ret) { @@ -2464,7 +2799,7 @@ static int resolve_template(Unit *u, const char *name, const char*path, char **b return 0; } -int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { +int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference, UnitDependencyMask mask) { _cleanup_free_ char *buf = NULL; Unit *other; int r; @@ -2480,10 +2815,10 @@ int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, con if (r < 0) return r; - return unit_add_dependency(u, d, other, add_reference); + return unit_add_dependency(u, d, other, add_reference, mask); } -int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { +int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference, UnitDependencyMask mask) { _cleanup_free_ char *buf = NULL; Unit *other; int r; @@ -2499,7 +2834,7 @@ int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency if (r < 0) return r; - return unit_add_two_dependencies(u, d, e, other, add_reference); + return unit_add_two_dependencies(u, d, e, other, add_reference, mask); } int set_unit_path(const char *p) { @@ -2745,7 +3080,15 @@ static int unit_serialize_cgroup_mask(FILE *f, const char *key, CGroupMask mask) return r; } +static const char *ip_accounting_metric_field[_CGROUP_IP_ACCOUNTING_METRIC_MAX] = { + [CGROUP_IP_INGRESS_BYTES] = "ip-accounting-ingress-bytes", + [CGROUP_IP_INGRESS_PACKETS] = "ip-accounting-ingress-packets", + [CGROUP_IP_EGRESS_BYTES] = "ip-accounting-egress-bytes", + [CGROUP_IP_EGRESS_PACKETS] = "ip-accounting-egress-packets", +}; + int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { + CGroupIPAccountingMetric m; int r; assert(u); @@ -2785,6 +3128,10 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { unit_serialize_item(u, f, "transient", yes_no(u->transient)); + unit_serialize_item(u, f, "exported-invocation-id", yes_no(u->exported_invocation_id)); + unit_serialize_item(u, f, "exported-log-level-max", yes_no(u->exported_log_level_max)); + unit_serialize_item(u, f, "exported-log-extra-fields", yes_no(u->exported_log_extra_fields)); + unit_serialize_item_format(u, f, "cpu-usage-base", "%" PRIu64, u->cpu_usage_base); if (u->cpu_usage_last != NSEC_INFINITY) unit_serialize_item_format(u, f, "cpu-usage-last", "%" PRIu64, u->cpu_usage_last); @@ -2794,6 +3141,7 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { unit_serialize_item(u, f, "cgroup-realized", yes_no(u->cgroup_realized)); (void) unit_serialize_cgroup_mask(f, "cgroup-realized-mask", u->cgroup_realized_mask); (void) unit_serialize_cgroup_mask(f, "cgroup-enabled-mask", u->cgroup_enabled_mask); + unit_serialize_item_format(u, f, "cgroup-bpf-realized", "%i", u->cgroup_bpf_state); if (uid_is_valid(u->ref_uid)) unit_serialize_item_format(u, f, "ref-uid", UID_FMT, u->ref_uid); @@ -2805,6 +3153,14 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { bus_track_serialize(u->bus_track, f, "ref"); + for (m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) { + uint64_t v; + + r = unit_get_ip_accounting(u, m, &v); + if (r >= 0) + unit_serialize_item_format(u, f, ip_accounting_metric_field[m], "%" PRIu64, v); + } + if (serialize_jobs) { if (u->job) { fprintf(f, "job\n"); @@ -2911,6 +3267,7 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { for (;;) { char line[LINE_MAX], *l, *v; + CGroupIPAccountingMetric m; size_t k; if (!fgets(line, sizeof(line), f)) { @@ -3015,6 +3372,36 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { continue; + } else if (streq(l, "exported-invocation-id")) { + + r = parse_boolean(v); + if (r < 0) + log_unit_debug(u, "Failed to parse exported invocation ID bool %s, ignoring.", v); + else + u->exported_invocation_id = r; + + continue; + + } else if (streq(l, "exported-log-level-max")) { + + r = parse_boolean(v); + if (r < 0) + log_unit_debug(u, "Failed to parse exported log level max bool %s, ignoring.", v); + else + u->exported_log_level_max = r; + + continue; + + } else if (streq(l, "exported-log-extra-fields")) { + + r = parse_boolean(v); + if (r < 0) + log_unit_debug(u, "Failed to parse exported log extra fields bool %s, ignoring.", v); + else + u->exported_log_extra_fields = r; + + continue; + } else if (STR_IN_SET(l, "cpu-usage-base", "cpuacct-usage-base")) { r = safe_atou64(v, &u->cpu_usage_base); @@ -3065,6 +3452,20 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { log_unit_debug(u, "Failed to parse cgroup-enabled-mask %s, ignoring.", v); continue; + } else if (streq(l, "cgroup-bpf-realized")) { + int i; + + r = safe_atoi(v, &i); + if (r < 0) + log_unit_debug(u, "Failed to parse cgroup BPF state %s, ignoring.", v); + else + u->cgroup_bpf_state = + i < 0 ? UNIT_CGROUP_BPF_INVALIDATED : + i > 0 ? UNIT_CGROUP_BPF_ON : + UNIT_CGROUP_BPF_OFF; + + continue; + } else if (streq(l, "ref-uid")) { uid_t uid; @@ -3107,6 +3508,21 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { continue; } + /* Check if this is an IP accounting metric serialization field */ + for (m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) + if (streq(l, ip_accounting_metric_field[m])) + break; + if (m < _CGROUP_IP_ACCOUNTING_METRIC_MAX) { + uint64_t c; + + r = safe_atou64(v, &c); + if (r < 0) + log_unit_debug(u, "Failed to parse IP accounting value %s, ignoring.", v); + else + u->ip_accounting_extra[m] = c; + continue; + } + if (unit_can_serialize(u)) { if (rt) { r = exec_runtime_deserialize_item(u, rt, l, v, fds); @@ -3133,6 +3549,11 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { if (!dual_timestamp_is_set(&u->state_change_timestamp)) dual_timestamp_get(&u->state_change_timestamp); + /* Let's make sure that everything that is deserialized also gets any potential new cgroup settings applied + * after we are done. For that we invalidate anything already realized, so that we can realize it again. */ + unit_invalidate_cgroup(u, _CGROUP_MASK_ALL); + unit_invalidate_cgroup_bpf(u); + return 0; } @@ -3157,7 +3578,7 @@ void unit_deserialize_skip(FILE *f) { } -int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency dep) { +int unit_add_node_dependency(Unit *u, const char *what, bool wants, UnitDependency dep, UnitDependencyMask mask) { Unit *device; _cleanup_free_ char *e = NULL; int r; @@ -3189,12 +3610,12 @@ int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency dep r = unit_add_two_dependencies(u, UNIT_AFTER, MANAGER_IS_SYSTEM(u->manager) ? dep : UNIT_WANTS, - device, true); + device, true, mask); if (r < 0) return r; if (wants) { - r = unit_add_dependency(device, UNIT_WANTS, u, false); + r = unit_add_dependency(device, UNIT_WANTS, u, false, mask); if (r < 0) return r; } @@ -3277,7 +3698,8 @@ bool unit_need_daemon_reload(Unit *u) { if (fragment_mtime_newer(u->source_path, u->source_mtime, false)) return true; - (void) unit_find_dropin_paths(u, &t); + if (u->load_state == UNIT_LOADED) + (void) unit_find_dropin_paths(u, &t); if (!strv_equal(u->dropin_paths, t)) return true; @@ -3343,9 +3765,7 @@ bool unit_active_or_pending(Unit *u) { return true; if (u->job && - (u->job->type == JOB_START || - u->job->type == JOB_RELOAD_OR_START || - u->job->type == JOB_RESTART)) + IN_SET(u->job->type, JOB_START, JOB_RELOAD_OR_START, JOB_RESTART)) return true; return false; @@ -3441,7 +3861,7 @@ int unit_kill_common( return -ENOMEM; q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, 0, pid_set, NULL, NULL); - if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT) + if (q < 0 && !IN_SET(q, -EAGAIN, -ESRCH, -ENOENT)) r = q; else killed = true; @@ -3973,7 +4393,7 @@ int unit_kill_context( pid_set, log_func, u); if (r < 0) { - if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) + if (!IN_SET(r, -EAGAIN, -ESRCH, -ENOENT)) log_unit_warning_errno(u, r, "Failed to kill control group %s, ignoring: %m", u->cgroup_path); } else if (r > 0) { @@ -3993,7 +4413,7 @@ int unit_kill_context( * them. */ if (cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) > 0 || - (detect_container() == 0 && !unit_cgroup_delegate(u))) + (detect_container() == 0 && !UNIT_CGROUP_BOOL(u, delegate))) wait_for_exit = true; if (send_sighup) { @@ -4015,23 +4435,26 @@ int unit_kill_context( return wait_for_exit; } -int unit_require_mounts_for(Unit *u, const char *path) { +int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask) { char prefix[strlen(path) + 1], *p; + UnitDependencyInfo di; 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. */ + /* Registers a unit for requiring a certain path and all its prefixes. We keep a hashtable of these paths in + * the unit (from the path to the UnitDependencyInfo structure indicating how to the dependency came to + * be). 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; + r = hashmap_ensure_allocated(&u->requires_mounts_for, &string_hash_ops); + if (r < 0) + return r; + p = strdup(path); if (!p) return -ENOMEM; @@ -4043,14 +4466,20 @@ int unit_require_mounts_for(Unit *u, const char *path) { return -EPERM; } - if (strv_contains(u->requires_mounts_for, p)) { + if (hashmap_contains(u->requires_mounts_for, p)) { free(p); return 0; } - r = strv_consume(&u->requires_mounts_for, p); - if (r < 0) + di = (UnitDependencyInfo) { + .origin_mask = mask + }; + + r = hashmap_put(u->requires_mounts_for, p, di.data); + if (r < 0) { + free(p); return r; + } PATH_FOREACH_PREFIX_MORE(prefix, p) { Set *x; @@ -4092,8 +4521,9 @@ int unit_require_mounts_for(Unit *u, const char *path) { int unit_setup_exec_runtime(Unit *u) { ExecRuntime **rt; size_t offset; - Iterator i; Unit *other; + Iterator i; + void *v; offset = UNIT_VTABLE(u)->exec_runtime_offset; assert(offset > 0); @@ -4104,7 +4534,7 @@ int unit_setup_exec_runtime(Unit *u) { return 0; /* Try to get it from somebody else */ - SET_FOREACH(other, u->dependencies[UNIT_JOINS_NAMESPACE_OF], i) { + HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_JOINS_NAMESPACE_OF], i) { *rt = unit_get_exec_runtime(other); if (*rt) { @@ -4165,6 +4595,7 @@ void unit_warn_if_dir_nonempty(Unit *u, const char* where) { log_struct(LOG_NOTICE, "MESSAGE_ID=" SD_MESSAGE_OVERMOUNTING_STR, LOG_UNIT_ID(u), + LOG_UNIT_INVOCATION_ID(u), LOG_UNIT_MESSAGE(u, "Directory %s to mount over is not empty, mounting anyway.", where), "WHERE=%s", where, NULL); @@ -4187,6 +4618,7 @@ int unit_fail_if_symlink(Unit *u, const char* where) { log_struct(LOG_ERR, "MESSAGE_ID=" SD_MESSAGE_OVERMOUNTING_STR, LOG_UNIT_ID(u), + LOG_UNIT_INVOCATION_ID(u), LOG_UNIT_MESSAGE(u, "Mount on symlink %s not allowed.", where), "WHERE=%s", where, NULL); @@ -4425,14 +4857,297 @@ int unit_acquire_invocation_id(Unit *u) { return 0; } -void unit_set_exec_params(Unit *s, ExecParameters *p) { - CGroupContext *c; +void unit_set_exec_params(Unit *u, ExecParameters *p) { + assert(u); + assert(p); - assert(s); - assert(s); + p->cgroup_path = u->cgroup_path; + SET_FLAG(p->flags, EXEC_CGROUP_DELEGATE, UNIT_CGROUP_BOOL(u, delegate)); +} + +int unit_fork_helper_process(Unit *u, pid_t *ret) { + pid_t pid; + int r; + + assert(u); + assert(ret); + + /* Forks off a helper process and makes sure it is a member of the unit's cgroup. Returns == 0 in the child, + * and > 0 in the parent. The pid parameter is always filled in with the child's PID. */ + + (void) unit_realize_cgroup(u); + + pid = fork(); + if (pid < 0) + return -errno; + + if (pid == 0) { + + (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); + (void) ignore_signals(SIGPIPE, -1); + + log_close(); + log_open(); + + if (u->cgroup_path) { + r = cg_attach_everywhere(u->manager->cgroup_supported, u->cgroup_path, 0, NULL, NULL); + if (r < 0) { + log_unit_error_errno(u, r, "Failed to join unit cgroup %s: %m", u->cgroup_path); + _exit(EXIT_CGROUP); + } + } + + *ret = getpid_cached(); + return 0; + } - p->cgroup_path = s->cgroup_path; + *ret = pid; + return 1; +} - c = unit_get_cgroup_context(s); - SET_FLAG(p->flags, EXEC_CGROUP_DELEGATE, c && c->delegate); +static void unit_update_dependency_mask(Unit *u, UnitDependency d, Unit *other, UnitDependencyInfo di) { + assert(u); + assert(d >= 0); + assert(d < _UNIT_DEPENDENCY_MAX); + assert(other); + + if (di.origin_mask == 0 && di.destination_mask == 0) { + /* No bit set anymore, let's drop the whole entry */ + assert_se(hashmap_remove(u->dependencies[d], other)); + log_unit_debug(u, "%s lost dependency %s=%s", u->id, unit_dependency_to_string(d), other->id); + } else + /* Mask was reduced, let's update the entry */ + assert_se(hashmap_update(u->dependencies[d], other, di.data) == 0); } + +void unit_remove_dependencies(Unit *u, UnitDependencyMask mask) { + UnitDependency d; + + assert(u); + + /* Removes all dependencies u has on other units marked for ownership by 'mask'. */ + + if (mask == 0) + return; + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + bool done; + + do { + UnitDependencyInfo di; + Unit *other; + Iterator i; + + done = true; + + HASHMAP_FOREACH_KEY(di.data, other, u->dependencies[d], i) { + UnitDependency q; + + if ((di.origin_mask & ~mask) == di.origin_mask) + continue; + di.origin_mask &= ~mask; + unit_update_dependency_mask(u, d, other, di); + + /* We updated the dependency from our unit to the other unit now. But most dependencies + * imply a reverse dependency. Hence, let's delete that one too. For that we go through + * all dependency types on the other unit and delete all those which point to us and + * have the right mask set. */ + + for (q = 0; q < _UNIT_DEPENDENCY_MAX; q++) { + UnitDependencyInfo dj; + + dj.data = hashmap_get(other->dependencies[q], u); + if ((dj.destination_mask & ~mask) == dj.destination_mask) + continue; + dj.destination_mask &= ~mask; + + unit_update_dependency_mask(other, q, u, dj); + } + + unit_add_to_gc_queue(other); + + done = false; + break; + } + + } while (!done); + } +} + +static int unit_export_invocation_id(Unit *u) { + const char *p; + int r; + + assert(u); + + if (u->exported_invocation_id) + return 0; + + if (sd_id128_is_null(u->invocation_id)) + return 0; + + p = strjoina("/run/systemd/units/invocation:", u->id); + r = symlink_atomic(u->invocation_id_string, p); + if (r < 0) + return log_unit_debug_errno(u, r, "Failed to create invocation ID symlink %s: %m", p); + + u->exported_invocation_id = true; + return 0; +} + +static int unit_export_log_level_max(Unit *u, const ExecContext *c) { + const char *p; + char buf[2]; + int r; + + assert(u); + assert(c); + + if (u->exported_log_level_max) + return 0; + + if (c->log_level_max < 0) + return 0; + + assert(c->log_level_max <= 7); + + buf[0] = '0' + c->log_level_max; + buf[1] = 0; + + p = strjoina("/run/systemd/units/log-level-max:", u->id); + r = symlink_atomic(buf, p); + if (r < 0) + return log_unit_debug_errno(u, r, "Failed to create maximum log level symlink %s: %m", p); + + u->exported_log_level_max = true; + return 0; +} + +static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) { + _cleanup_close_ int fd = -1; + struct iovec *iovec; + const char *p; + char *pattern; + le64_t *sizes; + ssize_t n; + size_t i; + int r; + + if (u->exported_log_extra_fields) + return 0; + + if (c->n_log_extra_fields <= 0) + return 0; + + sizes = newa(le64_t, c->n_log_extra_fields); + iovec = newa(struct iovec, c->n_log_extra_fields * 2); + + for (i = 0; i < c->n_log_extra_fields; i++) { + sizes[i] = htole64(c->log_extra_fields[i].iov_len); + + iovec[i*2] = IOVEC_MAKE(sizes + i, sizeof(le64_t)); + iovec[i*2+1] = c->log_extra_fields[i]; + } + + p = strjoina("/run/systemd/units/log-extra-fields:", u->id); + pattern = strjoina(p, ".XXXXXX"); + + fd = mkostemp_safe(pattern); + if (fd < 0) + return log_unit_debug_errno(u, fd, "Failed to create extra fields file %s: %m", p); + + n = writev(fd, iovec, c->n_log_extra_fields*2); + if (n < 0) { + r = log_unit_debug_errno(u, errno, "Failed to write extra fields: %m"); + goto fail; + } + + (void) fchmod(fd, 0644); + + if (rename(pattern, p) < 0) { + r = log_unit_debug_errno(u, errno, "Failed to rename extra fields file: %m"); + goto fail; + } + + u->exported_log_extra_fields = true; + return 0; + +fail: + (void) unlink(pattern); + return r; +} + +void unit_export_state_files(Unit *u) { + const ExecContext *c; + + assert(u); + + if (!u->id) + return; + + if (!MANAGER_IS_SYSTEM(u->manager)) + return; + + /* Exports a couple of unit properties to /run/systemd/units/, so that journald can quickly query this data + * from there. Ideally, journald would use IPC to query this, like everybody else, but that's hard, as long as + * the IPC system itself and PID 1 also log to the journal. + * + * Note that these files really shouldn't be considered API for anyone else, as use a runtime file system as + * IPC replacement is not compatible with today's world of file system namespaces. However, this doesn't really + * apply to communication between the journal and systemd, as we assume that these two daemons live in the same + * namespace at least. + * + * Note that some of the "files" exported here are actually symlinks and not regular files. Symlinks work + * better for storing small bits of data, in particular as we can write them with two system calls, and read + * them with one. */ + + (void) unit_export_invocation_id(u); + + c = unit_get_exec_context(u); + if (c) { + (void) unit_export_log_level_max(u, c); + (void) unit_export_log_extra_fields(u, c); + } +} + +void unit_unlink_state_files(Unit *u) { + const char *p; + + assert(u); + + if (!u->id) + return; + + if (!MANAGER_IS_SYSTEM(u->manager)) + return; + + /* Undoes the effect of unit_export_state() */ + + if (u->exported_invocation_id) { + p = strjoina("/run/systemd/units/invocation:", u->id); + (void) unlink(p); + + u->exported_invocation_id = false; + } + + if (u->exported_log_level_max) { + p = strjoina("/run/systemd/units/log-level-max:", u->id); + (void) unlink(p); + + u->exported_log_level_max = false; + } + + if (u->exported_log_extra_fields) { + p = strjoina("/run/systemd/units/extra-fields:", u->id); + (void) unlink(p); + + u->exported_log_extra_fields = false; + } +} + +static const char* const collect_mode_table[_COLLECT_MODE_MAX] = { + [COLLECT_INACTIVE] = "inactive", + [COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed", +}; + +DEFINE_STRING_TABLE_LOOKUP(collect_mode, CollectMode);