diff options
Diffstat (limited to 'src/core/unit.c')
-rw-r--r-- | src/core/unit.c | 1632 |
1 files changed, 1060 insertions, 572 deletions
diff --git a/src/core/unit.c b/src/core/unit.c index fac017c57d..690f7f7dd9 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -20,41 +18,53 @@ ***/ #include <errno.h> -#include <string.h> #include <stdlib.h> -#include <unistd.h> +#include <string.h> #include <sys/stat.h> +#include <unistd.h> #include "sd-id128.h" #include "sd-messages.h" -#include "set.h" -#include "unit.h" -#include "macro.h" -#include "strv.h" -#include "path-util.h" -#include "load-fragment.h" -#include "load-dropin.h" -#include "log.h" -#include "unit-name.h" -#include "dbus-unit.h" -#include "special.h" -#include "cgroup-util.h" -#include "missing.h" -#include "mkdir.h" -#include "fileio-label.h" + +#include "alloc-util.h" #include "bus-common-errors.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "dbus-unit.h" #include "dbus.h" -#include "execute.h" #include "dropin.h" +#include "escape.h" +#include "execute.h" +#include "fileio-label.h" #include "formats-util.h" +#include "id128-util.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" #include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "special.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "umask-util.h" +#include "unit-name.h" +#include "unit.h" +#include "user-util.h" +#include "virt.h" const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = &service_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, @@ -89,7 +99,13 @@ Unit *unit_new(Manager *m, size_t size) { u->unit_file_state = _UNIT_FILE_STATE_INVALID; u->unit_file_preset = -1; u->on_failure_job_mode = JOB_REPLACE; + u->cgroup_inotify_wd = -1; + u->job_timeout = USEC_INFINITY; + u->ref_uid = UID_INVALID; + u->ref_gid = GID_INVALID; + u->cpu_usage_last = NSEC_INFINITY; + 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); return u; @@ -99,7 +115,7 @@ bool unit_has_name(Unit *u, const char *name) { assert(u); assert(name); - return !!set_get(u->names, (char*) name); + return set_contains(u->names, (char*) name); } static void unit_init(Unit *u) { @@ -120,8 +136,13 @@ static void unit_init(Unit *u) { * been initialized */ cc->cpu_accounting = u->manager->default_cpu_accounting; + cc->io_accounting = u->manager->default_io_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; + + if (u->type != UNIT_SLICE) + cc->tasks_max = u->manager->default_tasks_max; } ec = unit_get_exec_context(u); @@ -177,7 +198,7 @@ int unit_add_name(Unit *u, const char *text) { if (r < 0) return r; - if (i && unit_vtable[t]->no_instances) + if (i && !unit_type_may_template(t)) return -EINVAL; /* Ensure that this unit is either instanced or not instanced, @@ -186,7 +207,7 @@ int unit_add_name(Unit *u, const char *text) { if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) return -EINVAL; - if (unit_vtable[t]->no_alias && !set_isempty(u->names)) + if (!unit_type_may_alias(t) && !set_isempty(u->names)) return -EEXIST; if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) @@ -304,15 +325,15 @@ bool unit_check_gc(Unit *u) { if (state != UNIT_INACTIVE) return true; - if (UNIT_VTABLE(u)->no_gc) - return true; - if (u->no_gc) return true; if (u->refs) return true; + if (sd_bus_track_count(u->bus_track) > 0) + return true; + if (UNIT_VTABLE(u)->check_gc) if (UNIT_VTABLE(u)->check_gc(u)) return true; @@ -353,7 +374,7 @@ void unit_add_to_gc_queue(Unit *u) { LIST_PREPEND(gc_queue, u->manager->gc_queue, u); u->in_gc_queue = true; - u->manager->n_in_gc_queue ++; + u->manager->n_in_gc_queue++; } void unit_add_to_dbus_queue(Unit *u) { @@ -404,17 +425,25 @@ static void unit_remove_transient(Unit *u) { return; if (u->fragment_path) - unlink(u->fragment_path); + (void) unlink(u->fragment_path); STRV_FOREACH(i, u->dropin_paths) { - _cleanup_free_ char *p = NULL; - int r; + _cleanup_free_ char *p = NULL, *pp = NULL; + + p = dirname_malloc(*i); /* Get the drop-in directory from the drop-in file */ + if (!p) + continue; - unlink(*i); + pp = dirname_malloc(p); /* Get the config directory from the drop-in directory */ + if (!pp) + continue; - r = path_get_parent(*i, &p); - if (r >= 0) - rmdir(p); + /* Only drop transient drop-ins */ + if (!path_equal(u->manager->lookup_paths.transient, pp)) + continue; + + (void) unlink(*i); + (void) rmdir(p); } } @@ -442,8 +471,7 @@ static void unit_free_requires_mounts_for(Unit *u) { } } - strv_free(u->requires_mounts_for); - u->requires_mounts_for = NULL; + u->requires_mounts_for = strv_free(u->requires_mounts_for); } static void unit_done(Unit *u) { @@ -474,18 +502,29 @@ void unit_free(Unit *u) { assert(u); - if (u->manager->n_reloading <= 0) + if (u->transient_file) + fclose(u->transient_file); + + if (!MANAGER_IS_RELOADING(u->manager)) unit_remove_transient(u); bus_unit_send_removed_signal(u); unit_done(u); + sd_bus_slot_unref(u->match_bus_slot); + + sd_bus_track_unref(u->bus_track); + u->deserialized_refs = strv_free(u->deserialized_refs); + unit_free_requires_mounts_for(u); SET_FOREACH(t, u->names, i) hashmap_remove_value(u->manager->units, t, u); + if (!sd_id128_is_null(u->invocation_id)) + hashmap_remove_value(u->manager->units_by_invocation_id, &u->invocation_id, u); + if (u->job) { Job *j = u->job; job_uninstall(j); @@ -521,12 +560,11 @@ void unit_free(Unit *u) { if (u->in_cgroup_queue) 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); - } + unit_release_cgroup(u); + + unit_unref_uid_gid(u, false); - manager_update_failed_units(u->manager, u, false); + (void) manager_update_failed_units(u->manager, u, false); set_remove(u->manager->startup_units, u); free(u->description); @@ -545,6 +583,8 @@ void unit_free(Unit *u) { condition_free_list(u->conditions); condition_free_list(u->asserts); + free(u->reboot_arg); + unit_ref_unset(&u->slice); while (u->refs) @@ -672,8 +712,7 @@ static void merge_dependencies(Unit *u, Unit *other, const char *other_id, UnitD /* 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; + other->dependencies[d] = set_free(other->dependencies[d]); } int unit_merge(Unit *u, Unit *other) { @@ -697,6 +736,9 @@ int unit_merge(Unit *u, Unit *other) { if (!u->instance != !other->instance) return -EINVAL; + 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) return -EEXIST; @@ -753,9 +795,9 @@ int unit_merge(Unit *u, Unit *other) { } int unit_merge_by_name(Unit *u, const char *name) { + _cleanup_free_ char *s = NULL; Unit *other; int r; - _cleanup_free_ char *s = NULL; assert(u); assert(name); @@ -805,7 +847,7 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { return r; } - if (u->manager->running_as != MANAGER_SYSTEM) + if (!MANAGER_IS_SYSTEM(u->manager)) return 0; if (c->private_tmp) { @@ -857,6 +899,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { Iterator i; const char *prefix2; char + timestamp0[FORMAT_TIMESTAMP_MAX], timestamp1[FORMAT_TIMESTAMP_MAX], timestamp2[FORMAT_TIMESTAMP_MAX], timestamp3[FORMAT_TIMESTAMP_MAX], @@ -865,6 +908,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { Unit *following; _cleanup_set_free_ Set *following_set = NULL; int r; + const char *n; assert(u); assert(u->type >= 0); @@ -878,6 +922,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tInstance: %s\n" "%s\tUnit Load State: %s\n" "%s\tUnit Active State: %s\n" + "%s\tState Change Timestamp: %s\n" "%s\tInactive Exit Timestamp: %s\n" "%s\tActive Enter Timestamp: %s\n" "%s\tActive Exit Timestamp: %s\n" @@ -895,6 +940,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, strna(u->instance), prefix, unit_load_state_to_string(u->load_state), prefix, unit_active_state_to_string(unit_active_state(u)), + prefix, strna(format_timestamp(timestamp0, sizeof(timestamp0), u->state_change_timestamp.realtime)), prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->inactive_exit_timestamp.realtime)), prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->active_enter_timestamp.realtime)), prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->active_exit_timestamp.realtime)), @@ -911,6 +957,10 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { SET_FOREACH(t, u->names, i) fprintf(f, "%s\tName: %s\n", prefix, t); + if (!sd_id128_is_null(u->invocation_id)) + fprintf(f, "%s\tInvocation ID: " SD_ID128_FORMAT_STR "\n", + prefix, SD_ID128_FORMAT_VAL(u->invocation_id)); + STRV_FOREACH(j, u->documentation) fprintf(f, "%s\tDocumentation: %s\n", prefix, *j); @@ -935,7 +985,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { STRV_FOREACH(j, u->dropin_paths) fprintf(f, "%s\tDropIn Path: %s\n", prefix, *j); - if (u->job_timeout > 0) + if (u->job_timeout != USEC_INFINITY) fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0)); if (u->job_timeout_action != FAILURE_ACTION_NONE) @@ -986,15 +1036,13 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tRefuseManualStop: %s\n" "%s\tDefaultDependencies: %s\n" "%s\tOnFailureJobMode: %s\n" - "%s\tIgnoreOnIsolate: %s\n" - "%s\tIgnoreOnSnapshot: %s\n", + "%s\tIgnoreOnIsolate: %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, job_mode_to_string(u->on_failure_job_mode), - prefix, yes_no(u->ignore_on_isolate), - prefix, yes_no(u->ignore_on_snapshot)); + prefix, yes_no(u->ignore_on_isolate)); if (UNIT_VTABLE(u)->dump) UNIT_VTABLE(u)->dump(u, f, prefix2); @@ -1006,13 +1054,14 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { else if (u->load_state == UNIT_ERROR) fprintf(f, "%s\tLoad Error Code: %s\n", prefix, strerror(-u->load_error)); + for (n = sd_bus_track_first(u->bus_track); n; n = sd_bus_track_next(u->bus_track)) + fprintf(f, "%s\tBus Ref: %s\n", prefix, n); if (u->job) job_dump(u->job, f, prefix2); if (u->nop_job) job_dump(u->nop_job, f, prefix2); - } /* Common implementation for multiple backends */ @@ -1092,9 +1141,7 @@ static int unit_add_target_dependencies(Unit *u) { static const UnitDependency deps[] = { UNIT_REQUIRED_BY, - UNIT_REQUIRED_BY_OVERRIDABLE, UNIT_REQUISITE_OF, - UNIT_REQUISITE_OF_OVERRIDABLE, UNIT_WANTED_BY, UNIT_BOUND_BY }; @@ -1119,16 +1166,16 @@ static int unit_add_target_dependencies(Unit *u) { static int unit_add_slice_dependencies(Unit *u) { assert(u); - if (!unit_get_cgroup_context(u)) + if (!UNIT_HAS_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); + return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT_DEREF(u->slice), true); - if (streq(u->id, SPECIAL_ROOT_SLICE)) + if (unit_has_name(u, SPECIAL_ROOT_SLICE)) return 0; - return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, SPECIAL_ROOT_SLICE, NULL, true); + return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_ROOT_SLICE, NULL, true); } static int unit_add_mount_dependencies(Unit *u) { @@ -1141,13 +1188,23 @@ static int unit_add_mount_dependencies(Unit *u) { char prefix[strlen(*i) + 1]; PATH_FOREACH_PREFIX_MORE(prefix, *i) { + _cleanup_free_ char *p = NULL; Unit *m; - r = manager_get_unit_by_path(u->manager, prefix, ".mount", &m); + r = unit_name_from_path(prefix, ".mount", &p); if (r < 0) return r; - if (r == 0) + + m = manager_get_unit(u->manager, p); + if (!m) { + /* Make sure to load the mount unit if + * it exists. If so the dependencies + * on this unit will be added later + * during the loading of the mount + * unit. */ + (void) manager_load_unit_prepare(u->manager, p, NULL, NULL, &m); continue; + } if (m == u) continue; @@ -1171,15 +1228,21 @@ static int unit_add_mount_dependencies(Unit *u) { static int unit_add_startup_units(Unit *u) { CGroupContext *c; + int r; 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) + if (c->startup_cpu_shares == CGROUP_CPU_SHARES_INVALID && + c->startup_io_weight == CGROUP_WEIGHT_INVALID && + c->startup_blockio_weight == CGROUP_BLKIO_WEIGHT_INVALID) return 0; + r = set_ensure_allocated(&u->manager->startup_units, NULL); + if (r < 0) + return r; + return set_put(u->manager->startup_units, u); } @@ -1199,6 +1262,17 @@ int unit_load(Unit *u) { if (u->load_state != UNIT_STUB) return 0; + if (u->transient_file) { + r = fflush_and_check(u->transient_file); + if (r < 0) + goto fail; + + fclose(u->transient_file); + u->transient_file = NULL; + + u->fragment_mtime = now(CLOCK_REALTIME); + } + if (UNIT_VTABLE(u)->load) { r = UNIT_VTABLE(u)->load(u); if (r < 0) @@ -1317,43 +1391,35 @@ static bool unit_assert_test(Unit *u) { 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); - assert(t < _JOB_TYPE_MAX); - - if (t != JOB_START && t != JOB_STOP) - return NULL; - - format_table = &UNIT_VTABLE(u)->status_message_formats; - if (!format_table) - return NULL; - - return format_table->starting_stopping[t == JOB_STOP]; +void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) { + DISABLE_WARNING_FORMAT_NONLITERAL; + manager_status_printf(u->manager, STATUS_TYPE_NORMAL, status, unit_status_msg_format, unit_description(u)); + REENABLE_WARNING; } -_pure_ static const char *unit_get_status_message_format_try_harder(Unit *u, JobType t) { +_pure_ static const char* unit_get_status_message_format(Unit *u, JobType t) { const char *format; + const UnitStatusMessageFormats *format_table; assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - format = unit_get_status_message_format(u, t); - if (format) - return format; + assert(IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)); + + if (t != JOB_RELOAD) { + format_table = &UNIT_VTABLE(u)->status_message_formats; + if (format_table) { + format = format_table->starting_stopping[t == JOB_STOP]; + if (format) + return format; + } + } /* Return generic strings */ if (t == JOB_START) return "Starting %s."; else if (t == JOB_STOP) return "Stopping %s."; - else if (t == JOB_RELOAD) + else return "Reloading %s."; - - return NULL; } static void unit_status_print_starting_stopping(Unit *u, JobType t) { @@ -1361,12 +1427,11 @@ static void unit_status_print_starting_stopping(Unit *u, JobType t) { assert(u); - /* We only print status messages for selected units on - * selected operations. */ + /* Reload status messages have traditionally not been printed to console. */ + if (!IN_SET(t, JOB_START, JOB_STOP)) + return; format = unit_get_status_message_format(u, t); - if (!format) - return; DISABLE_WARNING_FORMAT_NONLITERAL; unit_status_printf(u, "", format); @@ -1380,7 +1445,7 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { assert(u); - if (t != JOB_START && t != JOB_STOP && t != JOB_RELOAD) + if (!IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)) return; if (log_on_console()) @@ -1388,12 +1453,10 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { /* We log status messages for all units and all operations. */ - format = unit_get_status_message_format_try_harder(u, t); - if (!format) - return; + format = unit_get_status_message_format(u, t); DISABLE_WARNING_FORMAT_NONLITERAL; - snprintf(buf, sizeof(buf), format, unit_description(u)); + xsprintf(buf, format, unit_description(u)); REENABLE_WARNING; mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING : @@ -1413,23 +1476,44 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { NULL); } +void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t) { + assert(u); + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + unit_status_log_starting_stopping_reloading(u, t); + unit_status_print_starting_stopping(u, t); +} + +int unit_start_limit_test(Unit *u) { + assert(u); + + if (ratelimit_test(&u->start_limit)) { + u->start_limit_hit = false; + return 0; + } + + log_unit_warning(u, "Start request repeated too quickly."); + u->start_limit_hit = true; + + return failure_action(u->manager, u->start_limit_action, u->reboot_arg); +} + /* 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 + * -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 + * -EINVAL: Unit not loaded + * -EOPNOTSUPP: Unit type not supported */ int unit_start(Unit *u) { UnitActiveState state; Unit *following; - int r; assert(u); - if (u->load_state != UNIT_LOADED) - return -EINVAL; - /* If this is already started, then this will succeed. Note * that this will even succeed if this unit is not startable * by the user. This is relied on to detect when we need to @@ -1438,6 +1522,10 @@ int unit_start(Unit *u) { if (UNIT_IS_ACTIVE_OR_RELOADING(state)) return -EALREADY; + /* Units that aren't loaded cannot be started */ + if (u->load_state != UNIT_LOADED) + return -EINVAL; + /* If the conditions failed, don't do anything at all. If we * already are activating this call might still be useful to * speed up activation in case there is some hold-off time, @@ -1455,6 +1543,15 @@ int unit_start(Unit *u) { return -EPROTO; } + /* Units of types that aren't supported cannot be + * started. Note that we do this test only after the condition + * checks, so that we rather return condition check errors + * (which are usually not considered a true failure) than "not + * supported" errors (which are considered a failure). + */ + if (!unit_supported(u)) + return -EOPNOTSUPP; + /* Forward to the main object, if we aren't it. */ following = unit_following(u); if (following) { @@ -1462,9 +1559,6 @@ int unit_start(Unit *u) { return unit_start(following); } - if (!unit_supported(u)) - return -EOPNOTSUPP; - /* If it is stopped, but we cannot start it, then fail */ if (!UNIT_VTABLE(u)->start) return -EBADR; @@ -1477,19 +1571,18 @@ int unit_start(Unit *u) { unit_add_to_dbus_queue(u); - r = UNIT_VTABLE(u)->start(u); - if (r <= 0) - return r; - - /* Log if the start function actually did something */ - unit_status_log_starting_stopping_reloading(u, JOB_START); - unit_status_print_starting_stopping(u, JOB_START); - return r; + return UNIT_VTABLE(u)->start(u); } bool unit_can_start(Unit *u) { assert(u); + if (u->load_state != UNIT_LOADED) + return false; + + if (!unit_supported(u)) + return false; + return !!UNIT_VTABLE(u)->start; } @@ -1508,7 +1601,6 @@ bool unit_can_isolate(Unit *u) { int unit_stop(Unit *u) { UnitActiveState state; Unit *following; - int r; assert(u); @@ -1527,13 +1619,7 @@ int unit_stop(Unit *u) { unit_add_to_dbus_queue(u); - r = UNIT_VTABLE(u)->stop(u); - if (r <= 0) - return r; - - unit_status_log_starting_stopping_reloading(u, JOB_STOP); - unit_status_print_starting_stopping(u, JOB_STOP); - return r; + return UNIT_VTABLE(u)->stop(u); } /* Errors: @@ -1544,7 +1630,6 @@ int unit_stop(Unit *u) { int unit_reload(Unit *u) { UnitActiveState state; Unit *following; - int r; assert(u); @@ -1571,12 +1656,7 @@ int unit_reload(Unit *u) { unit_add_to_dbus_queue(u); - r = UNIT_VTABLE(u)->reload(u); - if (r <= 0) - return r; - - unit_status_log_starting_stopping_reloading(u, JOB_RELOAD); - return r; + return UNIT_VTABLE(u)->reload(u); } bool unit_can_reload(Unit *u) { @@ -1593,11 +1673,11 @@ bool unit_can_reload(Unit *u) { static void unit_check_unneeded(Unit *u) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + static const UnitDependency needed_dependencies[] = { UNIT_REQUIRED_BY, - UNIT_REQUIRED_BY_OVERRIDABLE, UNIT_REQUISITE_OF, - UNIT_REQUISITE_OF_OVERRIDABLE, UNIT_WANTED_BY, UNIT_BOUND_BY, }; @@ -1623,7 +1703,7 @@ static void unit_check_unneeded(Unit *u) { if (unit_active_or_pending(other)) return; - /* If stopping a unit fails continously we might enter a stop + /* If stopping a unit fails continuously we might enter a stop * loop here, hence stop acting on the service being * unnecessary after a while. */ if (!ratelimit_test(&u->auto_stop_ratelimit)) { @@ -1634,12 +1714,13 @@ static void unit_check_unneeded(Unit *u) { log_unit_info(u, "Unit not needed anymore. Stopping."); /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ - r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, NULL, NULL); + r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); if (r < 0) - log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %m"); + log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); } static void unit_check_binds_to(Unit *u) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; bool stop = false; Unit *other; Iterator i; @@ -1667,7 +1748,7 @@ static void unit_check_binds_to(Unit *u) { if (!stop) return; - /* If stopping a unit fails continously we might enter a stop + /* If stopping a unit fails continuously we might enter a stop * loop here, hence stop acting on the service being * unnecessary after a while. */ if (!ratelimit_test(&u->auto_stop_ratelimit)) { @@ -1679,9 +1760,9 @@ static void unit_check_binds_to(Unit *u) { log_unit_info(u, "Unit is bound to inactive unit %s. Stopping, too.", other->id); /* A unit we need to run is gone. Sniff. Let's stop this. */ - r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, NULL, NULL); + r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); if (r < 0) - log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %m"); + log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); } static void retroactively_start_dependencies(Unit *u) { @@ -1694,30 +1775,25 @@ static void retroactively_start_dependencies(Unit *u) { SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) if (!set_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, true, NULL, NULL); + 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) && !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, true, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_REQUIRES_OVERRIDABLE], i) - if (!set_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, false, NULL, NULL); + 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) && !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_FAIL, false, NULL, NULL); + manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL); SET_FOREACH(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, true, NULL, NULL); + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); SET_FOREACH(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, true, NULL, NULL); + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); } static void retroactively_stop_dependencies(Unit *u) { @@ -1730,7 +1806,7 @@ static void retroactively_stop_dependencies(Unit *u) { /* Pull down units which are bound to us recursively if enabled */ SET_FOREACH(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, true, NULL, NULL); + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); } static void check_unneeded_dependencies(Unit *u) { @@ -1744,18 +1820,12 @@ static void check_unneeded_dependencies(Unit *u) { SET_FOREACH(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_REQUIRES_OVERRIDABLE], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); SET_FOREACH(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) if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_REQUISITE_OVERRIDABLE], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) unit_check_unneeded(other); @@ -1775,7 +1845,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_job_mode, true, NULL, NULL); + r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, NULL, NULL); if (r < 0) log_unit_error_errno(u, r, "Failed to enqueue OnFailure= job: %m"); } @@ -1809,28 +1879,26 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su m = u->manager; /* Update timestamps for state changes */ - if (m->n_reloading <= 0) { - dual_timestamp ts; - - dual_timestamp_get(&ts); + if (!MANAGER_IS_RELOADING(m)) { + dual_timestamp_get(&u->state_change_timestamp); if (UNIT_IS_INACTIVE_OR_FAILED(os) && !UNIT_IS_INACTIVE_OR_FAILED(ns)) - u->inactive_exit_timestamp = ts; + u->inactive_exit_timestamp = u->state_change_timestamp; else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_INACTIVE_OR_FAILED(ns)) - u->inactive_enter_timestamp = ts; + u->inactive_enter_timestamp = u->state_change_timestamp; if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) - u->active_enter_timestamp = ts; + u->active_enter_timestamp = u->state_change_timestamp; else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) - u->active_exit_timestamp = ts; + u->active_exit_timestamp = u->state_change_timestamp; } /* Keep track of failed units */ - manager_update_failed_units(u->manager, u, ns == UNIT_FAILED); + (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)) - unit_destroy_cgroup_if_empty(u); + unit_prune_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 @@ -1841,13 +1909,13 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su 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 --; + 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 ++; + m->n_on_console++; } } @@ -1871,27 +1939,28 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su case JOB_VERIFY_ACTIVE: if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) - job_finish_and_invalidate(u->job, JOB_DONE, true); + job_finish_and_invalidate(u->job, JOB_DONE, true, false); else if (u->job->state == JOB_RUNNING && ns != UNIT_ACTIVATING) { unexpected = true; if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true); + job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); } break; case JOB_RELOAD: case JOB_RELOAD_OR_START: + case JOB_TRY_RELOAD: if (u->job->state == JOB_RUNNING) { if (ns == UNIT_ACTIVE) - job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED, true); + job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED, true, false); else if (ns != UNIT_ACTIVATING && ns != UNIT_RELOADING) { unexpected = true; if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true); + job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true, false); } } @@ -1902,10 +1971,10 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su case JOB_TRY_RESTART: if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - job_finish_and_invalidate(u->job, JOB_DONE, true); + job_finish_and_invalidate(u->job, JOB_DONE, true, false); else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { unexpected = true; - job_finish_and_invalidate(u->job, JOB_FAILED, true); + job_finish_and_invalidate(u->job, JOB_FAILED, true, false); } break; @@ -1917,7 +1986,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su } else unexpected = true; - if (m->n_reloading <= 0) { + if (!MANAGER_IS_RELOADING(m)) { /* If this state change happened without being * requested by a job, then let's retroactively start @@ -1954,7 +2023,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su if (u->type == UNIT_SERVICE && !UNIT_IS_ACTIVE_OR_RELOADING(os) && - m->n_reloading <= 0) { + !MANAGER_IS_RELOADING(m)) { /* Write audit record if we have just finished starting up */ manager_send_unit_audit(m, u, AUDIT_SERVICE_START, true); u->in_audit = true; @@ -1971,7 +2040,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su if (u->type == UNIT_SERVICE && UNIT_IS_INACTIVE_OR_FAILED(ns) && !UNIT_IS_INACTIVE_OR_FAILED(os) && - m->n_reloading <= 0) { + !MANAGER_IS_RELOADING(m)) { /* Hmm, if there was no start record written * write it now, so that we always have a nice @@ -1992,7 +2061,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su manager_recheck_journal(m); unit_trigger_notify(u); - if (u->manager->n_reloading <= 0) { + if (!MANAGER_IS_RELOADING(u->manager)) { /* Maybe we finished startup and are now ready for * being stopped because unneeded? */ unit_check_unneeded(u); @@ -2026,16 +2095,16 @@ int unit_watch_pid(Unit *u, pid_t pid) { if (r < 0) return r; - r = hashmap_put(u->manager->watch_pids1, LONG_TO_PTR(pid), u); + r = hashmap_put(u->manager->watch_pids1, PID_TO_PTR(pid), u); if (r == -EEXIST) { r = hashmap_ensure_allocated(&u->manager->watch_pids2, NULL); if (r < 0) return r; - r = hashmap_put(u->manager->watch_pids2, LONG_TO_PTR(pid), u); + r = hashmap_put(u->manager->watch_pids2, PID_TO_PTR(pid), u); } - q = set_put(u->pids, LONG_TO_PTR(pid)); + q = set_put(u->pids, PID_TO_PTR(pid)); if (q < 0) return q; @@ -2046,81 +2115,18 @@ void unit_unwatch_pid(Unit *u, pid_t pid) { assert(u); assert(pid >= 1); - 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) hashmap_remove_value(u->manager->watch_pids1, PID_TO_PTR(pid), u); + (void) hashmap_remove_value(u->manager->watch_pids2, PID_TO_PTR(pid), u); + (void) set_remove(u->pids, PID_TO_PTR(pid)); } void unit_unwatch_all_pids(Unit *u) { assert(u); while (!set_isempty(u->pids)) - unit_unwatch_pid(u, PTR_TO_LONG(set_first(u->pids))); + unit_unwatch_pid(u, PTR_TO_PID(set_first(u->pids))); - set_free(u->pids); - u->pids = NULL; -} - -static int unit_watch_pids_in_path(Unit *u, const char *path) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_fclose_ FILE *f = NULL; - int ret = 0, r; - - assert(u); - assert(path); - - /* Adds all PIDs from a specific cgroup path to the set of PIDs we watch. */ - - r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f); - if (r >= 0) { - pid_t pid; - - 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; - - } else if (ret >= 0) - ret = r; - - r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d); - if (r >= 0) { - char *fn; - - while ((r = cg_read_subgroup(d, &fn)) > 0) { - _cleanup_free_ char *p = NULL; - - p = strjoin(path, "/", fn, NULL); - free(fn); - - if (!p) - return -ENOMEM; - - r = unit_watch_pids_in_path(u, p); - if (r < 0 && ret >= 0) - ret = r; - } - if (r < 0 && ret >= 0) - ret = r; - - } else if (ret >= 0) - ret = r; - - return ret; -} - -int unit_watch_all_pids(Unit *u) { - assert(u); - - /* 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); + u->pids = set_free(u->pids); } void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2) { @@ -2132,7 +2138,7 @@ void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2) { /* Cleans dead PIDs from our list */ SET_FOREACH(e, u->pids, i) { - pid_t pid = PTR_TO_LONG(e); + pid_t pid = PTR_TO_PID(e); if (pid == except1 || pid == except2) continue; @@ -2159,6 +2165,7 @@ bool unit_job_is_applicable(Unit *u, JobType j) { return unit_can_start(u); case JOB_RELOAD: + case JOB_TRY_RELOAD: return unit_can_reload(u); case JOB_RELOAD_OR_START: @@ -2186,16 +2193,12 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = { [UNIT_REQUIRES] = UNIT_REQUIRED_BY, - [UNIT_REQUIRES_OVERRIDABLE] = UNIT_REQUIRED_BY_OVERRIDABLE, [UNIT_WANTS] = UNIT_WANTED_BY, [UNIT_REQUISITE] = UNIT_REQUISITE_OF, - [UNIT_REQUISITE_OVERRIDABLE] = UNIT_REQUISITE_OF_OVERRIDABLE, [UNIT_BINDS_TO] = UNIT_BOUND_BY, [UNIT_PART_OF] = UNIT_CONSISTS_OF, [UNIT_REQUIRED_BY] = UNIT_REQUIRES, - [UNIT_REQUIRED_BY_OVERRIDABLE] = UNIT_REQUIRES_OVERRIDABLE, [UNIT_REQUISITE_OF] = UNIT_REQUISITE, - [UNIT_REQUISITE_OF_OVERRIDABLE] = UNIT_REQUISITE_OVERRIDABLE, [UNIT_WANTED_BY] = UNIT_WANTS, [UNIT_BOUND_BY] = UNIT_BINDS_TO, [UNIT_CONSISTS_OF] = UNIT_PART_OF, @@ -2229,6 +2232,11 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen return 0; } + if (d == UNIT_BEFORE && other->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); if (r < 0) return r; @@ -2374,47 +2382,9 @@ int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency 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) { - _cleanup_free_ char *buf = NULL; - Unit *other; - int r; - - assert(u); - assert(name || path); - - r = resolve_template(u, name, path, &buf, &name); - if (r < 0) - return r; - - r = manager_load_unit(u->manager, name, path, NULL, &other); - if (r < 0) - 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) { - _cleanup_free_ char *buf = NULL; - Unit *other; - int r; - - assert(u); - assert(name || path); - - r = resolve_template(u, name, path, &buf, &name); - if (r < 0) - return r; - - r = manager_load_unit(u->manager, name, path, NULL, &other); - if (r < 0) - return r; - - return unit_add_two_dependencies(other, d, e, u, add_reference); -} - int set_unit_path(const char *p) { /* This is mostly for debug purposes */ - if (setenv("SYSTEMD_UNIT_PATH", p, 0) < 0) + if (setenv("SYSTEMD_UNIT_PATH", p, 1) < 0) return -errno; return 0; @@ -2429,39 +2399,60 @@ char *unit_dbus_path(Unit *u) { return unit_dbus_path_from_name(u->id); } -char *unit_default_cgroup_path(Unit *u) { - _cleanup_free_ char *escaped = NULL, *slice = NULL; - int r; +char *unit_dbus_path_invocation_id(Unit *u) { + assert(u); + + if (sd_id128_is_null(u->invocation_id)) + return NULL; + return unit_dbus_path_from_name(u->invocation_id_string); +} + +int unit_set_slice(Unit *u, Unit *slice) { assert(u); + assert(slice); - if (unit_has_name(u, SPECIAL_ROOT_SLICE)) - return strdup(u->manager->cgroup_root); + /* Sets the unit slice if it has not been set before. Is extra + * careful, to only allow this for units that actually have a + * cgroup context. Also, we don't allow to set this for slices + * (since the parent slice is derived from the name). Make + * sure the unit we set is actually a slice. */ - if (UNIT_ISSET(u->slice) && !unit_has_name(UNIT_DEREF(u->slice), SPECIAL_ROOT_SLICE)) { - r = cg_slice_to_path(UNIT_DEREF(u->slice)->id, &slice); - if (r < 0) - return NULL; - } + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return -EOPNOTSUPP; - escaped = cg_escape(u->id); - if (!escaped) - return NULL; + if (u->type == UNIT_SLICE) + return -EINVAL; - if (slice) - return strjoin(u->manager->cgroup_root, "/", slice, "/", escaped, NULL); - else - return strjoin(u->manager->cgroup_root, "/", escaped, NULL); + if (unit_active_state(u) != UNIT_INACTIVE) + return -EBUSY; + + if (slice->type != UNIT_SLICE) + return -EINVAL; + + if (unit_has_name(u, SPECIAL_INIT_SCOPE) && + !unit_has_name(slice, SPECIAL_ROOT_SLICE)) + return -EPERM; + + if (UNIT_DEREF(u->slice) == slice) + return 0; + + /* Disallow slice changes if @u is already bound to cgroups */ + if (UNIT_ISSET(u->slice) && u->cgroup_realized) + return -EBUSY; + + unit_ref_unset(&u->slice); + unit_ref_set(&u->slice, slice); + return 1; } -int unit_add_default_slice(Unit *u, CGroupContext *c) { +int unit_set_default_slice(Unit *u) { _cleanup_free_ char *b = NULL; const char *slice_name; Unit *slice; int r; assert(u); - assert(c); if (UNIT_ISSET(u->slice)) return 0; @@ -2483,7 +2474,7 @@ int unit_add_default_slice(Unit *u, CGroupContext *c) { if (!escaped) return -ENOMEM; - if (u->manager->running_as == MANAGER_SYSTEM) + if (MANAGER_IS_SYSTEM(u->manager)) b = strjoin("system-", escaped, ".slice", NULL); else b = strappend(escaped, ".slice"); @@ -2493,7 +2484,7 @@ int unit_add_default_slice(Unit *u, CGroupContext *c) { slice_name = b; } else slice_name = - u->manager->running_as == MANAGER_SYSTEM + MANAGER_IS_SYSTEM(u->manager) && !unit_has_name(u, SPECIAL_INIT_SCOPE) ? SPECIAL_SYSTEM_SLICE : SPECIAL_ROOT_SLICE; @@ -2501,8 +2492,7 @@ int unit_add_default_slice(Unit *u, CGroupContext *c) { if (r < 0) return r; - unit_ref_set(&u->slice, slice); - return 0; + return unit_set_slice(u, slice); } const char *unit_slice_name(Unit *u) { @@ -2533,14 +2523,70 @@ int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { return r; } +static int signal_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *name, *old_owner, *new_owner; + Unit *u = userdata; + int r; + + assert(message); + assert(u); + + r = sd_bus_message_read(message, "sss", &name, &old_owner, &new_owner); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (UNIT_VTABLE(u)->bus_name_owner_change) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner); + + return 0; +} + +int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name) { + const char *match; + + assert(u); + assert(bus); + assert(name); + + if (u->match_bus_slot) + return -EBUSY; + + match = strjoina("type='signal'," + "sender='org.freedesktop.DBus'," + "path='/org/freedesktop/DBus'," + "interface='org.freedesktop.DBus'," + "member='NameOwnerChanged'," + "arg0='", name, "'"); + + return sd_bus_add_match(bus, &u->match_bus_slot, match, signal_name_owner_changed, u); +} + int unit_watch_bus_name(Unit *u, const char *name) { + int r; + assert(u); assert(name); /* Watch a specific name on the bus. We only support one unit * watching each name for now. */ - return hashmap_put(u->manager->watch_bus, name, u); + if (u->manager->api_bus) { + /* If the bus is already available, install the match directly. + * Otherwise, just put the name in the list. bus_setup_api() will take care later. */ + r = unit_install_bus_match(u, u->manager->api_bus, name); + if (r < 0) + return log_warning_errno(r, "Failed to subscribe to NameOwnerChanged signal for '%s': %m", name); + } + + r = hashmap_put(u->manager->watch_bus, name, u); + if (r < 0) { + u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); + return log_warning_errno(r, "Failed to put bus name to hashmap: %m"); + } + + return 0; } void unit_unwatch_bus_name(Unit *u, const char *name) { @@ -2548,6 +2594,7 @@ void unit_unwatch_bus_name(Unit *u, const char *name) { assert(name); hashmap_remove_value(u->manager->watch_bus, name, u); + u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); } bool unit_can_serialize(Unit *u) { @@ -2578,10 +2625,13 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { } } + dual_timestamp_serialize(f, "state-change-timestamp", &u->state_change_timestamp); + 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); @@ -2592,21 +2642,34 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { unit_serialize_item(u, f, "assert-result", yes_no(u->assert_result)); unit_serialize_item(u, f, "transient", yes_no(u->transient)); - unit_serialize_item_format(u, f, "cpuacct-usage-base", "%" PRIu64, u->cpuacct_usage_base); + + 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); if (u->cgroup_path) unit_serialize_item(u, f, "cgroup", u->cgroup_path); unit_serialize_item(u, f, "cgroup-realized", yes_no(u->cgroup_realized)); + if (uid_is_valid(u->ref_uid)) + unit_serialize_item_format(u, f, "ref-uid", UID_FMT, u->ref_uid); + if (gid_is_valid(u->ref_gid)) + unit_serialize_item_format(u, f, "ref-gid", GID_FMT, u->ref_gid); + + if (!sd_id128_is_null(u->invocation_id)) + unit_serialize_item_format(u, f, "invocation-id", SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(u->invocation_id)); + + bus_track_serialize(u->bus_track, f, "ref"); + if (serialize_jobs) { if (u->job) { fprintf(f, "job\n"); - job_serialize(u->job, f, fds); + job_serialize(u->job, f); } if (u->nop_job) { fprintf(f, "job\n"); - job_serialize(u->nop_job, f, fds); + job_serialize(u->nop_job, f); } } @@ -2615,65 +2678,78 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { return 0; } -void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *format, ...) { - va_list ap; - +int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { assert(u); assert(f); assert(key); - assert(format); + + if (!value) + return 0; fputs(key, f); fputc('=', f); - - va_start(ap, format); - vfprintf(f, format, ap); - va_end(ap); - + fputs(value, f); fputc('\n', f); + + return 1; } -void unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { +int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value) { + _cleanup_free_ char *c = NULL; + assert(u); assert(f); assert(key); - assert(value); - fprintf(f, "%s=%s\n", key, value); + if (!value) + return 0; + + c = cescape(value); + if (!c) + return -ENOMEM; + + fputs(key, f); + fputc('=', f); + fputs(c, f); + fputc('\n', f); + + return 1; } -static int unit_set_cgroup_path(Unit *u, const char *path) { - _cleanup_free_ char *p = NULL; - int r; +int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd) { + int copy; assert(u); + assert(f); + assert(key); - if (path) { - p = strdup(path); - if (!p) - return -ENOMEM; - } else - p = NULL; - - if (streq_ptr(u->cgroup_path, p)) + if (fd < 0) return 0; - if (p) { - r = hashmap_put(u->manager->cgroup_unit, p, u); - if (r < 0) - return r; - } + copy = fdset_put_dup(fds, fd); + if (copy < 0) + return copy; - if (u->cgroup_path) { - log_unit_debug(u, "Changing cgroup path from %s to %s.", u->cgroup_path, strna(p)); - hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); - free(u->cgroup_path); - } + fprintf(f, "%s=%i\n", key, copy); + return 1; +} + +void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *format, ...) { + va_list ap; - u->cgroup_path = p; - p = NULL; + assert(u); + assert(f); + assert(key); + assert(format); - return 0; + fputs(key, f); + fputc('=', f); + + va_start(ap, format); + vfprintf(f, format, ap); + va_end(ap); + + fputc('\n', f); } int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { @@ -2704,7 +2780,7 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { /* End marker */ if (isempty(l)) - return 0; + break; k = strcspn(l, "="); @@ -2723,7 +2799,7 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { if (!j) return log_oom(); - r = job_deserialize(j, f, fds); + r = job_deserialize(j, f); if (r < 0) { job_free(j); return r; @@ -2744,6 +2820,9 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { } else /* legacy for pre-44 */ log_unit_warning(u, "Update from too old systemd versions are unsupported, cannot deserialize job: %s", v); continue; + } else if (streq(l, "state-change-timestamp")) { + dual_timestamp_deserialize(v, &u->state_change_timestamp); + continue; } else if (streq(l, "inactive-exit-timestamp")) { dual_timestamp_deserialize(v, &u->inactive_exit_timestamp); continue; @@ -2792,11 +2871,19 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { continue; - } else if (streq(l, "cpuacct-usage-base")) { + } else if (STR_IN_SET(l, "cpu-usage-base", "cpuacct-usage-base")) { - r = safe_atou64(v, &u->cpuacct_usage_base); + r = safe_atou64(v, &u->cpu_usage_base); if (r < 0) - log_unit_debug(u, "Failed to parse CPU usage %s, ignoring.", v); + log_unit_debug(u, "Failed to parse CPU usage base %s, ignoring.", v); + + continue; + + } else if (streq(l, "cpu-usage-last")) { + + r = safe_atou64(v, &u->cpu_usage_last); + if (r < 0) + log_unit_debug(u, "Failed to read CPU usage last %s, ignoring.", v); continue; @@ -2806,6 +2893,8 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { if (r < 0) log_unit_debug_errno(u, r, "Failed to set cgroup path %s, ignoring: %m", v); + (void) unit_watch_cgroup(u); + continue; } else if (streq(l, "cgroup-realized")) { int b; @@ -2817,6 +2906,47 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { u->cgroup_realized = b; continue; + + } else if (streq(l, "ref-uid")) { + uid_t uid; + + r = parse_uid(v, &uid); + if (r < 0) + log_unit_debug(u, "Failed to parse referenced UID %s, ignoring.", v); + else + unit_ref_uid_gid(u, uid, GID_INVALID); + + continue; + + } else if (streq(l, "ref-gid")) { + gid_t gid; + + r = parse_gid(v, &gid); + if (r < 0) + log_unit_debug(u, "Failed to parse referenced GID %s, ignoring.", v); + else + unit_ref_uid_gid(u, UID_INVALID, gid); + + } else if (streq(l, "ref")) { + + r = strv_extend(&u->deserialized_refs, v); + if (r < 0) + log_oom(); + + continue; + } else if (streq(l, "invocation-id")) { + sd_id128_t id; + + r = sd_id128_from_string(v, &id); + if (r < 0) + log_unit_debug(u, "Failed to parse invocation id %s, ignoring.", v); + else { + r = unit_set_invocation_id(u, id); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to set invocation ID for unit: %m"); + } + + continue; } if (unit_can_serialize(u)) { @@ -2837,9 +2967,18 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { log_unit_warning(u, "Failed to deserialize unit parameter '%s', ignoring.", l); } } + + /* Versions before 228 did not carry a state change timestamp. In this case, take the current time. This is + * useful, so that timeouts based on this timestamp don't trigger too early, and is in-line with the logic from + * before 228 where the base for timeouts was not persistent across reboots. */ + + if (!dual_timestamp_is_set(&u->state_change_timestamp)) + dual_timestamp_get(&u->state_change_timestamp); + + return 0; } -int unit_add_node_link(Unit *u, const char *what, bool wants) { +int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency dep) { Unit *device; _cleanup_free_ char *e = NULL; int r; @@ -2866,7 +3005,9 @@ int unit_add_node_link(Unit *u, const char *what, bool wants) { if (r < 0) return r; - r = unit_add_two_dependencies(u, UNIT_AFTER, u->manager->running_as == MANAGER_SYSTEM ? UNIT_BINDS_TO : UNIT_WANTS, device, true); + r = unit_add_two_dependencies(u, UNIT_AFTER, + MANAGER_IS_SYSTEM(u->manager) ? dep : UNIT_WANTS, + device, true); if (r < 0) return r; @@ -2880,7 +3021,8 @@ int unit_add_node_link(Unit *u, const char *what, bool wants) { } int unit_coldplug(Unit *u) { - int r; + int r = 0, q; + char **i; assert(u); @@ -2891,81 +3033,69 @@ int unit_coldplug(Unit *u) { u->coldplugged = true; + STRV_FOREACH(i, u->deserialized_refs) { + q = bus_unit_track_add_name(u, *i); + if (q < 0 && r >= 0) + r = q; + } + u->deserialized_refs = strv_free(u->deserialized_refs); + if (UNIT_VTABLE(u)->coldplug) { - r = UNIT_VTABLE(u)->coldplug(u); - if (r < 0) - return r; + q = UNIT_VTABLE(u)->coldplug(u); + if (q < 0 && r >= 0) + r = q; } if (u->job) { - r = job_coldplug(u->job); - if (r < 0) - return r; + q = job_coldplug(u->job); + if (q < 0 && r >= 0) + r = q; } - return 0; + return r; } -void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) { - DISABLE_WARNING_FORMAT_NONLITERAL; - manager_status_printf(u->manager, STATUS_TYPE_NORMAL, - status, unit_status_msg_format, unit_description(u)); - REENABLE_WARNING; +static bool fragment_mtime_newer(const char *path, usec_t mtime) { + struct stat st; + + if (!path) + return false; + + if (stat(path, &st) < 0) + /* What, cannot access this anymore? */ + return true; + + if (mtime > 0) + /* For non-empty files check the mtime */ + return timespec_load(&st.st_mtim) > mtime; + else if (!null_or_empty(&st)) + /* For masked files check if they are still so */ + return true; + + return false; } bool unit_need_daemon_reload(Unit *u) { _cleanup_strv_free_ char **t = NULL; char **path; - struct stat st; - unsigned loaded_cnt, current_cnt; assert(u); - if (u->fragment_path) { - zero(st); - if (stat(u->fragment_path, &st) < 0) - /* What, cannot access this anymore? */ - return true; - - if (u->fragment_mtime > 0 && - timespec_load(&st.st_mtim) != u->fragment_mtime) - return true; - } - - if (u->source_path) { - zero(st); - if (stat(u->source_path, &st) < 0) - return true; + if (fragment_mtime_newer(u->fragment_path, u->fragment_mtime)) + return true; - if (u->source_mtime > 0 && - timespec_load(&st.st_mtim) != u->source_mtime) - return true; - } + if (fragment_mtime_newer(u->source_path, u->source_mtime)) + return true; (void) unit_find_dropin_paths(u, &t); - loaded_cnt = strv_length(t); - current_cnt = strv_length(u->dropin_paths); - - if (loaded_cnt == current_cnt) { - if (loaded_cnt == 0) - return false; - - if (strv_overlap(u->dropin_paths, t)) { - STRV_FOREACH(path, u->dropin_paths) { - zero(st); - if (stat(*path, &st) < 0) - return true; - - if (u->dropin_mtime > 0 && - timespec_load(&st.st_mtim) > u->dropin_mtime) - return true; - } + if (!strv_equal(u->dropin_paths, t)) + return true; - return false; - } else + STRV_FOREACH(path, u->dropin_paths) + if (fragment_mtime_newer(*path, u->dropin_mtime)) return true; - } else - return true; + + return false; } void unit_reset_failed(Unit *u) { @@ -2973,6 +3103,9 @@ void unit_reset_failed(Unit *u) { if (UNIT_VTABLE(u)->reset_failed) UNIT_VTABLE(u)->reset_failed(u); + + RATELIMIT_RESET(u->start_limit); + u->start_limit_hit = false; } Unit *unit_following(Unit *u) { @@ -3030,8 +3163,7 @@ bool unit_active_or_pending(Unit *u) { 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); - assert(signo < _NSIG); + assert(SIGNAL_VALID(signo)); if (!UNIT_VTABLE(u)->kill) return -EOPNOTSUPP; @@ -3049,13 +3181,13 @@ static Set *unit_pid_set(pid_t main_pid, pid_t control_pid) { /* 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)); + r = set_put(pid_set, PID_TO_PTR(main_pid)); if (r < 0) goto fail; } if (control_pid > 0) { - r = set_put(pid_set, LONG_TO_PTR(control_pid)); + r = set_put(pid_set, PID_TO_PTR(control_pid)); if (r < 0) goto fail; } @@ -3076,32 +3208,39 @@ int unit_kill_common( sd_bus_error *error) { int r = 0; + bool killed = false; - if (who == KILL_MAIN && main_pid <= 0) { + if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL)) { if (main_pid < 0) return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type)); - else + else if (main_pid == 0) 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 (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL)) { if (control_pid < 0) return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no control processes", unit_type_to_string(u->type)); - else + else if (control_pid == 0) return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); } - if (who == KILL_CONTROL || who == KILL_ALL) - if (control_pid > 0) + if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL, KILL_ALL, KILL_ALL_FAIL)) + if (control_pid > 0) { if (kill(control_pid, signo) < 0) r = -errno; + else + killed = true; + } - if (who == KILL_MAIN || who == KILL_ALL) - if (main_pid > 0) + if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL, KILL_ALL, KILL_ALL_FAIL)) + if (main_pid > 0) { if (kill(main_pid, signo) < 0) r = -errno; + else + killed = true; + } - if (who == KILL_ALL && u->cgroup_path) { + if (IN_SET(who, KILL_ALL, KILL_ALL_FAIL) && u->cgroup_path) { _cleanup_set_free_ Set *pid_set = NULL; int q; @@ -3110,11 +3249,16 @@ int unit_kill_common( if (!pid_set) return -ENOMEM; - q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, true, false, pid_set); + 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) r = q; + else + killed = true; } + if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL)) + return -ESRCH; + return r; } @@ -3130,12 +3274,19 @@ int unit_following_set(Unit *u, Set **s) { } UnitFileState unit_get_unit_file_state(Unit *u) { + int r; + assert(u); - if (u->unit_file_state < 0 && u->fragment_path) - u->unit_file_state = unit_file_get_state( - u->manager->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, - NULL, basename(u->fragment_path)); + if (u->unit_file_state < 0 && u->fragment_path) { + r = unit_file_get_state( + u->manager->unit_file_scope, + NULL, + basename(u->fragment_path), + &u->unit_file_state); + if (r < 0) + u->unit_file_state = UNIT_FILE_BAD; + } return u->unit_file_state; } @@ -3145,8 +3296,9 @@ int unit_get_unit_file_preset(Unit *u) { if (u->unit_file_preset < 0 && u->fragment_path) u->unit_file_preset = unit_file_query_preset( - u->manager->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, - NULL, basename(u->fragment_path)); + u->manager->unit_file_scope, + NULL, + basename(u->fragment_path)); return u->unit_file_preset; } @@ -3169,10 +3321,41 @@ void unit_ref_unset(UnitRef *ref) { if (!ref->unit) return; + /* We are about to drop a reference to the unit, make sure the garbage collection has a look at it as it might + * be unreferenced now. */ + unit_add_to_gc_queue(ref->unit); + LIST_REMOVE(refs, ref->unit->refs, ref); ref->unit = NULL; } +static int user_from_unit_name(Unit *u, char **ret) { + + static const uint8_t hash_key[] = { + 0x58, 0x1a, 0xaf, 0xe6, 0x28, 0x58, 0x4e, 0x96, + 0xb4, 0x4e, 0xf5, 0x3b, 0x8c, 0x92, 0x07, 0xec + }; + + _cleanup_free_ char *n = NULL; + int r; + + r = unit_name_to_prefix(u->id, &n); + if (r < 0) + return r; + + if (valid_user_group_name(n)) { + *ret = n; + n = NULL; + return 0; + } + + /* If we can't use the unit name as a user name, then let's hash it and use that */ + if (asprintf(ret, "_du%016" PRIx64, siphash24(n, strlen(n), hash_key)) < 0) + return -ENOMEM; + + return 0; +} + int unit_patch_contexts(Unit *u) { CGroupContext *cc; ExecContext *ec; @@ -3195,7 +3378,7 @@ int unit_patch_contexts(Unit *u) { return -ENOMEM; } - if (u->manager->running_as == MANAGER_USER && + if (MANAGER_IS_USER(u->manager) && !ec->working_directory) { r = get_home_dir(&ec->working_directory); @@ -3207,7 +3390,7 @@ int unit_patch_contexts(Unit *u) { ec->working_directory_missing_ok = true; } - if (u->manager->running_as == MANAGER_USER && + if (MANAGER_IS_USER(u->manager) && (ec->syscall_whitelist || !set_isempty(ec->syscall_filter) || !set_isempty(ec->syscall_archs) || @@ -3216,7 +3399,30 @@ int unit_patch_contexts(Unit *u) { ec->no_new_privileges = true; if (ec->private_devices) - ec->capability_bounding_set_drop |= (uint64_t) 1ULL << (uint64_t) CAP_MKNOD; + ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD); + + if (ec->dynamic_user) { + if (!ec->user) { + r = user_from_unit_name(u, &ec->user); + if (r < 0) + return r; + } + + if (!ec->group) { + ec->group = strdup(ec->user); + if (!ec->group) + return -ENOMEM; + } + + /* If the dynamic user option is on, let's make sure that the unit can't leave its UID/GID + * around in the file system or on IPC objects. Hence enforce a strict sandbox. */ + + ec->private_tmp = true; + ec->remove_ipc = true; + ec->protect_system = PROTECT_SYSTEM_STRICT; + if (ec->protect_home == PROTECT_HOME_NO) + ec->protect_home = PROTECT_HOME_READ_ONLY; + } } cc = unit_get_cgroup_context(u); @@ -3285,71 +3491,65 @@ ExecRuntime *unit_get_exec_runtime(Unit *u) { 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 == MANAGER_USER) { - int r; - - if (mode == UNIT_PERSISTENT && !transient) - r = user_config_home(dir); - else - r = user_runtime_dir(dir); +static const char* unit_drop_in_dir(Unit *u, UnitSetPropertiesMode mode) { + assert(u); - if (r == 0) - return -ENOENT; - return r; - } + if (!IN_SET(mode, UNIT_RUNTIME, UNIT_PERSISTENT)) + return NULL; - if (mode == UNIT_PERSISTENT && !transient) - *dir = strdup("/etc/systemd/system"); - else - *dir = strdup("/run/systemd/system"); - if (!*dir) - return -ENOMEM; + if (u->transient) /* Redirect drop-ins for transient units always into the transient directory. */ + return u->manager->lookup_paths.transient; - return 0; -} + if (mode == UNIT_RUNTIME) + return u->manager->lookup_paths.runtime_control; -static int unit_drop_in_file(Unit *u, - UnitSetPropertiesMode mode, const char *name, char **p, char **q) { - _cleanup_free_ char *dir = NULL; - int r; + if (mode == UNIT_PERSISTENT) + return u->manager->lookup_paths.persistent_control; - 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); + return NULL; } int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { - - _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL; + _cleanup_free_ char *p = NULL, *q = NULL; + const char *dir, *wrapped; int r; assert(u); + if (u->transient_file) { + /* When this is a transient unit file in creation, then let's not create a new drop-in but instead + * write to the transient unit file. */ + fputs(data, u->transient_file); + fputc('\n', u->transient_file); + return 0; + } + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) return 0; - r = unit_drop_in_dir(u, mode, u->transient, &dir); - if (r < 0) - return r; + dir = unit_drop_in_dir(u, mode); + if (!dir) + return -EINVAL; + + wrapped = strjoina("# This is a drop-in unit file extension, created via \"systemctl set-property\"\n" + "# or an equivalent operation. Do not edit.\n", + data, + "\n"); - r = write_drop_in(dir, u->id, 50, name, data); + r = drop_in_file(dir, u->id, 50, name, &p, &q); if (r < 0) return r; - r = drop_in_file(dir, u->id, 50, name, &p, &q); + (void) mkdir_p(p, 0755); + r = write_string_file_atomic_label(q, wrapped); if (r < 0) return r; - r = strv_extend(&u->dropin_paths, q); + r = strv_push(&u->dropin_paths, q); if (r < 0) return r; + q = NULL; - strv_sort(u->dropin_paths); strv_uniq(u->dropin_paths); u->dropin_mtime = now(CLOCK_REALTIME); @@ -3380,7 +3580,7 @@ int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *n } int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { - _cleanup_free_ char *ndata = NULL; + const char *ndata; assert(u); assert(name); @@ -3392,9 +3592,7 @@ int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char * if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) return 0; - ndata = strjoin("[", UNIT_VTABLE(u)->private_section, "]\n", data, NULL); - if (!ndata) - return -ENOMEM; + ndata = strjoina("[", UNIT_VTABLE(u)->private_section, "]\n", data); return unit_write_drop_in(u, mode, name, ndata); } @@ -3421,63 +3619,89 @@ int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const 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; +int unit_make_transient(Unit *u) { + FILE *f; + char *path; assert(u); - if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) - return 0; + if (!UNIT_VTABLE(u)->can_transient) + return -EOPNOTSUPP; - r = unit_drop_in_file(u, mode, name, &p, &q); - if (r < 0) - return r; + path = strjoin(u->manager->lookup_paths.transient, "/", u->id, NULL); + if (!path) + return -ENOMEM; - if (unlink(q) < 0) - r = errno == ENOENT ? 0 : -errno; - else - r = 1; + /* Let's open the file we'll write the transient settings into. This file is kept open as long as we are + * creating the transient, and is closed in unit_load(), as soon as we start loading the file. */ - rmdir(p); - return r; -} + RUN_WITH_UMASK(0022) { + f = fopen(path, "we"); + if (!f) { + free(path); + return -errno; + } + } -int unit_make_transient(Unit *u) { - int r; + if (u->transient_file) + fclose(u->transient_file); + u->transient_file = f; - assert(u); + free(u->fragment_path); + u->fragment_path = path; + + u->source_path = mfree(u->source_path); + u->dropin_paths = strv_free(u->dropin_paths); + u->fragment_mtime = u->source_mtime = u->dropin_mtime = 0; u->load_state = UNIT_STUB; u->load_error = 0; u->transient = true; - free(u->fragment_path); - u->fragment_path = NULL; + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); - if (u->manager->running_as == MANAGER_USER) { - _cleanup_free_ char *c = NULL; + fputs("# This is a transient unit file, created programmatically via the systemd API. Do not edit.\n", + u->transient_file); - r = user_runtime_dir(&c); - if (r < 0) - return r; - if (r == 0) - return -ENOENT; + return 0; +} - u->fragment_path = strjoin(c, "/", u->id, NULL); - if (!u->fragment_path) - return -ENOMEM; +static void log_kill(pid_t pid, int sig, void *userdata) { + _cleanup_free_ char *comm = NULL; - mkdir_p(c, 0755); - } else { - u->fragment_path = strappend("/run/systemd/system/", u->id); - if (!u->fragment_path) - return -ENOMEM; + (void) get_process_comm(pid, &comm); - mkdir_p("/run/systemd/system", 0755); - } + /* Don't log about processes marked with brackets, under the assumption that these are temporary processes + only, like for example systemd's own PAM stub process. */ + if (comm && comm[0] == '(') + return; + + log_unit_notice(userdata, + "Killing process " PID_FMT " (%s) with signal SIG%s.", + pid, + strna(comm), + signal_to_string(sig)); +} + +static int operation_to_signal(KillContext *c, KillOperation k) { + assert(c); - return write_string_file_atomic_label(u->fragment_path, "# Transient stub"); + switch (k) { + + case KILL_TERMINATE: + case KILL_TERMINATE_AND_LOG: + return c->kill_signal; + + case KILL_KILL: + return SIGKILL; + + case KILL_ABORT: + return SIGABRT; + + default: + assert_not_reached("KillOperation unknown"); + } } int unit_kill_context( @@ -3488,62 +3712,69 @@ int unit_kill_context( pid_t control_pid, bool main_pid_alien) { - int sig, wait_for_exit = false, r; + bool wait_for_exit = false, send_sighup; + cg_kill_log_func_t log_func; + int sig, r; assert(u); assert(c); + /* Kill the processes belonging to this unit, in preparation for shutting the unit down. Returns > 0 if we + * killed something worth waiting for, 0 otherwise. */ + if (c->kill_mode == KILL_NONE) return 0; - 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"); - } + sig = operation_to_signal(c, k); + + send_sighup = + c->send_sighup && + IN_SET(k, KILL_TERMINATE, KILL_TERMINATE_AND_LOG) && + sig != SIGHUP; + + log_func = + k != KILL_TERMINATE || + IN_SET(sig, SIGKILL, SIGABRT) ? log_kill : NULL; if (main_pid > 0) { - r = kill_and_sigcont(main_pid, sig); + if (log_func) + log_func(main_pid, sig, u); + r = kill_and_sigcont(main_pid, sig); if (r < 0 && r != -ESRCH) { _cleanup_free_ char *comm = NULL; - get_process_comm(main_pid, &comm); + (void) get_process_comm(main_pid, &comm); - log_unit_warning_errno(u, r, "Failed to kill main process " PID_FMT " (%s): %m", main_pid, strna(comm)); + log_unit_warning_errno(u, r, "Failed to kill main process " PID_FMT " (%s), ignoring: %m", main_pid, strna(comm)); } else { if (!main_pid_alien) wait_for_exit = true; - if (c->send_sighup && k != KILL_KILL) - kill(main_pid, SIGHUP); + if (r != -ESRCH && send_sighup) + (void) kill(main_pid, SIGHUP); } } if (control_pid > 0) { - r = kill_and_sigcont(control_pid, sig); + if (log_func) + log_func(control_pid, sig, u); + r = kill_and_sigcont(control_pid, sig); if (r < 0 && r != -ESRCH) { _cleanup_free_ char *comm = NULL; - get_process_comm(control_pid, &comm); + (void) get_process_comm(control_pid, &comm); - log_unit_warning_errno(u, r, "Failed to kill control process " PID_FMT " (%s): %m", control_pid, strna(comm)); + log_unit_warning_errno(u, r, "Failed to kill control process " PID_FMT " (%s), ignoring: %m", control_pid, strna(comm)); } else { wait_for_exit = true; - if (c->send_sighup && k != KILL_KILL) - kill(control_pid, SIGHUP); + if (r != -ESRCH && send_sighup) + (void) kill(control_pid, SIGHUP); } } - if ((c->kill_mode == KILL_CONTROL_GROUP || (c->kill_mode == KILL_MIXED && k == KILL_KILL)) && u->cgroup_path) { + if (u->cgroup_path && + (c->kill_mode == KILL_CONTROL_GROUP || (c->kill_mode == KILL_MIXED && k == KILL_KILL))) { _cleanup_set_free_ Set *pid_set = NULL; /* Exclude the main/control pids from being killed via the cgroup */ @@ -3551,30 +3782,47 @@ int unit_kill_context( if (!pid_set) return -ENOMEM; - r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, sig, true, true, false, pid_set); + r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, + sig, + CGROUP_SIGCONT|CGROUP_IGNORE_SELF, + pid_set, + log_func, u); if (r < 0) { if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) - log_unit_warning_errno(u, r, "Failed to kill control group: %m"); - } else if (r > 0) { + log_unit_warning_errno(u, r, "Failed to kill control group %s, ignoring: %m", u->cgroup_path); - /* 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. */ + } else if (r > 0) { - /* wait_for_exit = true; */ + /* FIXME: For now, on the legacy hierarchy, we + * will not wait for the cgroup members to die + * if we are running in a container or if this + * is a delegation unit, simply because cgroup + * notification is unreliable in these + * cases. It doesn't work at all in + * containers, and outside of containers it + * can be confused easily by left-over + * directories in the cgroup — which however + * should not exist in non-delegated units. On + * the unified hierarchy that's different, + * there we get proper events. Hence rely on + * them.*/ + + if (cg_unified(SYSTEMD_CGROUP_CONTROLLER) > 0 || + (detect_container() == 0 && !unit_cgroup_delegate(u))) + wait_for_exit = true; - if (c->send_sighup && k != KILL_KILL) { + if (send_sighup) { 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); + cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, + SIGHUP, + CGROUP_IGNORE_SELF, + pid_set, + NULL, NULL); } } } @@ -3683,6 +3931,26 @@ int unit_setup_exec_runtime(Unit *u) { return exec_runtime_make(rt, unit_get_exec_context(u), u->id); } +int unit_setup_dynamic_creds(Unit *u) { + ExecContext *ec; + DynamicCreds *dcreds; + size_t offset; + + assert(u); + + offset = UNIT_VTABLE(u)->dynamic_creds_offset; + assert(offset > 0); + dcreds = (DynamicCreds*) ((uint8_t*) u + offset); + + ec = unit_get_exec_context(u); + assert(ec); + + if (!ec->dynamic_user) + return 0; + + return dynamic_creds_acquire(dcreds, u->manager, ec->user, ec->group); +} + bool unit_type_supported(UnitType t) { if (_unlikely_(t < 0)) return false; @@ -3741,13 +4009,233 @@ int unit_fail_if_symlink(Unit *u, const char* where) { return -ELOOP; } -static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { - [UNIT_ACTIVE] = "active", - [UNIT_RELOADING] = "reloading", - [UNIT_INACTIVE] = "inactive", - [UNIT_FAILED] = "failed", - [UNIT_ACTIVATING] = "activating", - [UNIT_DEACTIVATING] = "deactivating" -}; +bool unit_is_pristine(Unit *u) { + assert(u); + + /* Check if the unit already exists or is already around, + * in a number of different ways. Note that to cater for unit + * types such as slice, we are generally fine with units that + * are marked UNIT_LOADED even though nothing was + * actually loaded, as those unit types don't require a file + * on disk to validly load. */ + + return !(!IN_SET(u->load_state, UNIT_NOT_FOUND, UNIT_LOADED) || + u->fragment_path || + u->source_path || + !strv_isempty(u->dropin_paths) || + u->job || + u->merged_into); +} + +pid_t unit_control_pid(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->control_pid) + return UNIT_VTABLE(u)->control_pid(u); + + return 0; +} -DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); +pid_t unit_main_pid(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->main_pid) + return UNIT_VTABLE(u)->main_pid(u); + + return 0; +} + +static void unit_unref_uid_internal( + Unit *u, + uid_t *ref_uid, + bool destroy_now, + void (*_manager_unref_uid)(Manager *m, uid_t uid, bool destroy_now)) { + + assert(u); + assert(ref_uid); + assert(_manager_unref_uid); + + /* Generic implementation of both unit_unref_uid() and unit_unref_gid(), under the assumption that uid_t and + * gid_t are actually the same time, with the same validity rules. + * + * Drops a reference to UID/GID from a unit. */ + + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + assert_cc(UID_INVALID == (uid_t) GID_INVALID); + + if (!uid_is_valid(*ref_uid)) + return; + + _manager_unref_uid(u->manager, *ref_uid, destroy_now); + *ref_uid = UID_INVALID; +} + +void unit_unref_uid(Unit *u, bool destroy_now) { + unit_unref_uid_internal(u, &u->ref_uid, destroy_now, manager_unref_uid); +} + +void unit_unref_gid(Unit *u, bool destroy_now) { + unit_unref_uid_internal(u, (uid_t*) &u->ref_gid, destroy_now, manager_unref_gid); +} + +static int unit_ref_uid_internal( + Unit *u, + uid_t *ref_uid, + uid_t uid, + bool clean_ipc, + int (*_manager_ref_uid)(Manager *m, uid_t uid, bool clean_ipc)) { + + int r; + + assert(u); + assert(ref_uid); + assert(uid_is_valid(uid)); + assert(_manager_ref_uid); + + /* Generic implementation of both unit_ref_uid() and unit_ref_guid(), under the assumption that uid_t and gid_t + * are actually the same type, and have the same validity rules. + * + * Adds a reference on a specific UID/GID to this unit. Each unit referencing the same UID/GID maintains a + * reference so that we can destroy the UID/GID's IPC resources as soon as this is requested and the counter + * drops to zero. */ + + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + assert_cc(UID_INVALID == (uid_t) GID_INVALID); + + if (*ref_uid == uid) + return 0; + + if (uid_is_valid(*ref_uid)) /* Already set? */ + return -EBUSY; + + r = _manager_ref_uid(u->manager, uid, clean_ipc); + if (r < 0) + return r; + + *ref_uid = uid; + return 1; +} + +int unit_ref_uid(Unit *u, uid_t uid, bool clean_ipc) { + return unit_ref_uid_internal(u, &u->ref_uid, uid, clean_ipc, manager_ref_uid); +} + +int unit_ref_gid(Unit *u, gid_t gid, bool clean_ipc) { + return unit_ref_uid_internal(u, (uid_t*) &u->ref_gid, (uid_t) gid, clean_ipc, manager_ref_gid); +} + +static int unit_ref_uid_gid_internal(Unit *u, uid_t uid, gid_t gid, bool clean_ipc) { + int r = 0, q = 0; + + assert(u); + + /* Reference both a UID and a GID in one go. Either references both, or neither. */ + + if (uid_is_valid(uid)) { + r = unit_ref_uid(u, uid, clean_ipc); + if (r < 0) + return r; + } + + if (gid_is_valid(gid)) { + q = unit_ref_gid(u, gid, clean_ipc); + if (q < 0) { + if (r > 0) + unit_unref_uid(u, false); + + return q; + } + } + + return r > 0 || q > 0; +} + +int unit_ref_uid_gid(Unit *u, uid_t uid, gid_t gid) { + ExecContext *c; + int r; + + assert(u); + + c = unit_get_exec_context(u); + + r = unit_ref_uid_gid_internal(u, uid, gid, c ? c->remove_ipc : false); + if (r < 0) + return log_unit_warning_errno(u, r, "Couldn't add UID/GID reference to unit, proceeding without: %m"); + + return r; +} + +void unit_unref_uid_gid(Unit *u, bool destroy_now) { + assert(u); + + unit_unref_uid(u, destroy_now); + unit_unref_gid(u, destroy_now); +} + +void unit_notify_user_lookup(Unit *u, uid_t uid, gid_t gid) { + int r; + + assert(u); + + /* This is invoked whenever one of the forked off processes let's us know the UID/GID its user name/group names + * resolved to. We keep track of which UID/GID is currently assigned in order to be able to destroy its IPC + * objects when no service references the UID/GID anymore. */ + + r = unit_ref_uid_gid(u, uid, gid); + if (r > 0) + bus_unit_send_change_signal(u); +} + +int unit_set_invocation_id(Unit *u, sd_id128_t id) { + int r; + + assert(u); + + /* Set the invocation ID for this unit. If we cannot, this will not roll back, but reset the whole thing. */ + + if (sd_id128_equal(u->invocation_id, id)) + return 0; + + if (!sd_id128_is_null(u->invocation_id)) + (void) hashmap_remove_value(u->manager->units_by_invocation_id, &u->invocation_id, u); + + if (sd_id128_is_null(id)) { + r = 0; + goto reset; + } + + r = hashmap_ensure_allocated(&u->manager->units_by_invocation_id, &id128_hash_ops); + if (r < 0) + goto reset; + + u->invocation_id = id; + sd_id128_to_string(id, u->invocation_id_string); + + r = hashmap_put(u->manager->units_by_invocation_id, &u->invocation_id, u); + if (r < 0) + goto reset; + + return 0; + +reset: + u->invocation_id = SD_ID128_NULL; + u->invocation_id_string[0] = 0; + return r; +} + +int unit_acquire_invocation_id(Unit *u) { + sd_id128_t id; + int r; + + assert(u); + + r = sd_id128_randomize(&id); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to generate invocation ID for unit: %m"); + + r = unit_set_invocation_id(u, id); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to set invocation ID for unit: %m"); + + return 0; +} |