X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fcore%2Funit.c;h=1db7d061c4d46437f98a5808c88b3ef0bb1c8272;hp=86aaa1509a02eb56ebec204440afeee056390dad;hb=6891529fe1176c046ece579807ff48e3191692f3;hpb=a5c32cff1f56afe6f0c6c70d91a88a7a8238b2d7 diff --git a/src/core/unit.c b/src/core/unit.c index 86aaa1509..1db7d061c 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -44,10 +44,10 @@ #include "special.h" #include "cgroup-util.h" #include "missing.h" -#include "cgroup-attr.h" #include "mkdir.h" #include "label.h" #include "fileio-label.h" +#include "bus-errors.h" const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = &service_vtable, @@ -59,7 +59,9 @@ const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_AUTOMOUNT] = &automount_vtable, [UNIT_SNAPSHOT] = &snapshot_vtable, [UNIT_SWAP] = &swap_vtable, - [UNIT_PATH] = &path_vtable + [UNIT_PATH] = &path_vtable, + [UNIT_SLICE] = &slice_vtable, + [UNIT_SCOPE] = &scope_vtable }; Unit *unit_new(Manager *m, size_t size) { @@ -187,7 +189,8 @@ fail: } int unit_choose_id(Unit *u, const char *name) { - char *s, *t = NULL, *i; + char *s, *i; + _cleanup_free_ char *t = NULL; int r; assert(u); @@ -206,7 +209,6 @@ int unit_choose_id(Unit *u, const char *name) { /* Selects one of the names of this unit as the id */ s = set_get(u->names, (char*) name); - free(t); if (!s) return -ENOENT; @@ -229,8 +231,13 @@ int unit_set_description(Unit *u, const char *description) { assert(u); - if (!(s = strdup(description))) - return -ENOMEM; + if (isempty(description)) + s = NULL; + else { + s = strdup(description); + if (!s) + return -ENOMEM; + } free(u->description); u->description = s; @@ -304,9 +311,6 @@ void unit_add_to_gc_queue(Unit *u) { u->in_gc_queue = true; u->manager->n_in_gc_queue ++; - - if (u->manager->gc_queue_timestamp <= 0) - u->manager->gc_queue_timestamp = now(CLOCK_MONOTONIC); } void unit_add_to_dbus_queue(Unit *u) { @@ -347,6 +351,57 @@ static void bidi_set_free(Unit *u, Set *s) { set_free(s); } +static void unit_remove_transient(Unit *u) { + char **i; + + assert(u); + + if (!u->transient) + return; + + if (u->fragment_path) + unlink(u->fragment_path); + + STRV_FOREACH(i, u->dropin_paths) { + _cleanup_free_ char *p = NULL; + int r; + + unlink(*i); + + r = path_get_parent(*i, &p); + if (r >= 0) + rmdir(p); + } +} + +static void unit_free_requires_mounts_for(Unit *u) { + char **j; + + STRV_FOREACH(j, u->requires_mounts_for) { + char s[strlen(*j) + 1]; + + PATH_FOREACH_PREFIX_MORE(s, *j) { + char *y; + Set *x; + + x = hashmap_get2(u->manager->units_requiring_mounts_for, s, (void**) &y); + if (!x) + continue; + + set_remove(x, u); + + if (set_isempty(x)) { + hashmap_remove(u->manager->units_requiring_mounts_for, y); + free(y); + set_free(x); + } + } + } + + strv_free(u->requires_mounts_for); + u->requires_mounts_for = NULL; +} + void unit_free(Unit *u) { UnitDependency d; Iterator i; @@ -354,12 +409,17 @@ void unit_free(Unit *u) { assert(u); + if (u->manager->n_reloading <= 0) + unit_remove_transient(u); + bus_unit_send_removed_signal(u); if (u->load_state != UNIT_STUB) if (UNIT_VTABLE(u)->done) UNIT_VTABLE(u)->done(u); + unit_free_requires_mounts_for(u); + SET_FOREACH(t, u->names, i) hashmap_remove_value(u->manager->units, t, u); @@ -378,11 +438,6 @@ void unit_free(Unit *u) { for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) bidi_set_free(u, u->dependencies[d]); - if (u->requires_mounts_for) { - LIST_REMOVE(Unit, has_requires_mounts_for, u->manager->has_requires_mounts_for, u); - strv_free(u->requires_mounts_for); - } - if (u->type != _UNIT_TYPE_INVALID) LIST_REMOVE(Unit, units_by_type, u->manager->units_by_type[u->type], u); @@ -400,19 +455,27 @@ void unit_free(Unit *u) { u->manager->n_in_gc_queue--; } - cgroup_bonding_free_list(u->cgroup_bondings, u->manager->n_reloading <= 0); - cgroup_attribute_free_list(u->cgroup_attributes); + if (u->in_cgroup_queue) + LIST_REMOVE(Unit, cgroup_queue, u->manager->cgroup_queue, u); + + if (u->cgroup_path) { + hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); + free(u->cgroup_path); + } free(u->description); strv_free(u->documentation); free(u->fragment_path); free(u->source_path); + strv_free(u->dropin_paths); free(u->instance); set_free_free(u->names); condition_free_list(u->conditions); + unit_ref_unset(&u->slice); + while (u->refs) unit_ref_unset(u->refs); @@ -519,7 +582,7 @@ int unit_merge(Unit *u, Unit *other) { return -EINVAL; if (other->load_state != UNIT_STUB && - other->load_state != UNIT_ERROR) + other->load_state != UNIT_NOT_FOUND) return -EEXIST; if (other->job) @@ -560,7 +623,7 @@ int unit_merge(Unit *u, Unit *other) { int unit_merge_by_name(Unit *u, const char *name) { Unit *other; int r; - char *s = NULL; + _cleanup_free_ char *s = NULL; assert(u); assert(name); @@ -575,12 +638,12 @@ int unit_merge_by_name(Unit *u, const char *name) { name = s; } - if (!(other = manager_get_unit(u->manager, name))) + other = manager_get_unit(u->manager, name); + if (!other) r = unit_add_name(u, name); else r = unit_merge(u, other); - free(s); return r; } @@ -616,9 +679,11 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { /* If syslog or kernel logging is requested, make sure our own * logging daemon is run first. */ - if (u->manager->running_as == SYSTEMD_SYSTEM) - if ((r = unit_add_two_dependencies_by_name(u, UNIT_REQUIRES, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true)) < 0) + if (u->manager->running_as == SYSTEMD_SYSTEM) { + r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true); + if (r < 0) return r; + } return 0; } @@ -636,7 +701,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { char *t, **j; UnitDependency d; Iterator i; - char *p2; + _cleanup_free_ char *p2 = NULL; const char *prefix2; char timestamp1[FORMAT_TIMESTAMP_MAX], @@ -665,7 +730,12 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tActive Exit Timestamp: %s\n" "%s\tInactive Enter Timestamp: %s\n" "%s\tGC Check Good: %s\n" - "%s\tNeed Daemon Reload: %s\n", + "%s\tNeed Daemon Reload: %s\n" + "%s\tTransient: %s\n" + "%s\tSlice: %s\n" + "%s\tCGroup: %s\n" + "%s\tCGroup realized: %s\n" + "%s\tCGroup mask: 0x%x\n", prefix, u->id, prefix, unit_description(u), prefix, strna(u->instance), @@ -676,7 +746,12 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->active_exit_timestamp.realtime)), prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->inactive_enter_timestamp.realtime)), prefix, yes_no(unit_check_gc(u)), - prefix, yes_no(unit_need_daemon_reload(u))); + prefix, yes_no(unit_need_daemon_reload(u)), + prefix, yes_no(u->transient), + prefix, strna(unit_slice_name(u)), + prefix, strna(u->cgroup_path), + prefix, yes_no(u->cgroup_realized), + prefix, u->cgroup_mask); SET_FOREACH(t, u->names, i) fprintf(f, "%s\tName: %s\n", prefix, t); @@ -693,8 +768,11 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { if (u->source_path) fprintf(f, "%s\tSource Path: %s\n", prefix, u->source_path); + STRV_FOREACH(j, u->dropin_paths) + fprintf(f, "%s\tDropIn Path: %s\n", prefix, *j); + if (u->job_timeout > 0) - fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout)); + fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0)); condition_dump_list(u->conditions, f, prefix); @@ -723,8 +801,6 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { } if (u->load_state == UNIT_LOADED) { - CGroupBonding *b; - CGroupAttribute *a; fprintf(f, "%s\tStopWhenUnneeded: %s\n" @@ -742,22 +818,6 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, yes_no(u->ignore_on_isolate), prefix, yes_no(u->ignore_on_snapshot)); - LIST_FOREACH(by_unit, b, u->cgroup_bondings) - fprintf(f, "%s\tControlGroup: %s:%s\n", - prefix, b->controller, b->path); - - LIST_FOREACH(by_unit, a, u->cgroup_attributes) { - char *v = NULL; - - if (a->map_callback) - a->map_callback(a->controller, a->name, a->value, &v); - - fprintf(f, "%s\tControlGroupAttribute: %s %s \"%s\"\n", - prefix, a->controller, a->name, v ? v : a->value); - - free(v); - } - if (UNIT_VTABLE(u)->dump) UNIT_VTABLE(u)->dump(u, f, prefix2); @@ -775,7 +835,6 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { if (u->nop_job) job_dump(u->nop_job, f, prefix2); - free(p2); } /* Common implementation for multiple backends */ @@ -785,14 +844,16 @@ int unit_load_fragment_and_dropin(Unit *u) { assert(u); /* Load a .service file */ - if ((r = unit_load_fragment(u)) < 0) + r = unit_load_fragment(u); + if (r < 0) return r; if (u->load_state == UNIT_STUB) return -ENOENT; /* Load drop-in directory data */ - if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) + r = unit_load_dropin(unit_follow_merge(u)); + if (r < 0) return r; return 0; @@ -808,14 +869,16 @@ int unit_load_fragment_and_dropin_optional(Unit *u) { * something can be loaded or not doesn't matter. */ /* Load a .service file */ - if ((r = unit_load_fragment(u)) < 0) + r = unit_load_fragment(u); + if (r < 0) return r; if (u->load_state == UNIT_STUB) u->load_state = UNIT_LOADED; /* Load drop-in directory data */ - if ((r = unit_load_dropin(unit_follow_merge(u))) < 0) + r = unit_load_dropin(unit_follow_merge(u)); + if (r < 0) return r; return 0; @@ -848,6 +911,7 @@ int unit_add_default_target_dependency(Unit *u, Unit *target) { } static int unit_add_default_dependencies(Unit *u) { + static const UnitDependency deps[] = { UNIT_REQUIRED_BY, UNIT_REQUIRED_BY_OVERRIDABLE, @@ -863,9 +927,21 @@ static int unit_add_default_dependencies(Unit *u) { assert(u); for (k = 0; k < ELEMENTSOF(deps); k++) - SET_FOREACH(target, u->dependencies[deps[k]], i) - if ((r = unit_add_default_target_dependency(u, target)) < 0) + SET_FOREACH(target, u->dependencies[deps[k]], i) { + r = unit_add_default_target_dependency(u, target); + if (r < 0) return r; + } + + if (u->default_dependencies && unit_get_cgroup_context(u)) { + if (UNIT_ISSET(u->slice)) + r = unit_add_two_dependencies(u, UNIT_AFTER, UNIT_WANTS, UNIT_DEREF(u->slice), true); + else + r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, SPECIAL_ROOT_SLICE, NULL, true); + + if (r < 0) + return r; + } return 0; } @@ -886,34 +962,38 @@ int unit_load(Unit *u) { if (u->load_state != UNIT_STUB) return 0; - if (UNIT_VTABLE(u)->load) - if ((r = UNIT_VTABLE(u)->load(u)) < 0) + if (UNIT_VTABLE(u)->load) { + r = UNIT_VTABLE(u)->load(u); + if (r < 0) goto fail; + } if (u->load_state == UNIT_STUB) { r = -ENOENT; goto fail; } - if (u->load_state == UNIT_LOADED && - u->default_dependencies) - if ((r = unit_add_default_dependencies(u)) < 0) - goto fail; - if (u->load_state == UNIT_LOADED) { + + if (u->default_dependencies) { + r = unit_add_default_dependencies(u); + if (r < 0) + goto fail; + } + r = unit_add_mount_links(u); if (r < 0) - return r; - } + goto fail; - if (u->on_failure_isolate && - set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + if (u->on_failure_isolate && + set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { - log_error("More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.", - u->id); + log_error_unit(u->id, + "More than one OnFailure= dependencies specified for %s but OnFailureIsolate= enabled. Refusing.", u->id); - r = -EINVAL; - goto fail; + r = -EINVAL; + goto fail; + } } assert((u->load_state != UNIT_MERGED) == !u->merged_into); @@ -924,12 +1004,13 @@ int unit_load(Unit *u) { return 0; fail: - u->load_state = UNIT_ERROR; + u->load_state = u->load_state == UNIT_STUB ? UNIT_NOT_FOUND : UNIT_ERROR; u->load_error = r; unit_add_to_dbus_queue(u); unit_add_to_gc_queue(u); - log_debug("Failed to load configuration for %s: %s", u->id, strerror(-r)); + log_debug_unit(u->id, "Failed to load configuration for %s: %s", + u->id, strerror(-r)); return r; } @@ -938,12 +1019,12 @@ bool unit_condition_test(Unit *u) { assert(u); dual_timestamp_get(&u->condition_timestamp); - u->condition_result = condition_test_list(u->conditions); + u->condition_result = condition_test_list(u->id, u->conditions); return u->condition_result; } -static const char* unit_get_status_message_format(Unit *u, JobType t) { +_pure_ static const char* unit_get_status_message_format(Unit *u, JobType t) { const UnitStatusMessageFormats *format_table; assert(u); @@ -960,7 +1041,7 @@ static const char* unit_get_status_message_format(Unit *u, JobType t) { return format_table->starting_stopping[t == JOB_STOP]; } -static const char *unit_get_status_message_format_try_harder(Unit *u, JobType t) { +_pure_ static const char *unit_get_status_message_format_try_harder(Unit *u, JobType t) { const char *format; assert(u); @@ -994,7 +1075,7 @@ static void unit_status_print_starting_stopping(Unit *u, JobType t) { if (!format) return; - unit_status_printf(u, "", format, unit_description(u)); + unit_status_printf(u, "", format); } #pragma GCC diagnostic push @@ -1026,10 +1107,10 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { SD_MESSAGE_UNIT_RELOADING; log_struct_unit(LOG_INFO, - u->id, - MESSAGE_ID(mid), - "MESSAGE=%s", buf, - NULL); + u->id, + MESSAGE_ID(mid), + "MESSAGE=%s", buf, + NULL); } #pragma GCC diagnostic pop @@ -1062,13 +1143,15 @@ int unit_start(Unit *u) { * but we don't want to recheck the condition in that case. */ if (state != UNIT_ACTIVATING && !unit_condition_test(u)) { - log_debug("Starting of %s requested but condition failed. Ignoring.", u->id); + log_debug_unit(u->id, "Starting of %s requested but condition failed. Ignoring.", u->id); return -EALREADY; } /* Forward to the main object, if we aren't it. */ - if ((following = unit_following(u))) { - log_debug("Redirecting start request from %s to %s.", u->id, following->id); + following = unit_following(u); + if (following) { + log_debug_unit(u->id, "Redirecting start request from %s to %s.", + u->id, following->id); return unit_start(following); } @@ -1119,7 +1202,8 @@ int unit_stop(Unit *u) { return -EALREADY; if ((following = unit_following(u))) { - log_debug("Redirecting stop request from %s to %s.", u->id, following->id); + log_debug_unit(u->id, "Redirecting stop request from %s to %s.", + u->id, following->id); return unit_stop(following); } @@ -1159,7 +1243,8 @@ int unit_reload(Unit *u) { return -ENOEXEC; if ((following = unit_following(u))) { - log_debug("Redirecting reload request from %s to %s.", u->id, following->id); + log_debug_unit(u->id, "Redirecting reload request from %s to %s.", + u->id, following->id); return unit_reload(following); } @@ -1197,22 +1282,22 @@ static void unit_check_unneeded(Unit *u) { return; SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i) - if (unit_pending_active(other)) + if (unit_active_or_pending(other)) return; SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i) - if (unit_pending_active(other)) + if (unit_active_or_pending(other)) return; SET_FOREACH(other, u->dependencies[UNIT_WANTED_BY], i) - if (unit_pending_active(other)) + if (unit_active_or_pending(other)) return; SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) - if (unit_pending_active(other)) + if (unit_active_or_pending(other)) return; - log_info("Service %s is not needed anymore. Stopping.", u->id); + log_info_unit(u->id, "Service %s is not needed anymore. Stopping.", u->id); /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, true, NULL, NULL); @@ -1240,11 +1325,6 @@ static void retroactively_start_dependencies(Unit *u) { !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_START, other, JOB_FAIL, false, NULL, NULL); - SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], 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_WANTS], i) if (!set_get(u->dependencies[UNIT_AFTER], other) && !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) @@ -1300,7 +1380,7 @@ static void check_unneeded_dependencies(Unit *u) { unit_check_unneeded(other); } -void unit_trigger_on_failure(Unit *u) { +void unit_start_on_failure(Unit *u) { Unit *other; Iterator i; @@ -1309,17 +1389,30 @@ void unit_trigger_on_failure(Unit *u) { if (set_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) return; - log_info("Triggering OnFailure= dependencies of %s.", u->id); + log_info_unit(u->id, "Triggering OnFailure= dependencies of %s.", u->id); SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) { int r; - if ((r = manager_add_job(u->manager, JOB_START, other, u->on_failure_isolate ? JOB_ISOLATE : JOB_REPLACE, true, NULL, NULL)) < 0) - log_error("Failed to enqueue OnFailure= job: %s", strerror(-r)); + r = manager_add_job(u->manager, JOB_START, other, u->on_failure_isolate ? JOB_ISOLATE : JOB_REPLACE, true, NULL, NULL); + if (r < 0) + log_error_unit(u->id, "Failed to enqueue OnFailure= job: %s", strerror(-r)); } } +void unit_trigger_notify(Unit *u) { + Unit *other; + Iterator i; + + assert(u); + + SET_FOREACH(other, u->dependencies[UNIT_TRIGGERED_BY], i) + if (UNIT_VTABLE(other)->trigger_notify) + UNIT_VTABLE(other)->trigger_notify(other, u); +} + void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) { + Manager *m; bool unexpected; assert(u); @@ -1332,7 +1425,9 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su * behavior here. For example: if a mount point is remounted * this function will be called too! */ - if (u->manager->n_reloading <= 0) { + m = u->manager; + + if (m->n_reloading <= 0) { dual_timestamp ts; dual_timestamp_get(&ts); @@ -1346,13 +1441,24 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su u->active_enter_timestamp = ts; else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) u->active_exit_timestamp = ts; - - timer_unit_notify(u, ns); - path_unit_notify(u, ns); } if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - cgroup_bonding_trim_list(u->cgroup_bondings, true); + unit_destroy_cgroup(u); + + if (UNIT_IS_INACTIVE_OR_FAILED(os) != UNIT_IS_INACTIVE_OR_FAILED(ns)) { + ExecContext *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 --; + + if (m->n_on_console == 0) + /* unset no_console_output flag, since the console is free */ + m->no_console_output = 0; + } else + m->n_on_console ++; + } + } if (u->job) { unexpected = false; @@ -1420,7 +1526,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su } else unexpected = true; - if (u->manager->n_reloading <= 0) { + if (m->n_reloading <= 0) { /* If this state change happened without being * requested by a job, then let's retroactively start @@ -1441,11 +1547,9 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su check_unneeded_dependencies(u); if (ns != os && ns == UNIT_FAILED) { - log_struct_unit(LOG_NOTICE, - u->id, - "MESSAGE=Unit %s entered failed state", u->id, - NULL); - unit_trigger_on_failure(u); + log_notice_unit(u->id, + "Unit %s entered failed state.", u->id); + unit_start_on_failure(u); } } @@ -1456,18 +1560,18 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su /* The bus just might have become available, * hence try to connect to it, if we aren't * yet connected. */ - bus_init(u->manager, true); + bus_init(m, true); if (u->type == UNIT_SERVICE && !UNIT_IS_ACTIVE_OR_RELOADING(os) && - u->manager->n_reloading <= 0) { + m->n_reloading <= 0) { /* Write audit record if we have just finished starting up */ - manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_START, true); + manager_send_unit_audit(m, u, AUDIT_SERVICE_START, true); u->in_audit = true; } if (!UNIT_IS_ACTIVE_OR_RELOADING(os)) - manager_send_unit_plymouth(u->manager, u); + manager_send_unit_plymouth(m, u); } else { @@ -1477,46 +1581,47 @@ 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) && - u->manager->n_reloading <= 0) { + m->n_reloading <= 0) { /* 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(u->manager, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); + manager_send_unit_audit(m, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); if (ns == UNIT_INACTIVE) - manager_send_unit_audit(u->manager, u, AUDIT_SERVICE_STOP, true); + 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(u->manager, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); + manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); u->in_audit = false; } } - manager_recheck_journal(u->manager); + manager_recheck_journal(m); + unit_trigger_notify(u); /* Maybe we finished startup and are now ready for being * stopped because unneeded? */ - unit_check_unneeded(u); + if (u->manager->n_reloading <= 0) + unit_check_unneeded(u); unit_add_to_dbus_queue(u); unit_add_to_gc_queue(u); } int unit_watch_fd(Unit *u, int fd, uint32_t events, Watch *w) { - struct epoll_event ev; + struct epoll_event ev = { + .data.ptr = w, + .events = events, + }; assert(u); assert(fd >= 0); assert(w); assert(w->type == WATCH_INVALID || (w->type == WATCH_FD && w->fd == fd && w->data.unit == u)); - zero(ev); - ev.data.ptr = w; - ev.events = events; - if (epoll_ctl(u->manager->epoll_fd, w->type == WATCH_INVALID ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, fd, @@ -1564,7 +1669,7 @@ void unit_unwatch_pid(Unit *u, pid_t pid) { } int unit_watch_timer(Unit *u, clockid_t clock_id, bool relative, usec_t usec, Watch *w) { - struct itimerspec its; + struct itimerspec its = {}; int flags, fd; bool ours; @@ -1589,8 +1694,6 @@ int unit_watch_timer(Unit *u, clockid_t clock_id, bool relative, usec_t usec, Wa } else assert_not_reached("Invalid watch type"); - zero(its); - if (usec <= 0) { /* Set absolute time in the past, but not 0, since we * don't want to disarm the timer */ @@ -1608,11 +1711,10 @@ int unit_watch_timer(Unit *u, clockid_t clock_id, bool relative, usec_t usec, Wa goto fail; if (w->type == WATCH_INVALID) { - struct epoll_event ev; - - zero(ev); - ev.data.ptr = w; - ev.events = EPOLLIN; + struct epoll_event ev = { + .data.ptr = w, + .events = EPOLLIN, + }; if (epoll_ctl(u->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) goto fail; @@ -1797,14 +1899,13 @@ static const char *resolve_template(Unit *u, const char *name, const char*path, if (u->instance) s = unit_name_replace_instance(name, u->instance); else { - char *i; + _cleanup_free_ char *i = NULL; i = unit_name_to_prefix(u->id); if (!i) return NULL; s = unit_name_replace_instance(name, i); - free(i); } if (!s) @@ -1836,7 +1937,7 @@ int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, con int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { Unit *other; int r; - char *s; + _cleanup_free_ char *s = NULL; assert(u); assert(name || path); @@ -1845,19 +1946,17 @@ int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency return -ENOMEM; if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) - goto finish; + return r; r = unit_add_two_dependencies(u, d, e, other, add_reference); -finish: - free(s); return r; } int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { Unit *other; int r; - char *s; + _cleanup_free_ char *s = NULL; assert(u); assert(name || path); @@ -1866,19 +1965,17 @@ int unit_add_dependency_by_name_inverse(Unit *u, UnitDependency d, const char *n return -ENOMEM; if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) - goto finish; + return r; r = unit_add_dependency(other, d, u, add_reference); -finish: - free(s); return r; } int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { Unit *other; int r; - char *s; + _cleanup_free_ char *s = NULL; assert(u); assert(name || path); @@ -1887,41 +1984,21 @@ int unit_add_two_dependencies_by_name_inverse(Unit *u, UnitDependency d, UnitDep return -ENOMEM; if ((r = manager_load_unit(u->manager, name, path, NULL, &other)) < 0) - goto finish; + return r; if ((r = unit_add_two_dependencies(other, d, e, u, add_reference)) < 0) - goto finish; + return r; -finish: - free(s); return r; } int set_unit_path(const char *p) { - char *cwd, *c; - int r; + _cleanup_free_ char *c = NULL; /* This is mostly for debug purposes */ - - if (path_is_absolute(p)) { - if (!(c = strdup(p))) - return -ENOMEM; - } else { - if (!(cwd = get_current_dir_name())) - return -errno; - - r = asprintf(&c, "%s/%s", cwd, p); - free(cwd); - - if (r < 0) - return -ENOMEM; - } - - if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) { - r = -errno; - free(c); - return r; - } + c = path_make_absolute_cwd(p); + if (setenv("SYSTEMD_UNIT_PATH", c, 0) < 0) + return -errno; return 0; } @@ -1935,389 +2012,170 @@ char *unit_dbus_path(Unit *u) { return unit_dbus_path_from_name(u->id); } -int unit_add_cgroup(Unit *u, CGroupBonding *b) { +char *unit_default_cgroup_path(Unit *u) { + _cleanup_free_ char *escaped = NULL, *slice = NULL; int r; assert(u); - assert(b); - - assert(b->path); - - if (!b->controller) { - b->controller = strdup(SYSTEMD_CGROUP_CONTROLLER); - if (!b->controller) - return log_oom(); - - b->ours = true; - } - - /* Ensure this hasn't been added yet */ - assert(!b->unit); - - if (streq(b->controller, SYSTEMD_CGROUP_CONTROLLER)) { - CGroupBonding *l; - l = hashmap_get(u->manager->cgroup_bondings, b->path); - LIST_PREPEND(CGroupBonding, by_path, l, b); + if (unit_has_name(u, SPECIAL_ROOT_SLICE)) + return strdup(u->manager->cgroup_root); - r = hashmap_replace(u->manager->cgroup_bondings, b->path, l); - if (r < 0) { - LIST_REMOVE(CGroupBonding, by_path, l, b); - return r; - } + 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; } - LIST_PREPEND(CGroupBonding, by_unit, u->cgroup_bondings, b); - b->unit = u; - - return 0; -} - -char *unit_default_cgroup_path(Unit *u) { - assert(u); - - if (u->instance) { - _cleanup_free_ char *t = NULL; - - t = unit_name_template(u->id); - if (!t) - return NULL; + escaped = cg_escape(u->id); + if (!escaped) + return NULL; - return strjoin(u->manager->cgroup_hierarchy, "/", t, "/", u->instance, NULL); - } else - return strjoin(u->manager->cgroup_hierarchy, "/", u->id, NULL); + if (slice) + return strjoin(u->manager->cgroup_root, "/", slice, "/", escaped, NULL); + else + return strjoin(u->manager->cgroup_root, "/", escaped, NULL); } -int unit_add_cgroup_from_text(Unit *u, const char *name, bool overwrite, CGroupBonding **ret) { - char *controller = NULL, *path = NULL; - CGroupBonding *b = NULL; - bool ours = false; +int unit_add_default_slice(Unit *u) { + _cleanup_free_ char *b = NULL; + const char *slice_name; + Unit *slice; int r; assert(u); - assert(name); - - r = cg_split_spec(name, &controller, &path); - if (r < 0) - return r; - if (!path) { - path = unit_default_cgroup_path(u); - ours = true; - } + if (UNIT_ISSET(u->slice)) + return 0; - if (!controller) { - controller = strdup(SYSTEMD_CGROUP_CONTROLLER); - ours = true; - } + if (!unit_get_cgroup_context(u)) + return 0; - if (!path || !controller) { - free(path); - free(controller); - return log_oom(); - } + if (u->instance) { + _cleanup_free_ char *prefix = NULL, *escaped = NULL; - b = cgroup_bonding_find_list(u->cgroup_bondings, controller); - if (b) { - if (streq(path, b->path)) { - free(path); - free(controller); + /* Implicitly place all instantiated units in their + * own per-template slice */ - if (ret) - *ret = b; - return 0; - } + prefix = unit_name_to_prefix(u->id); + if (!prefix) + return -ENOMEM; - if (overwrite && !b->essential) { - free(controller); + /* The prefix is already escaped, but it might include + * "-" which has a special meaning for slice units, + * hence escape it here extra. */ + escaped = strreplace(prefix, "-", "\\x2d"); + if (!escaped) + return -ENOMEM; - free(b->path); - b->path = path; + if (u->manager->running_as == SYSTEMD_SYSTEM) + b = strjoin("system-", escaped, ".slice", NULL); + else + b = strappend(escaped, ".slice"); + if (!b) + return -ENOMEM; - b->ours = ours; - b->realized = false; + slice_name = b; + } else + slice_name = + u->manager->running_as == SYSTEMD_SYSTEM + ? SPECIAL_SYSTEM_SLICE + : SPECIAL_ROOT_SLICE; - if (ret) - *ret = b; + r = manager_load_unit(u->manager, slice_name, NULL, NULL, &slice); + if (r < 0) + return r; - return 1; - } + unit_ref_set(&u->slice, slice); + return 0; +} - r = -EEXIST; - b = NULL; - goto fail; - } +const char *unit_slice_name(Unit *u) { + assert(u); - b = new0(CGroupBonding, 1); - if (!b) { - r = -ENOMEM; - goto fail; - } + if (!UNIT_ISSET(u->slice)) + return NULL; - b->controller = controller; - b->path = path; - b->ours = ours; - b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER); + return UNIT_DEREF(u->slice)->id; +} - r = unit_add_cgroup(u, b); - if (r < 0) - goto fail; +int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { + _cleanup_free_ char *t = NULL; + int r; - if (ret) - *ret = b; + assert(u); + assert(type); + assert(_found); - return 1; + t = unit_name_change_suffix(u->id, type); + if (!t) + return -ENOMEM; -fail: - free(path); - free(controller); - free(b); + assert(!unit_has_name(u, t)); + r = manager_load_unit(u->manager, t, NULL, NULL, _found); + assert(r < 0 || *_found != u); return r; } -static int unit_add_one_default_cgroup(Unit *u, const char *controller) { - CGroupBonding *b = NULL; - int r = -ENOMEM; +int unit_get_related_unit(Unit *u, const char *type, Unit **_found) { + _cleanup_free_ char *t = NULL; + Unit *found; assert(u); + assert(type); + assert(_found); - if (!controller) - controller = SYSTEMD_CGROUP_CONTROLLER; - - if (cgroup_bonding_find_list(u->cgroup_bondings, controller)) - return 0; - - b = new0(CGroupBonding, 1); - if (!b) + t = unit_name_change_suffix(u->id, type); + if (!t) return -ENOMEM; - b->controller = strdup(controller); - if (!b->controller) - goto fail; - - b->path = unit_default_cgroup_path(u); - if (!b->path) - goto fail; - - b->ours = true; - b->essential = streq(controller, SYSTEMD_CGROUP_CONTROLLER); + assert(!unit_has_name(u, t)); - r = unit_add_cgroup(u, b); - if (r < 0) - goto fail; + found = manager_get_unit(u->manager, t); + if (!found) + return -ENOENT; + *_found = found; return 0; - -fail: - free(b->path); - free(b->controller); - free(b); - - return r; } -int unit_add_default_cgroups(Unit *u) { - CGroupAttribute *a; - char **c; - int r; - +int unit_watch_bus_name(Unit *u, const char *name) { assert(u); + assert(name); - /* Adds in the default cgroups, if they weren't specified - * otherwise. */ - - if (!u->manager->cgroup_hierarchy) - return 0; - - r = unit_add_one_default_cgroup(u, NULL); - if (r < 0) - return r; + /* Watch a specific name on the bus. We only support one unit + * watching each name for now. */ - STRV_FOREACH(c, u->manager->default_controllers) - unit_add_one_default_cgroup(u, *c); + return hashmap_put(u->manager->watch_bus, name, u); +} - LIST_FOREACH(by_unit, a, u->cgroup_attributes) - unit_add_one_default_cgroup(u, a->controller); +void unit_unwatch_bus_name(Unit *u, const char *name) { + assert(u); + assert(name); - return 0; + hashmap_remove_value(u->manager->watch_bus, name, u); } -CGroupBonding* unit_get_default_cgroup(Unit *u) { +bool unit_can_serialize(Unit *u) { assert(u); - return cgroup_bonding_find_list(u->cgroup_bondings, NULL); + return UNIT_VTABLE(u)->serialize && UNIT_VTABLE(u)->deserialize_item; } -int unit_add_cgroup_attribute( - Unit *u, - const char *controller, - const char *name, - const char *value, - CGroupAttributeMapCallback map_callback, - CGroupAttribute **ret) { - - _cleanup_free_ char *c = NULL; - CGroupAttribute *a; +int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { int r; assert(u); - assert(name); - assert(value); + assert(f); + assert(fds); - if (!controller) { - r = cg_controller_from_attr(name, &c); - if (r < 0) - return -EINVAL; + if (!unit_can_serialize(u)) + return 0; - controller = c; - } else { - if (!filename_is_safe(name)) - return -EINVAL; - - if (!filename_is_safe(controller)) - return -EINVAL; - } - - if (!controller || streq(controller, SYSTEMD_CGROUP_CONTROLLER)) - return -EINVAL; - - a = cgroup_attribute_find_list(u->cgroup_attributes, controller, name); - if (a) { - char *v; - - if (streq(value, a->value)) { - if (ret) - *ret = a; - - return 0; - } - - v = strdup(value); - if (!v) - return -ENOMEM; - - free(a->value); - a->value = v; - - if (ret) - *ret = a; - - return 1; - } - - a = new0(CGroupAttribute, 1); - if (!a) - return -ENOMEM; - - if (c) { - a->controller = c; - c = NULL; - } else - a->controller = strdup(controller); - - a->name = strdup(name); - a->value = strdup(value); - - if (!a->controller || !a->name || !a->value) { - free(a->controller); - free(a->name); - free(a->value); - free(a); - - return -ENOMEM; - } - - a->map_callback = map_callback; - a->unit = u; - - LIST_PREPEND(CGroupAttribute, by_unit, u->cgroup_attributes, a); - - if (ret) - *ret = a; - - return 1; -} - -int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { - char *t; - int r; - - assert(u); - assert(type); - assert(_found); - - if (!(t = unit_name_change_suffix(u->id, type))) - return -ENOMEM; - - assert(!unit_has_name(u, t)); - - r = manager_load_unit(u->manager, t, NULL, NULL, _found); - free(t); - - assert(r < 0 || *_found != u); - - return r; -} - -int unit_get_related_unit(Unit *u, const char *type, Unit **_found) { - Unit *found; - char *t; - - assert(u); - assert(type); - assert(_found); - - if (!(t = unit_name_change_suffix(u->id, type))) - return -ENOMEM; - - assert(!unit_has_name(u, t)); - - found = manager_get_unit(u->manager, t); - free(t); - - if (!found) - return -ENOENT; - - *_found = found; - return 0; -} - -int unit_watch_bus_name(Unit *u, const char *name) { - 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); -} - -void unit_unwatch_bus_name(Unit *u, const char *name) { - assert(u); - assert(name); - - hashmap_remove_value(u->manager->watch_bus, name, u); -} - -bool unit_can_serialize(Unit *u) { - assert(u); - - return UNIT_VTABLE(u)->serialize && UNIT_VTABLE(u)->deserialize_item; -} - -int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { - int r; - - assert(u); - assert(f); - assert(fds); - - if (!unit_can_serialize(u)) - return 0; - - if ((r = UNIT_VTABLE(u)->serialize(u, f, fds)) < 0) - return r; + r = UNIT_VTABLE(u)->serialize(u, f, fds); + if (r < 0) + return r; if (serialize_jobs) { @@ -2341,6 +2199,11 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { if (dual_timestamp_is_set(&u->condition_timestamp)) unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); + unit_serialize_item(u, f, "transient", yes_no(u->transient)); + + if (u->cgroup_path) + unit_serialize_item(u, f, "cgroup", u->cgroup_path); + /* End marker */ fputc('\n', f); return 0; @@ -2433,6 +2296,9 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { job_free(j); return r; } + + if (j->state == JOB_RUNNING) + u->manager->n_running_jobs++; } else { /* legacy */ JobType type = job_type_from_string(v); @@ -2460,22 +2326,47 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { } else if (streq(l, "condition-result")) { int b; - if ((b = parse_boolean(v)) < 0) + b = parse_boolean(v); + if (b < 0) log_debug("Failed to parse condition result value %s", v); else u->condition_result = b; continue; + + } else if (streq(l, "transient")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse transient bool %s", v); + else + u->transient = b; + + continue; + } else if (streq(l, "cgroup")) { + char *s; + + s = strdup(v); + if (!s) + return -ENOMEM; + + free(u->cgroup_path); + u->cgroup_path = s; + + assert(hashmap_put(u->manager->cgroup_unit, s, u) == 1); + continue; } - if ((r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds)) < 0) + r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds); + if (r < 0) return r; } } int unit_add_node_link(Unit *u, const char *what, bool wants) { Unit *device; - char *e; + _cleanup_free_ char *e = NULL; int r; assert(u); @@ -2493,7 +2384,7 @@ int unit_add_node_link(Unit *u, const char *what, bool wants) { return -ENOMEM; r = manager_load_unit(u->manager, e, NULL, NULL, &device); - free(e); + if (r < 0) return r; @@ -2535,25 +2426,18 @@ int unit_coldplug(Unit *u) { return 0; } -void unit_status_printf(Unit *u, const char *status, const char *format, ...) { - va_list ap; - - assert(u); - assert(format); - - if (!manager_get_show_status(u->manager)) - return; - - if (!manager_is_booting_or_shutting_down(u->manager)) - return; - - va_start(ap, format); - status_vprintf(status, true, format, ap); - va_end(ap); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) { + manager_status_printf(u->manager, false, status, unit_status_msg_format, unit_description(u)); } +#pragma GCC diagnostic pop 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); @@ -2578,7 +2462,30 @@ bool unit_need_daemon_reload(Unit *u) { return true; } - return false; + t = unit_find_dropin_paths(u); + 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; + } + + return false; + } else + return true; + } else + return true; } void unit_reset_failed(Unit *u) { @@ -2597,7 +2504,19 @@ Unit *unit_following(Unit *u) { return NULL; } -bool unit_pending_inactive(Unit *u) { +bool unit_stop_pending(Unit *u) { + assert(u); + + /* This call does check the current state of the unit. It's + * hence useful to be called from state change calls of the + * unit itself, where the state isn't updated yet. This is + * different from unit_inactive_or_pending() which checks both + * the current state and for a queued job. */ + + return u->job && u->job->type == JOB_STOP; +} + +bool unit_inactive_or_pending(Unit *u) { assert(u); /* Returns true if the unit is inactive or going down */ @@ -2605,13 +2524,13 @@ bool unit_pending_inactive(Unit *u) { if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))) return true; - if (u->job && u->job->type == JOB_STOP) + if (unit_stop_pending(u)) return true; return false; } -bool unit_pending_active(Unit *u) { +bool unit_active_or_pending(Unit *u) { assert(u); /* Returns true if the unit is active or going up */ @@ -2640,6 +2559,87 @@ int unit_kill(Unit *u, KillWho w, int signo, DBusError *error) { return UNIT_VTABLE(u)->kill(u, w, signo, error); } +static Set *unit_pid_set(pid_t main_pid, pid_t control_pid) { + Set *pid_set; + int r; + + pid_set = set_new(trivial_hash_func, trivial_compare_func); + if (!pid_set) + return NULL; + + /* Exclude the main/control pids from being killed via the cgroup */ + if (main_pid > 0) { + r = set_put(pid_set, LONG_TO_PTR(main_pid)); + if (r < 0) + goto fail; + } + + if (control_pid > 0) { + r = set_put(pid_set, LONG_TO_PTR(control_pid)); + if (r < 0) + goto fail; + } + + return pid_set; + +fail: + set_free(pid_set); + return NULL; +} + +int unit_kill_common( + Unit *u, + KillWho who, + int signo, + pid_t main_pid, + pid_t control_pid, + DBusError *error) { + + int r = 0; + + if (who == KILL_MAIN && main_pid <= 0) { + if (main_pid < 0) + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type)); + else + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); + return -ESRCH; + } + + if (who == KILL_CONTROL && control_pid <= 0) { + if (control_pid < 0) + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no control processes", unit_type_to_string(u->type)); + else + dbus_set_error(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + return -ESRCH; + } + + if (who == KILL_CONTROL || who == KILL_ALL) + if (control_pid > 0) + if (kill(control_pid, signo) < 0) + r = -errno; + + if (who == KILL_MAIN || who == KILL_ALL) + if (main_pid > 0) + if (kill(main_pid, signo) < 0) + r = -errno; + + if (who == KILL_ALL && u->cgroup_path) { + _cleanup_set_free_ Set *pid_set = NULL; + int q; + + /* Exclude the main/control pids from being killed via the cgroup */ + pid_set = unit_pid_set(main_pid, control_pid); + if (!pid_set) + return -ENOMEM; + + q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, true, false, pid_set); + if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + } + + return r; +} + int unit_following_set(Unit *u, Set **s) { assert(u); assert(s); @@ -2684,40 +2684,39 @@ void unit_ref_unset(UnitRef *ref) { ref->unit = NULL; } -int unit_add_one_mount_link(Unit *u, Mount *m) { +int unit_add_mount_links(Unit *u) { char **i; + int r; assert(u); - assert(m); - - if (u->load_state != UNIT_LOADED || - UNIT(m)->load_state != UNIT_LOADED) - return 0; STRV_FOREACH(i, u->requires_mounts_for) { + char prefix[strlen(*i) + 1]; - if (UNIT(m) == u) - continue; + PATH_FOREACH_PREFIX_MORE(prefix, *i) { + Unit *m; - if (!path_startswith(*i, m->where)) - continue; - - return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT(m), true); - } + r = manager_get_unit_by_path(u->manager, prefix, ".mount", &m); + if (r < 0) + return r; + if (r == 0) + continue; + if (m == u) + continue; - return 0; -} + if (m->load_state != UNIT_LOADED) + continue; -int unit_add_mount_links(Unit *u) { - Unit *other; - int r; - - assert(u); + r = unit_add_dependency(u, UNIT_AFTER, m, true); + if (r < 0) + return r; - LIST_FOREACH(units_by_type, other, u->manager->units_by_type[UNIT_MOUNT]) { - r = unit_add_one_mount_link(u, MOUNT(other)); - if (r < 0) - return r; + if (m->fragment_path) { + r = unit_add_dependency(u, UNIT_REQUIRES, m, true); + if (r < 0) + return r; + } + } } return 0; @@ -2731,7 +2730,6 @@ int unit_exec_context_defaults(Unit *u, ExecContext *c) { assert(c); /* This only copies in the ones that need memory */ - for (i = 0; i < RLIMIT_NLIMITS; i++) if (u->manager->rlimit[i] && !c->rlimit[i]) { c->rlimit[i] = newdup(struct rlimit, u->manager->rlimit[i], 1); @@ -2761,52 +2759,202 @@ ExecContext *unit_get_exec_context(Unit *u) { return (ExecContext*) ((uint8_t*) u + offset); } -int unit_write_drop_in(Unit *u, bool runtime, const char *name, const char *data) { - _cleanup_free_ char *p = NULL, *q = NULL; +CGroupContext *unit_get_cgroup_context(Unit *u) { + size_t offset; + + offset = UNIT_VTABLE(u)->cgroup_context_offset; + if (offset <= 0) + return NULL; + + return (CGroupContext*) ((uint8_t*) u + offset); +} + +static int drop_in_file(Unit *u, UnitSetPropertiesMode mode, const char *name, char **_p, char **_q) { + _cleanup_free_ char *b = NULL; + char *p, *q; + int r; + assert(u); + assert(name); + assert(_p); + assert(_q); + assert(mode & (UNIT_PERSISTENT|UNIT_RUNTIME)); - if (u->manager->running_as != SYSTEMD_SYSTEM) - return -ENOTSUP; + b = xescape(name, "/."); + if (!b) + return -ENOMEM; - if (!filename_is_safe(name)) + if (!filename_is_safe(b)) return -EINVAL; - p = strjoin(runtime ? "/run/systemd/system/" : "/etc/systemd/system/", u->id, ".d", NULL); + if (u->manager->running_as == SYSTEMD_USER) { + _cleanup_free_ char *c = NULL; + + r = user_config_home(&c); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + p = strjoin(c, "/", u->id, ".d", NULL); + } else if (mode & UNIT_PERSISTENT) + p = strjoin("/etc/systemd/system/", u->id, ".d", NULL); + else + p = strjoin("/run/systemd/system/", u->id, ".d", NULL); if (!p) return -ENOMEM; - q = strjoin(p, "/50-", name, ".conf", NULL); - if (!q) + q = strjoin(p, "/90-", b, ".conf", NULL); + if (!q) { + free(p); return -ENOMEM; + } - mkdir_p(p, 0755); - return write_one_line_file_atomic_label(q, data); + *_p = p; + *_q = q; + return 0; } -int unit_remove_drop_in(Unit *u, bool runtime, const char *name) { +int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { _cleanup_free_ char *p = NULL, *q = NULL; + int r; assert(u); + assert(name); + assert(data); - if (u->manager->running_as != SYSTEMD_SYSTEM) - return -ENOTSUP; + if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + return 0; + + r = drop_in_file(u, mode, name, &p, &q); + if (r < 0) + return r; + + mkdir_p(p, 0755); + return write_string_file_atomic_label(q, data); +} + +int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(u); + assert(name); + assert(format); + + if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + return 0; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return unit_write_drop_in(u, mode, name, p); +} - if (!filename_is_safe(name)) +int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { + _cleanup_free_ char *ndata = NULL; + + assert(u); + assert(name); + assert(data); + + if (!UNIT_VTABLE(u)->private_section) return -EINVAL; - p = strjoin(runtime ? "/run/systemd/system/" : "/etc/systemd/system/", u->id, ".d", NULL); - if (!p) + if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + return 0; + + ndata = strjoin("[", UNIT_VTABLE(u)->private_section, "]\n", data, NULL); + if (!ndata) return -ENOMEM; - q = strjoin(p, "/50-", name, ".conf", NULL); - if (!q) + return unit_write_drop_in(u, mode, name, ndata); +} + +int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(u); + assert(name); + assert(format); + + if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + return 0; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) return -ENOMEM; + return unit_write_drop_in_private(u, mode, name, p); +} + +int unit_remove_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name) { + _cleanup_free_ char *p = NULL, *q = NULL; + int r; + + assert(u); + + if (!(mode & (UNIT_PERSISTENT|UNIT_RUNTIME))) + return 0; + + r = drop_in_file(u, mode, name, &p, &q); + if (r < 0) + return r; + if (unlink(q) < 0) - return -errno; + r = errno == ENOENT ? 0 : -errno; + else + r = 1; rmdir(p); - return 0; + return r; +} + +int unit_make_transient(Unit *u) { + int r; + + assert(u); + + u->load_state = UNIT_STUB; + u->load_error = 0; + u->transient = true; + + free(u->fragment_path); + u->fragment_path = NULL; + + if (u->manager->running_as == SYSTEMD_USER) { + _cleanup_free_ char *c = NULL; + + r = user_config_home(&c); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + u->fragment_path = strjoin(c, "/", u->id, NULL); + if (!u->fragment_path) + return -ENOMEM; + + mkdir_p(c, 0755); + } else { + u->fragment_path = strappend("/run/systemd/system/", u->id); + if (!u->fragment_path) + return -ENOMEM; + + mkdir_p("/run/systemd/system", 0755); + } + + return write_string_file_atomic_label(u->fragment_path, "# Transient stub"); } int unit_kill_context( @@ -2836,8 +2984,12 @@ int unit_kill_context( log_warning_unit(u->id, "Failed to kill main process %li (%s): %s", (long) main_pid, strna(comm), strerror(-r)); - } else + } else { wait_for_exit = !main_pid_alien; + + if (c->send_sighup) + kill(main_pid, SIGHUP); + } } if (control_pid > 0) { @@ -2850,41 +3002,123 @@ int unit_kill_context( log_warning_unit(u->id, "Failed to kill control process %li (%s): %s", (long) control_pid, strna(comm), strerror(-r)); - } else + } else { wait_for_exit = true; + + if (c->send_sighup) + kill(control_pid, SIGHUP); + } } - if (c->kill_mode == KILL_CONTROL_GROUP) { + if (c->kill_mode == KILL_CONTROL_GROUP && u->cgroup_path) { _cleanup_set_free_ Set *pid_set = NULL; - pid_set = set_new(trivial_hash_func, trivial_compare_func); + /* Exclude the main/control pids from being killed via the cgroup */ + pid_set = unit_pid_set(main_pid, control_pid); if (!pid_set) return -ENOMEM; - /* Exclude the main/control pids from being killed via the cgroup */ - if (main_pid > 0) { - r = set_put(pid_set, LONG_TO_PTR(main_pid)); - if (r < 0) - return r; - } - - if (control_pid > 0) { - r = set_put(pid_set, LONG_TO_PTR(control_pid)); - if (r < 0) - return r; - } - - r = cgroup_bonding_kill_list(u->cgroup_bondings, sig, true, false, pid_set, NULL); + r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, sig, true, true, false, pid_set); if (r < 0) { if (r != -EAGAIN && r != -ESRCH && r != -ENOENT) log_warning_unit(u->id, "Failed to kill control group: %s", strerror(-r)); - } else if (r > 0) + } else if (r > 0) { wait_for_exit = true; + if (c->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, true, true, false, pid_set); + } + } } return wait_for_exit; } +int unit_require_mounts_for(Unit *u, const char *path) { + char prefix[strlen(path) + 1], *p; + int r; + + assert(u); + assert(path); + + /* Registers a unit for requiring a certain path and all its + * prefixes. We keep a simple array of these paths in the + * unit, since its usually short. However, we build a prefix + * table for all possible prefixes so that new appearing mount + * units can easily determine which units to make themselves a + * dependency of. */ + + p = strdup(path); + if (!p) + return -ENOMEM; + + path_kill_slashes(p); + + if (!path_is_absolute(p)) { + free(p); + return -EINVAL; + } + + if (!path_is_safe(p)) { + free(p); + return -EPERM; + } + + if (strv_contains(u->requires_mounts_for, p)) { + free(p); + return 0; + } + + r = strv_push(&u->requires_mounts_for, p); + if (r < 0) { + free(p); + return r; + } + + PATH_FOREACH_PREFIX_MORE(prefix, p) { + Set *x; + + x = hashmap_get(u->manager->units_requiring_mounts_for, prefix); + if (!x) { + char *q; + + if (!u->manager->units_requiring_mounts_for) { + u->manager->units_requiring_mounts_for = hashmap_new(string_hash_func, string_compare_func); + if (!u->manager->units_requiring_mounts_for) + return -ENOMEM; + } + + q = strdup(prefix); + if (!q) + return -ENOMEM; + + x = set_new(NULL, NULL); + if (!x) { + free(q); + return -ENOMEM; + } + + r = hashmap_put(u->manager->units_requiring_mounts_for, q, x); + if (r < 0) { + free(q); + set_free(x); + return r; + } + } + + r = set_put(x, u); + if (r < 0) + return r; + } + + return 0; +} + static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { [UNIT_ACTIVE] = "active", [UNIT_RELOADING] = "reloading",