diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 3d6f1a39b3..d55f3c8f9b 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -1630,6 +1630,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as OnFailure = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as OnFailureOf = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as OnSuccess = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as OnSuccessOf = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as Triggers = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as TriggeredBy = ['...', ...]; @@ -1638,8 +1644,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as ReloadPropagatedFrom = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as PropagatesStopTo = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as StopPropagatedFrom = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as JoinsNamespaceOf = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as SliceOf = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as RequiresMountsFor = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as Documentation = ['...', ...]; @@ -1694,6 +1706,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b DefaultDependencies = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s OnSuccesJobMode = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s OnFailureJobMode = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b IgnoreOnIsolate = ...; @@ -1771,10 +1785,22 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + + + + + + + + + @@ -1789,6 +1815,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -1901,6 +1929,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + + + @@ -1909,8 +1943,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + + + @@ -1979,6 +2019,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 913cad05b4..0b5e869321 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -708,6 +708,24 @@ + + Upholds= + + Configures dependencies similar to Wants=, but as long a this unit + is up, all units listed in Upholds= are started whenever found to be inactive or + failed, and no job is queued for them. While a Wants= dependency on another unit + has a one-time effect when this units started, a Upholds= dependency on it has a + continuous effect, constantly restarting the unit if necessary. This is an alternative to the + Restart= setting of service units, to ensure they are kept running whatever + happens. + + When Upholds=b.service is used on a.service, this + dependency will show as UpheldBy=a.service in the property listing of + b.service. The UpheldBy= dependency cannot be specified + directly. + + + Conflicts= @@ -776,24 +794,36 @@ OnFailure= - A space-separated list of one or more units - that are activated when this unit enters the - failed state. A service unit using - Restart= enters the failed state only after - the start limits are reached. + A space-separated list of one or more units that are activated when this unit enters + the failed state. A service unit using Restart= enters the + failed state only after the start limits are reached. + + + + OnSuccess= + + A space-separated list of one or more units that are activated when this unit enters + the inactive state. PropagatesReloadTo= ReloadPropagatedFrom= - A space-separated list of one or more units - where reload requests on this unit will be propagated to, or - reload requests on the other unit will be propagated to this - unit, respectively. Issuing a reload request on a unit will - automatically also enqueue a reload request on all units that - the reload request shall be propagated to via these two - settings. + A space-separated list of one or more units to which reload requests from this unit + shall be propagated to, or units from which reload requests shall be propagated to this unit, + respectively. Issuing a reload request on a unit will automatically also enqueue reload requests on + all units that are linked to it using these two settings. + + + + PropagatesStopTo= + StopPropagatedFrom= + + A space-separated list of one or more units to which stop requests from this unit + shall be propagated to, or units from which stop requests shall be propagated to this unit, + respectively. Issuing a stop request on a unit will automatically also enqueue stop requests on all + units that are linked to it using these two settings. diff --git a/src/basic/hashmap.h b/src/basic/hashmap.h index c855f39b1d..f7ade2e774 100644 --- a/src/basic/hashmap.h +++ b/src/basic/hashmap.h @@ -51,6 +51,7 @@ typedef struct { #define _IDX_ITERATOR_FIRST (UINT_MAX - 1) #define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL }) +#define ITERATOR_IS_FIRST(i) ((i).idx == _IDX_ITERATOR_FIRST) /* Macros for type checking */ #define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \ diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index 5cfabca83f..2667e61dc4 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -265,23 +265,32 @@ static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { [UNIT_WANTS] = "Wants", [UNIT_BINDS_TO] = "BindsTo", [UNIT_PART_OF] = "PartOf", + [UNIT_UPHOLDS] = "Upholds", [UNIT_REQUIRED_BY] = "RequiredBy", [UNIT_REQUISITE_OF] = "RequisiteOf", [UNIT_WANTED_BY] = "WantedBy", [UNIT_BOUND_BY] = "BoundBy", + [UNIT_UPHELD_BY] = "UpheldBy", [UNIT_CONSISTS_OF] = "ConsistsOf", [UNIT_CONFLICTS] = "Conflicts", [UNIT_CONFLICTED_BY] = "ConflictedBy", [UNIT_BEFORE] = "Before", [UNIT_AFTER] = "After", + [UNIT_ON_SUCCESS] = "OnSuccess", + [UNIT_ON_SUCCESS_OF] = "OnSuccessOf", [UNIT_ON_FAILURE] = "OnFailure", + [UNIT_ON_FAILURE_OF] = "OnFailureOf", [UNIT_TRIGGERS] = "Triggers", [UNIT_TRIGGERED_BY] = "TriggeredBy", [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo", [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom", + [UNIT_PROPAGATES_STOP_TO] = "PropagatesStopTo", + [UNIT_STOP_PROPAGATED_FROM] = "StopPropagatedFrom", [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf", [UNIT_REFERENCES] = "References", [UNIT_REFERENCED_BY] = "ReferencedBy", + [UNIT_IN_SLICE] = "InSlice", + [UNIT_SLICE_OF] = "SliceOf", }; DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency); diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index c30135f4d6..08651efa57 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -211,6 +211,7 @@ typedef enum UnitDependency { UNIT_WANTS, UNIT_BINDS_TO, UNIT_PART_OF, + UNIT_UPHOLDS, /* Inverse of the above */ UNIT_REQUIRED_BY, /* inverse of 'requires' is 'required_by' */ @@ -218,6 +219,7 @@ typedef enum UnitDependency { UNIT_WANTED_BY, /* inverse of 'wants' */ UNIT_BOUND_BY, /* inverse of 'binds_to' */ UNIT_CONSISTS_OF, /* inverse of 'part_of' */ + UNIT_UPHELD_BY, /* inverse of 'uphold' */ /* Negative dependencies */ UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */ @@ -227,8 +229,11 @@ typedef enum UnitDependency { UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */ UNIT_AFTER, - /* On Failure */ + /* OnSuccess= + OnFailure= */ + UNIT_ON_SUCCESS, + UNIT_ON_SUCCESS_OF, UNIT_ON_FAILURE, + UNIT_ON_FAILURE_OF, /* Triggers (i.e. a socket triggers a service) */ UNIT_TRIGGERS, @@ -238,6 +243,10 @@ typedef enum UnitDependency { UNIT_PROPAGATES_RELOAD_TO, UNIT_RELOAD_PROPAGATED_FROM, + /* Propagate stops */ + UNIT_PROPAGATES_STOP_TO, + UNIT_STOP_PROPAGATED_FROM, + /* Joins namespace of */ UNIT_JOINS_NAMESPACE_OF, @@ -245,6 +254,10 @@ typedef enum UnitDependency { UNIT_REFERENCES, /* Inverse of 'references' is 'referenced_by' */ UNIT_REFERENCED_BY, + /* Slice= */ + UNIT_IN_SLICE, + UNIT_SLICE_OF, + _UNIT_DEPENDENCY_MAX, _UNIT_DEPENDENCY_INVALID = -EINVAL, } UnitDependency; diff --git a/src/core/bpf-firewall.c b/src/core/bpf-firewall.c index 02e33399c3..2a41bffee6 100644 --- a/src/core/bpf-firewall.c +++ b/src/core/bpf-firewall.c @@ -422,7 +422,7 @@ static int bpf_firewall_prepare_access_maps( assert(ret_ipv6_map_fd); assert(ret_has_any); - for (p = u; p; p = UNIT_DEREF(p->slice)) { + for (p = u; p; p = UNIT_GET_SLICE(p)) { CGroupContext *cc; cc = unit_get_cgroup_context(p); @@ -463,7 +463,7 @@ static int bpf_firewall_prepare_access_maps( return ipv6_map_fd; } - for (p = u; p; p = UNIT_DEREF(p->slice)) { + for (p = u; p; p = UNIT_GET_SLICE(p)) { CGroupContext *cc; cc = unit_get_cgroup_context(p); diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 5453b5ae96..5b00faa6f6 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -680,7 +680,7 @@ int cgroup_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const if (c && c->entry##_set) \ return c->entry; \ \ - while ((u = UNIT_DEREF(u->slice))) { \ + while ((u = UNIT_GET_SLICE(u))) { \ c = unit_get_cgroup_context(u); \ if (c && c->default_##entry##_set) \ return c->default_##entry; \ @@ -1549,7 +1549,7 @@ static bool unit_get_needs_bpf_firewall(Unit *u) { return true; /* If any parent slice has an IP access list defined, it applies too */ - for (p = UNIT_DEREF(u->slice); p; p = UNIT_DEREF(p->slice)) { + for (p = UNIT_GET_SLICE(u); p; p = UNIT_GET_SLICE(p)) { c = unit_get_cgroup_context(p); if (!c) return false; @@ -1700,12 +1700,10 @@ CGroupMask unit_get_members_mask(Unit *u) { u->cgroup_members_mask = 0; if (u->type == UNIT_SLICE) { - void *v; Unit *member; - HASHMAP_FOREACH_KEY(v, member, u->dependencies[UNIT_BEFORE]) - if (UNIT_DEREF(member->slice) == u) - u->cgroup_members_mask |= unit_get_subtree_mask(member); /* note that this calls ourselves again, for the children */ + UNIT_FOREACH_DEPENDENCY(member, u, UNIT_ATOM_SLICE_OF) + u->cgroup_members_mask |= unit_get_subtree_mask(member); /* note that this calls ourselves again, for the children */ } u->cgroup_members_mask_valid = true; @@ -1713,14 +1711,16 @@ CGroupMask unit_get_members_mask(Unit *u) { } CGroupMask unit_get_siblings_mask(Unit *u) { + Unit *slice; assert(u); /* Returns the mask of controllers all of the unit's siblings * require, i.e. the members mask of the unit's parent slice * if there is one. */ - if (UNIT_ISSET(u->slice)) - return unit_get_members_mask(UNIT_DEREF(u->slice)); + slice = UNIT_GET_SLICE(u); + if (slice) + return unit_get_members_mask(slice); return unit_get_subtree_mask(u); /* we are the top-level slice */ } @@ -1737,6 +1737,7 @@ static CGroupMask unit_get_disable_mask(Unit *u) { CGroupMask unit_get_ancestor_disable_mask(Unit *u) { CGroupMask mask; + Unit *slice; assert(u); mask = unit_get_disable_mask(u); @@ -1744,8 +1745,9 @@ CGroupMask unit_get_ancestor_disable_mask(Unit *u) { /* Returns the mask of controllers which are marked as forcibly * disabled in any ancestor unit or the unit in question. */ - if (UNIT_ISSET(u->slice)) - mask |= unit_get_ancestor_disable_mask(UNIT_DEREF(u->slice)); + slice = UNIT_GET_SLICE(u); + if (slice) + mask |= unit_get_ancestor_disable_mask(slice); return mask; } @@ -1787,13 +1789,16 @@ CGroupMask unit_get_enable_mask(Unit *u) { } void unit_invalidate_cgroup_members_masks(Unit *u) { + Unit *slice; + assert(u); /* Recurse invalidate the member masks cache all the way up the tree */ u->cgroup_members_mask_valid = false; - if (UNIT_ISSET(u->slice)) - unit_invalidate_cgroup_members_masks(UNIT_DEREF(u->slice)); + slice = UNIT_GET_SLICE(u); + if (slice) + unit_invalidate_cgroup_members_masks(slice); } const char *unit_get_realized_cgroup_path(Unit *u, CGroupMask mask) { @@ -1807,7 +1812,7 @@ const char *unit_get_realized_cgroup_path(Unit *u, CGroupMask mask) { FLAGS_SET(u->cgroup_realized_mask, mask)) return u->cgroup_path; - u = UNIT_DEREF(u->slice); + u = UNIT_GET_SLICE(u); } return NULL; @@ -1821,7 +1826,8 @@ static const char *migrate_callback(CGroupMask mask, void *userdata) { } char *unit_default_cgroup_path(const Unit *u) { - _cleanup_free_ char *escaped = NULL, *slice = NULL; + _cleanup_free_ char *escaped = NULL, *slice_path = NULL; + Unit *slice; int r; assert(u); @@ -1829,8 +1835,9 @@ char *unit_default_cgroup_path(const Unit *u) { if (unit_has_name(u, SPECIAL_ROOT_SLICE)) return strdup(u->manager->cgroup_root); - 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); + slice = UNIT_GET_SLICE(u); + if (slice && !unit_has_name(slice, SPECIAL_ROOT_SLICE)) { + r = cg_slice_to_path(slice->id, &slice_path); if (r < 0) return NULL; } @@ -1839,7 +1846,7 @@ char *unit_default_cgroup_path(const Unit *u) { if (!escaped) return NULL; - return path_join(empty_to_root(u->manager->cgroup_root), slice, escaped); + return path_join(empty_to_root(u->manager->cgroup_root), slice_path, escaped); } int unit_set_cgroup_path(Unit *u, const char *path) { @@ -2313,14 +2320,16 @@ static void unit_remove_from_cgroup_realize_queue(Unit *u) { * hierarchy downwards to the unit in question. */ static int unit_realize_cgroup_now_enable(Unit *u, ManagerState state) { CGroupMask target_mask, enable_mask, new_target_mask, new_enable_mask; + Unit *slice; int r; assert(u); /* First go deal with this unit's parent, or we won't be able to enable * any new controllers at this layer. */ - if (UNIT_ISSET(u->slice)) { - r = unit_realize_cgroup_now_enable(UNIT_DEREF(u->slice), state); + slice = UNIT_GET_SLICE(u); + if (slice) { + r = unit_realize_cgroup_now_enable(slice, state); if (r < 0) return r; } @@ -2343,36 +2352,29 @@ static int unit_realize_cgroup_now_enable(Unit *u, ManagerState state) { * hierarchy upwards to the unit in question. */ static int unit_realize_cgroup_now_disable(Unit *u, ManagerState state) { Unit *m; - void *v; assert(u); if (u->type != UNIT_SLICE) return 0; - HASHMAP_FOREACH_KEY(v, m, u->dependencies[UNIT_BEFORE]) { + UNIT_FOREACH_DEPENDENCY(m, u, UNIT_ATOM_SLICE_OF) { CGroupMask target_mask, enable_mask, new_target_mask, new_enable_mask; int r; - if (UNIT_DEREF(m->slice) != u) - continue; - - /* The cgroup for this unit might not actually be fully - * realised yet, in which case it isn't holding any controllers - * open anyway. */ + /* The cgroup for this unit might not actually be fully realised yet, in which case it isn't + * holding any controllers open anyway. */ if (!m->cgroup_realized) continue; - /* We must disable those below us first in order to release the - * controller. */ + /* We must disable those below us first in order to release the controller. */ if (m->type == UNIT_SLICE) (void) unit_realize_cgroup_now_disable(m, state); target_mask = unit_get_target_mask(m); enable_mask = unit_get_enable_mask(m); - /* We can only disable in this direction, don't try to enable - * anything. */ + /* We can only disable in this direction, don't try to enable anything. */ if (unit_has_mask_disables_realized(m, target_mask, enable_mask)) continue; @@ -2433,6 +2435,7 @@ static int unit_realize_cgroup_now_disable(Unit *u, ManagerState state) { * Returns 0 on success and < 0 on failure. */ static int unit_realize_cgroup_now(Unit *u, ManagerState state) { CGroupMask target_mask, enable_mask; + Unit *slice; int r; assert(u); @@ -2451,8 +2454,9 @@ static int unit_realize_cgroup_now(Unit *u, ManagerState state) { return r; /* Enable controllers above us, if there are any */ - if (UNIT_ISSET(u->slice)) { - r = unit_realize_cgroup_now_enable(UNIT_DEREF(u->slice), state); + slice = UNIT_GET_SLICE(u); + if (slice) { + r = unit_realize_cgroup_now_enable(slice, state); if (r < 0) return r; } @@ -2515,15 +2519,11 @@ void unit_add_family_to_cgroup_realize_queue(Unit *u) { do { Unit *m; - void *v; /* Children of u likely changed when we're called */ u->cgroup_members_mask_valid = false; - HASHMAP_FOREACH_KEY(v, m, u->dependencies[UNIT_BEFORE]) { - /* Skip units that have a dependency on the slice but aren't actually in it. */ - if (UNIT_DEREF(m->slice) != u) - continue; + UNIT_FOREACH_DEPENDENCY(m, u, UNIT_ATOM_SLICE_OF) { /* No point in doing cgroup application for units without active processes. */ if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(m))) @@ -2534,8 +2534,8 @@ void unit_add_family_to_cgroup_realize_queue(Unit *u) { if (!m->cgroup_realized) continue; - /* If the unit doesn't need any new controllers and has current ones realized, it - * doesn't need any changes. */ + /* If the unit doesn't need any new controllers and has current ones + * realized, it doesn't need any changes. */ if (unit_has_mask_realized(m, unit_get_target_mask(m), unit_get_enable_mask(m))) @@ -2546,10 +2546,14 @@ void unit_add_family_to_cgroup_realize_queue(Unit *u) { /* Parent comes after children */ unit_add_to_cgroup_realize_queue(u); - } while ((u = UNIT_DEREF(u->slice))); + + u = UNIT_GET_SLICE(u); + } while (u); } int unit_realize_cgroup(Unit *u) { + Unit *slice; + assert(u); if (!UNIT_HAS_CGROUP_CONTEXT(u)) @@ -2564,8 +2568,9 @@ int unit_realize_cgroup(Unit *u) { * This call will defer work on the siblings and derealized ancestors to the next event loop * iteration and synchronously creates the parent cgroups (unit_realize_cgroup_now). */ - if (UNIT_ISSET(u->slice)) - unit_add_family_to_cgroup_realize_queue(UNIT_DEREF(u->slice)); + slice = UNIT_GET_SLICE(u); + if (slice) + unit_add_family_to_cgroup_realize_queue(slice); /* And realize this one now (and apply the values) */ return unit_realize_cgroup_now(u, manager_state(u->manager)); @@ -3782,11 +3787,9 @@ void unit_invalidate_cgroup_bpf(Unit *u) { * list of our children includes our own. */ if (u->type == UNIT_SLICE) { Unit *member; - void *v; - HASHMAP_FOREACH_KEY(v, member, u->dependencies[UNIT_BEFORE]) - if (UNIT_DEREF(member->slice) == u) - unit_invalidate_cgroup_bpf(member); + UNIT_FOREACH_DEPENDENCY(member, u, UNIT_ATOM_SLICE_OF) + unit_invalidate_cgroup_bpf(member); } } diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index a83648d968..8875ba082d 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -155,21 +155,27 @@ static int property_get_dependencies( void *userdata, sd_bus_error *error) { - Hashmap **h = userdata; - Unit *u; + Unit *u = userdata, *other; + UnitDependency d; + Hashmap *deps; void *v; int r; assert(bus); assert(reply); - assert(h); + assert(u); + + d = unit_dependency_from_string(property); + assert_se(d >= 0); + + deps = unit_get_dependencies(u, d); r = sd_bus_message_open_container(reply, 'a', "s"); if (r < 0) return r; - HASHMAP_FOREACH_KEY(v, u, *h) { - r = sd_bus_message_append(reply, "s", u->id); + HASHMAP_FOREACH_KEY(v, other, deps) { + r = sd_bus_message_append(reply, "s", other->id); if (r < 0) return r; } @@ -844,26 +850,32 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("Id", "s", NULL, offsetof(Unit, id), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Names", "as", property_get_names, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Following", "s", property_get_following, 0, 0), - SD_BUS_PROPERTY("Requires", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUIRES]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Requisite", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUISITE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Wants", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_WANTS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("BindsTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BINDS_TO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PartOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PART_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RequiredBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RequisiteOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUISITE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("WantedBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_WANTED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("BoundBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BOUND_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ConsistsOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONSISTS_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Conflicts", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONFLICTS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ConflictedBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Before", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BEFORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("After", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_AFTER]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OnFailure", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_ON_FAILURE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Triggers", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_TRIGGERS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TriggeredBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PropagatesReloadTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ReloadPropagatedFrom", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JoinsNamespaceOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_JOINS_NAMESPACE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Requires", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Requisite", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Wants", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("BindsTo", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PartOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RequiredBy", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RequisiteOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("WantedBy", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("BoundBy", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ConsistsOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Conflicts", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ConflictedBy", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Before", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("After", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OnFailure", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OnFailureOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OnSuccess", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OnSuccessOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Triggers", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TriggeredBy", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PropagatesReloadTo", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReloadPropagatedFrom", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PropagatesStopTo", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StopPropagatedFrom", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JoinsNamespaceOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SliceOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RequiresMountsFor", "as", property_get_requires_mounts_for, offsetof(Unit, requires_mounts_for), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Documentation", "as", NULL, offsetof(Unit, documentation), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST), @@ -893,6 +905,7 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("RefuseManualStop", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_stop), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("AllowIsolate", "b", bus_property_get_bool, offsetof(Unit, allow_isolate), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultDependencies", "b", bus_property_get_bool, offsetof(Unit, default_dependencies), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OnSuccesJobMode", "s", property_get_job_mode, offsetof(Unit, on_success_job_mode), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("OnFailureJobMode", "s", property_get_job_mode, offsetof(Unit, on_failure_job_mode), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("IgnoreOnIsolate", "b", bus_property_get_bool, offsetof(Unit, ignore_on_isolate), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NeedDaemonReload", "b", property_get_need_daemon_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), @@ -2116,6 +2129,9 @@ static int bus_unit_set_transient_property( if (streq(name, "DefaultDependencies")) return bus_set_transient_bool(u, name, &u->default_dependencies, message, flags, error); + if (streq(name, "OnSuccessJobMode")) + return bus_set_transient_job_mode(u, name, &u->on_success_job_mode, message, flags, error); + if (streq(name, "OnFailureJobMode")) return bus_set_transient_job_mode(u, name, &u->on_failure_job_mode, message, flags, error); @@ -2231,7 +2247,7 @@ static int bus_unit_set_transient_property( return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit name '%s' is not a slice", s); if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - r = unit_set_slice(u, slice); + r = unit_set_slice(u, slice, UNIT_DEPENDENCY_FILE); if (r < 0) return r; @@ -2288,12 +2304,16 @@ static int bus_unit_set_transient_property( UNIT_WANTS, UNIT_BINDS_TO, UNIT_PART_OF, + UNIT_UPHOLDS, UNIT_CONFLICTS, UNIT_BEFORE, UNIT_AFTER, + UNIT_ON_SUCCESS, UNIT_ON_FAILURE, UNIT_PROPAGATES_RELOAD_TO, UNIT_RELOAD_PROPAGATED_FROM, + UNIT_PROPAGATES_STOP_TO, + UNIT_STOP_PROPAGATED_FROM, UNIT_JOINS_NAMESPACE_OF)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Dependency type %s may not be created transiently.", unit_dependency_to_string(d)); diff --git a/src/core/device.c b/src/core/device.c index 356c389c55..d34d48d0ff 100644 --- a/src/core/device.c +++ b/src/core/device.c @@ -468,7 +468,7 @@ static void device_upgrade_mount_deps(Unit *u) { /* Let's upgrade Requires= to BindsTo= on us. (Used when SYSTEMD_MOUNT_DEVICE_BOUND is set) */ - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUIRED_BY]) { + HASHMAP_FOREACH_KEY(v, other, unit_get_dependencies(u, UNIT_REQUIRED_BY)) { if (other->type != UNIT_MOUNT) continue; diff --git a/src/core/job.c b/src/core/job.c index c626ddca91..a273e4220e 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -455,7 +455,6 @@ int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u) { static bool job_is_runnable(Job *j) { Unit *other; - void *v; assert(j); assert(j->installed); @@ -477,16 +476,16 @@ static bool job_is_runnable(Job *j) { if (j->type == JOB_NOP) return true; - HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER]) - if (other->job && job_compare(j, other->job, UNIT_AFTER) > 0) { + UNIT_FOREACH_DEPENDENCY(other, j->unit, UNIT_ATOM_AFTER) + if (other->job && job_compare(j, other->job, UNIT_ATOM_AFTER) > 0) { log_unit_debug(j->unit, "starting held back, waiting for: %s", other->id); return false; } - HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE]) - if (other->job && job_compare(j, other->job, UNIT_BEFORE) > 0) { + UNIT_FOREACH_DEPENDENCY(other, j->unit, UNIT_ATOM_BEFORE) + if (other->job && job_compare(j, other->job, UNIT_ATOM_BEFORE) > 0) { log_unit_debug(j->unit, "stopping held back, waiting for: %s", other->id); @@ -951,13 +950,12 @@ static void job_emit_done_status_message(Unit *u, uint32_t job_id, JobType t, Jo job_print_done_status_message(u, t, result); } -static void job_fail_dependencies(Unit *u, UnitDependency d) { +static void job_fail_dependencies(Unit *u, UnitDependencyAtom match_atom) { Unit *other; - void *v; assert(u); - HASHMAP_FOREACH_KEY(v, other, u->dependencies[d]) { + UNIT_FOREACH_DEPENDENCY(other, u, match_atom) { Job *j = other->job; if (!j) @@ -970,10 +968,8 @@ static void job_fail_dependencies(Unit *u, UnitDependency d) { } int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool already) { - Unit *u; - Unit *other; + Unit *u, *other; JobType t; - void *v; assert(j); assert(j->installed); @@ -1012,43 +1008,36 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool alr /* Fail depending jobs on failure */ if (result != JOB_DONE && recursive) { - if (IN_SET(t, JOB_START, JOB_VERIFY_ACTIVE)) { - job_fail_dependencies(u, UNIT_REQUIRED_BY); - job_fail_dependencies(u, UNIT_REQUISITE_OF); - job_fail_dependencies(u, UNIT_BOUND_BY); - } else if (t == JOB_STOP) - job_fail_dependencies(u, UNIT_CONFLICTED_BY); + if (IN_SET(t, JOB_START, JOB_VERIFY_ACTIVE)) + job_fail_dependencies(u, UNIT_ATOM_PROPAGATE_START_FAILURE); + else if (t == JOB_STOP) + job_fail_dependencies(u, UNIT_ATOM_PROPAGATE_STOP_FAILURE); } - /* A special check to make sure we take down anything RequisiteOf if we - * aren't active. This is when the verify-active job merges with a - * satisfying job type, and then loses it's invalidation effect, as the - * result there is JOB_DONE for the start job we merged into, while we - * should be failing the depending job if the said unit isn't in fact - * active. Oneshots are an example of this, where going directly from - * activating to inactive is success. + /* A special check to make sure we take down anything RequisiteOf= if we aren't active. This is when + * the verify-active job merges with a satisfying job type, and then loses it's invalidation effect, + * as the result there is JOB_DONE for the start job we merged into, while we should be failing the + * depending job if the said unit isn't in fact active. Oneshots are an example of this, where going + * directly from activating to inactive is success. * - * This happens when you use ConditionXYZ= in a unit too, since in that - * case the job completes with the JOB_DONE result, but the unit never - * really becomes active. Note that such a case still involves merging: + * This happens when you use ConditionXYZ= in a unit too, since in that case the job completes with + * the JOB_DONE result, but the unit never really becomes active. Note that such a case still + * involves merging: * - * A start job waits for something else, and a verify-active comes in - * and merges in the installed job. Then, later, when it becomes - * runnable, it finishes with JOB_DONE result as execution on conditions - * not being met is skipped, breaking our dependency semantics. + * A start job waits for something else, and a verify-active comes in and merges in the installed + * job. Then, later, when it becomes runnable, it finishes with JOB_DONE result as execution on + * conditions not being met is skipped, breaking our dependency semantics. * - * Also, depending on if start job waits or not, the merging may or may - * not happen (the verify-active job may trigger after it finishes), so - * you get undeterministic results without this check. + * Also, depending on if start job waits or not, the merging may or may not happen (the verify-active + * job may trigger after it finishes), so you get undeterministic results without this check. */ - if (result == JOB_DONE && recursive && !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) { - if (IN_SET(t, JOB_START, JOB_RELOAD)) - job_fail_dependencies(u, UNIT_REQUISITE_OF); - } - /* Trigger OnFailure dependencies that are not generated by - * the unit itself. We don't treat JOB_CANCELED as failure in - * this context. And JOB_FAILURE is already handled by the - * unit itself. */ + if (result == JOB_DONE && recursive && + IN_SET(t, JOB_START, JOB_RELOAD) && + !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) + job_fail_dependencies(u, UNIT_ATOM_PROPAGATE_INACTIVE_START_AS_FAILURE); + + /* Trigger OnFailure= dependencies that are not generated by the unit itself. We don't treat + * JOB_CANCELED as failure in this context. And JOB_FAILURE is already handled by the unit itself. */ if (IN_SET(result, JOB_TIMEOUT, JOB_DEPENDENCY)) { log_unit_struct(u, LOG_NOTICE, "JOB_TYPE=%s", job_type_to_string(t), @@ -1058,19 +1047,19 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool alr job_type_to_string(t), job_result_to_string(result))); - unit_start_on_failure(u); + unit_start_on_failure(u, "OnFailure=", UNIT_ATOM_ON_FAILURE, u->on_failure_job_mode); } unit_trigger_notify(u); finish: /* Try to start the next jobs that can be started */ - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_AFTER]) + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_AFTER) if (other->job) { job_add_to_run_queue(other->job); job_add_to_gc_queue(other->job); } - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BEFORE]) + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_BEFORE) if (other->job) { job_add_to_run_queue(other->job); job_add_to_gc_queue(other->job); @@ -1420,7 +1409,6 @@ int job_get_timeout(Job *j, usec_t *timeout) { bool job_may_gc(Job *j) { Unit *other; - void *v; assert(j); @@ -1449,12 +1437,12 @@ bool job_may_gc(Job *j) { return false; /* The logic is inverse to job_is_runnable, we cannot GC as long as we block any job. */ - HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE]) - if (other->job && job_compare(j, other->job, UNIT_BEFORE) < 0) + UNIT_FOREACH_DEPENDENCY(other, j->unit, UNIT_ATOM_BEFORE) + if (other->job && job_compare(j, other->job, UNIT_ATOM_BEFORE) < 0) return false; - HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER]) - if (other->job && job_compare(j, other->job, UNIT_AFTER) < 0) + UNIT_FOREACH_DEPENDENCY(other, j->unit, UNIT_ATOM_AFTER) + if (other->job && job_compare(j, other->job, UNIT_ATOM_AFTER) < 0) return false; return true; @@ -1500,7 +1488,6 @@ int job_get_before(Job *j, Job*** ret) { _cleanup_free_ Job** list = NULL; Unit *other = NULL; size_t n = 0; - void *v; /* Returns a list of all pending jobs that need to finish before this job may be started. */ @@ -1512,10 +1499,10 @@ int job_get_before(Job *j, Job*** ret) { return 0; } - HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER]) { + UNIT_FOREACH_DEPENDENCY(other, j->unit, UNIT_ATOM_AFTER) { if (!other->job) continue; - if (job_compare(j, other->job, UNIT_AFTER) <= 0) + if (job_compare(j, other->job, UNIT_ATOM_AFTER) <= 0) continue; if (!GREEDY_REALLOC(list, n+1)) @@ -1523,10 +1510,10 @@ int job_get_before(Job *j, Job*** ret) { list[n++] = other->job; } - HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE]) { + UNIT_FOREACH_DEPENDENCY(other, j->unit, UNIT_ATOM_BEFORE) { if (!other->job) continue; - if (job_compare(j, other->job, UNIT_BEFORE) <= 0) + if (job_compare(j, other->job, UNIT_ATOM_BEFORE) <= 0) continue; if (!GREEDY_REALLOC(list, n+1)) @@ -1545,21 +1532,20 @@ int job_get_after(Job *j, Job*** ret) { _cleanup_free_ Job** list = NULL; Unit *other = NULL; size_t n = 0; - void *v; assert(j); assert(ret); /* Returns a list of all pending jobs that are waiting for this job to finish. */ - HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE]) { + UNIT_FOREACH_DEPENDENCY(other, j->unit, UNIT_ATOM_BEFORE) { if (!other->job) continue; if (other->job->ignore_order) continue; - if (job_compare(j, other->job, UNIT_BEFORE) >= 0) + if (job_compare(j, other->job, UNIT_ATOM_BEFORE) >= 0) continue; if (!GREEDY_REALLOC(list, n+1)) @@ -1567,14 +1553,14 @@ int job_get_after(Job *j, Job*** ret) { list[n++] = other->job; } - HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER]) { + UNIT_FOREACH_DEPENDENCY(other, j->unit, UNIT_ATOM_AFTER) { if (!other->job) continue; if (other->job->ignore_order) continue; - if (job_compare(j, other->job, UNIT_AFTER) >= 0) + if (job_compare(j, other->job, UNIT_ATOM_AFTER) >= 0) continue; if (!GREEDY_REALLOC(list, n+1)) @@ -1666,13 +1652,14 @@ const char* job_type_to_access_method(JobType t) { * stop a + start b β†’ 1st step stop a, 2nd step start b * stop a + stop b β†’ 1st step stop b, 2nd step stop a * - * This has the side effect that restarts are properly - * synchronized too. + * This has the side effect that restarts are properly synchronized too. */ -int job_compare(Job *a, Job *b, UnitDependency assume_dep) { +int job_compare(Job *a, Job *b, UnitDependencyAtom assume_dep) { + assert(a); + assert(b); assert(a->type < _JOB_TYPE_MAX_IN_TRANSACTION); assert(b->type < _JOB_TYPE_MAX_IN_TRANSACTION); - assert(IN_SET(assume_dep, UNIT_AFTER, UNIT_BEFORE)); + assert(IN_SET(assume_dep, UNIT_ATOM_AFTER, UNIT_ATOM_BEFORE)); /* Trivial cases first */ if (a->type == JOB_NOP || b->type == JOB_NOP) @@ -1681,12 +1668,11 @@ int job_compare(Job *a, Job *b, UnitDependency assume_dep) { if (a->ignore_order || b->ignore_order) return 0; - if (assume_dep == UNIT_AFTER) - return -job_compare(b, a, UNIT_BEFORE); + if (assume_dep == UNIT_ATOM_AFTER) + return -job_compare(b, a, UNIT_ATOM_BEFORE); - /* Let's make it simple, JOB_STOP goes always first (in case both ua and ub stop, - * then ub's stop goes first anyway). - * JOB_RESTART is JOB_STOP in disguise (before it is patched to JOB_START). */ + /* Let's make it simple, JOB_STOP goes always first (in case both ua and ub stop, then ub's stop goes + * first anyway). JOB_RESTART is JOB_STOP in disguise (before it is patched to JOB_START). */ if (IN_SET(b->type, JOB_STOP, JOB_RESTART)) return 1; else diff --git a/src/core/job.h b/src/core/job.h index 64ea847ff9..c033c8a4fa 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -6,7 +6,9 @@ #include "sd-event.h" #include "list.h" +#include "unit-dependency-atom.h" #include "unit-name.h" +#include "unit.h" typedef struct Job Job; typedef struct JobDependency JobDependency; @@ -240,4 +242,4 @@ JobResult job_result_from_string(const char *s) _pure_; const char* job_type_to_access_method(JobType t); -int job_compare(Job *a, Job *b, UnitDependency assume_dep); +int job_compare(Job *a, Job *b, UnitDependencyAtom assume_dep); diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 98ece8054c..17f2bea5b8 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -261,14 +261,18 @@ Unit.Requisite, config_parse_unit_deps, Unit.Wants, config_parse_unit_deps, UNIT_WANTS, 0 Unit.BindsTo, config_parse_unit_deps, UNIT_BINDS_TO, 0 Unit.BindTo, config_parse_unit_deps, UNIT_BINDS_TO, 0 +Unit.Upholds, config_parse_unit_deps, UNIT_UPHOLDS, 0 Unit.Conflicts, config_parse_unit_deps, UNIT_CONFLICTS, 0 Unit.Before, config_parse_unit_deps, UNIT_BEFORE, 0 Unit.After, config_parse_unit_deps, UNIT_AFTER, 0 +Unit.OnSuccess, config_parse_unit_deps, UNIT_ON_SUCCESS, 0 Unit.OnFailure, config_parse_unit_deps, UNIT_ON_FAILURE, 0 Unit.PropagatesReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0 Unit.PropagateReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0 Unit.ReloadPropagatedFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 Unit.PropagateReloadFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 +Unit.PropagatesStopTo, config_parse_unit_deps, UNIT_PROPAGATES_STOP_TO, 0 +Unit.StopPropagatedFrom, config_parse_unit_deps, UNIT_STOP_PROPAGATED_FROM, 0 Unit.PartOf, config_parse_unit_deps, UNIT_PART_OF, 0 Unit.JoinsNamespaceOf, config_parse_unit_deps, UNIT_JOINS_NAMESPACE_OF, 0 Unit.RequiresOverridable, config_parse_obsolete_unit_deps, UNIT_REQUIRES, 0 @@ -279,6 +283,7 @@ Unit.RefuseManualStart, config_parse_bool, Unit.RefuseManualStop, config_parse_bool, 0, offsetof(Unit, refuse_manual_stop) Unit.AllowIsolate, config_parse_bool, 0, offsetof(Unit, allow_isolate) Unit.DefaultDependencies, config_parse_bool, 0, offsetof(Unit, default_dependencies) +Unit.OnSuccessJobMode, config_parse_job_mode, 0, offsetof(Unit, on_success_job_mode) Unit.OnFailureJobMode, config_parse_job_mode, 0, offsetof(Unit, on_failure_job_mode) {# The following is a legacy alias name for compatibility #} Unit.OnFailureIsolate, config_parse_job_mode_isolate, 0, offsetof(Unit, on_failure_job_mode) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index cd37314542..72378a4082 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -3574,7 +3574,7 @@ int config_parse_unit_slice( return 0; } - r = unit_set_slice(u, slice); + r = unit_set_slice(u, slice, UNIT_DEPENDENCY_FILE); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to assign slice %s to unit %s, ignoring: %m", slice->id, u->id); return 0; diff --git a/src/core/manager.c b/src/core/manager.c index 30aadb0944..419bab5b9d 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -1141,12 +1141,11 @@ enum { static void unit_gc_mark_good(Unit *u, unsigned gc_marker) { Unit *other; - void *v; u->gc_marker = gc_marker + GC_OFFSET_GOOD; /* Recursively mark referenced units as GOOD as well */ - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REFERENCES]) + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_REFERENCES) if (other->gc_marker == gc_marker + GC_OFFSET_UNSURE) unit_gc_mark_good(other, gc_marker); } @@ -1154,7 +1153,6 @@ static void unit_gc_mark_good(Unit *u, unsigned gc_marker) { static void unit_gc_sweep(Unit *u, unsigned gc_marker) { Unit *other; bool is_bad; - void *v; assert(u); @@ -1172,7 +1170,7 @@ static void unit_gc_sweep(Unit *u, unsigned gc_marker) { is_bad = true; - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REFERENCED_BY]) { + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_REFERENCED_BY) { unit_gc_sweep(other, gc_marker); if (other->gc_marker == gc_marker + GC_OFFSET_GOOD) @@ -1297,7 +1295,7 @@ static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) { /* 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_below(&u->auto_stop_ratelimit)) { + if (!ratelimit_below(&u->auto_start_stop_ratelimit)) { log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently."); continue; } @@ -1311,6 +1309,82 @@ static unsigned manager_dispatch_stop_when_unneeded_queue(Manager *m) { return n; } +static unsigned manager_dispatch_start_when_upheld_queue(Manager *m) { + unsigned n = 0; + Unit *u; + int r; + + assert(m); + + while ((u = m->start_when_upheld_queue)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Unit *culprit = NULL; + + assert(u->in_start_when_upheld_queue); + LIST_REMOVE(start_when_upheld_queue, m->start_when_upheld_queue, u); + u->in_start_when_upheld_queue = false; + + n++; + + if (!unit_is_upheld_by_active(u, &culprit)) + continue; + + log_unit_debug(u, "Unit is started because upheld by active unit %s.", culprit->id); + + /* 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_below(&u->auto_start_stop_ratelimit)) { + log_unit_warning(u, "Unit needs to be started because active unit %s upholds it, but not starting since we tried this too often recently.", culprit->id); + continue; + } + + r = manager_add_job(u->manager, JOB_START, u, JOB_FAIL, NULL, &error, NULL); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to enqueue start job, ignoring: %s", bus_error_message(&error, r)); + } + + return n; +} + +static unsigned manager_dispatch_stop_when_bound_queue(Manager *m) { + unsigned n = 0; + Unit *u; + int r; + + assert(m); + + while ((u = m->stop_when_bound_queue)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Unit *culprit = NULL; + + assert(u->in_stop_when_bound_queue); + LIST_REMOVE(stop_when_bound_queue, m->stop_when_bound_queue, u); + u->in_stop_when_bound_queue = false; + + n++; + + if (!unit_is_bound_by_inactive(u, &culprit)) + continue; + + log_unit_debug(u, "Unit is stopped because bound to inactive unit %s.", culprit->id); + + /* 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_below(&u->auto_start_stop_ratelimit)) { + log_unit_warning(u, "Unit needs to be stopped because it is bound to inactive unit %s it, but not stopping since we tried this too often recently.", culprit->id); + continue; + } + + r = manager_add_job(u->manager, JOB_STOP, u, JOB_REPLACE, NULL, &error, NULL); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); + } + + return n; +} + static void manager_clear_jobs_and_units(Manager *m) { Unit *u; @@ -1329,6 +1403,8 @@ static void manager_clear_jobs_and_units(Manager *m) { assert(!m->gc_unit_queue); assert(!m->gc_job_queue); assert(!m->stop_when_unneeded_queue); + assert(!m->start_when_upheld_queue); + assert(!m->stop_when_bound_queue); assert(hashmap_isempty(m->jobs)); assert(hashmap_isempty(m->units)); @@ -1870,30 +1946,28 @@ static int manager_dispatch_target_deps_queue(Manager *m) { Unit *u; int r = 0; - static const UnitDependency deps[] = { - UNIT_REQUIRED_BY, - UNIT_REQUISITE_OF, - UNIT_WANTED_BY, - UNIT_BOUND_BY - }; - assert(m); while ((u = m->target_deps_queue)) { + _cleanup_free_ Unit **targets = NULL; + int n_targets; + assert(u->in_target_deps_queue); LIST_REMOVE(target_deps_queue, u->manager->target_deps_queue, u); u->in_target_deps_queue = false; - for (size_t k = 0; k < ELEMENTSOF(deps); k++) { - Unit *target; - void *v; + /* Take an "atomic" snapshot of dependencies here, as the call below will likely modify the + * dependencies, and we can't have it that hash tables we iterate through are modified while + * we are iterating through them. */ + n_targets = unit_get_dependency_array(u, UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES, &targets); + if (n_targets < 0) + return n_targets; - HASHMAP_FOREACH_KEY(v, target, u->dependencies[deps[k]]) { - r = unit_add_default_target_dependency(u, target); - if (r < 0) - return r; - } + for (int i = 0; i < n_targets; i++) { + r = unit_add_default_target_dependency(u, targets[i]); + if (r < 0) + return r; } } @@ -2958,6 +3032,12 @@ int manager_loop(Manager *m) { if (manager_dispatch_cgroup_realize_queue(m) > 0) continue; + if (manager_dispatch_start_when_upheld_queue(m) > 0) + continue; + + if (manager_dispatch_stop_when_bound_queue(m) > 0) + continue; + if (manager_dispatch_stop_when_unneeded_queue(m) > 0) continue; diff --git a/src/core/manager.h b/src/core/manager.h index f58982a364..c4d4b2c375 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -187,6 +187,12 @@ struct Manager { /* Units that might be subject to StopWhenUnneeded= clean-up */ LIST_HEAD(Unit, stop_when_unneeded_queue); + /* Units which are upheld by another other which we might need to act on */ + LIST_HEAD(Unit, start_when_upheld_queue); + + /* Units that have BindsTo= another unit, and might need to be shutdown because the bound unit is not active. */ + LIST_HEAD(Unit, stop_when_bound_queue); + sd_event *event; /* This maps PIDs we care about to units that are interested in. We allow multiple units to he interested in diff --git a/src/core/meson.build b/src/core/meson.build index 69013311ea..06f0e8b5db 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -117,6 +117,8 @@ libcore_sources = ''' timer.h transaction.c transaction.h + unit-dependency-atom.c + unit-dependency-atom.h unit-printf.c unit-printf.h unit-serialize.c diff --git a/src/core/mount.c b/src/core/mount.c index ca5d0939a1..b496268164 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -66,6 +66,24 @@ static bool MOUNT_STATE_WITH_PROCESS(MountState state) { MOUNT_CLEANING); } +static MountParameters* get_mount_parameters_fragment(Mount *m) { + assert(m); + + if (m->from_fragment) + return &m->parameters_fragment; + + return NULL; +} + +static MountParameters* get_mount_parameters(Mount *m) { + assert(m); + + if (m->from_proc_self_mountinfo) + return &m->parameters_proc_self_mountinfo; + + return get_mount_parameters_fragment(m); +} + static bool mount_is_automount(const MountParameters *p) { assert(p); @@ -116,16 +134,33 @@ static bool mount_is_bind(const MountParameters *p) { return false; } -static bool mount_is_bound_to_device(const Mount *m) { +static bool mount_is_bound_to_device(Mount *m) { const MountParameters *p; - if (m->from_fragment) - return true; + assert(m); + + /* Determines whether to place a Requires= or BindsTo= dependency on the backing device unit. We do + * this by checking for the x-systemd.device-bound mount option. Iff it is set we use BindsTo=, + * otherwise Requires=. But note that we might combine the latter with StopPropagatedFrom=, see + * below. */ + + p = get_mount_parameters(m); + if (!p) + return false; - p = &m->parameters_proc_self_mountinfo; return fstab_test_option(p->options, "x-systemd.device-bound\0"); } +static bool mount_propagate_stop(Mount *m) { + assert(m); + + if (mount_is_bound_to_device(m)) /* If we are using BindsTo= the stop propagation is implicit, no need to bother */ + return false; + + return m->from_fragment; /* let's propagate stop whenever this is an explicitly configured unit, + * otherwise let's not bother. */ +} + static bool mount_needs_quota(const MountParameters *p) { assert(p); @@ -234,24 +269,6 @@ static void mount_done(Unit *u) { m->timer_event_source = sd_event_source_unref(m->timer_event_source); } -static MountParameters* get_mount_parameters_fragment(Mount *m) { - assert(m); - - if (m->from_fragment) - return &m->parameters_fragment; - - return NULL; -} - -static MountParameters* get_mount_parameters(Mount *m) { - assert(m); - - if (m->from_proc_self_mountinfo) - return &m->parameters_proc_self_mountinfo; - - return get_mount_parameters_fragment(m); -} - static int update_parameters_proc_self_mountinfo( Mount *m, const char *what, @@ -367,8 +384,9 @@ static int mount_add_device_dependencies(Mount *m) { return 0; /* Mount units from /proc/self/mountinfo are not bound to devices by default since they're subject to - * races when devices are unplugged. But the user can still force this dep with an appropriate option - * (or udev property) so the mount units are automatically stopped when the device disappears + * races when mounts are established by other tools with different backing devices than what we + * maintain. The user can still force this to be a BindsTo= dependency with an appropriate option (or + * udev property) so the mount units are automatically stopped when the device disappears * suddenly. */ dep = mount_is_bound_to_device(m) ? UNIT_BINDS_TO : UNIT_REQUIRES; @@ -378,6 +396,11 @@ static int mount_add_device_dependencies(Mount *m) { r = unit_add_node_dependency(UNIT(m), p->what, dep, mask); if (r < 0) return r; + if (mount_propagate_stop(m)) { + r = unit_add_node_dependency(UNIT(m), p->what, UNIT_STOP_PROPAGATED_FROM, mask); + if (r < 0) + return r; + } return unit_add_blockdev_dependency(UNIT(m), p->what, mask); } diff --git a/src/core/service.c b/src/core/service.c index ab3a476bcd..baad85e8b6 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -1245,12 +1245,11 @@ static int service_collect_fds( rn_socket_fds = 1; } else { - void *v; Unit *u; /* Pass all our configured sockets for singleton services */ - HASHMAP_FOREACH_KEY(v, u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY]) { + UNIT_FOREACH_DEPENDENCY(u, UNIT(s), UNIT_ATOM_TRIGGERED_BY) { _cleanup_free_ int *cfds = NULL; Socket *sock; int cn_fds; diff --git a/src/core/slice.c b/src/core/slice.c index 94eb56e1ca..595f704a17 100644 --- a/src/core/slice.c +++ b/src/core/slice.c @@ -47,25 +47,20 @@ static void slice_set_state(Slice *t, SliceState state) { } static int slice_add_parent_slice(Slice *s) { - Unit *u = UNIT(s), *parent; + Unit *u = UNIT(s); _cleanup_free_ char *a = NULL; int r; assert(s); - if (UNIT_ISSET(u->slice)) + if (UNIT_GET_SLICE(u)) return 0; r = slice_build_parent_slice(u->id, &a); if (r <= 0) /* 0 means root slice */ return r; - r = manager_load_unit(u->manager, a, NULL, NULL, &parent); - if (r < 0) - return r; - - unit_ref_set(&u->slice, u, parent); - return 0; + return unit_add_dependency_by_name(u, UNIT_IN_SLICE, a, true, UNIT_DEPENDENCY_IMPLICIT); } static int slice_add_default_dependencies(Slice *s) { @@ -101,7 +96,7 @@ static int slice_verify(Slice *s) { if (r < 0) return log_unit_error_errno(UNIT(s), r, "Failed to determine parent slice: %m"); - if (parent ? !unit_has_name(UNIT_DEREF(UNIT(s)->slice), parent) : UNIT_ISSET(UNIT(s)->slice)) + if (parent ? !unit_has_name(UNIT_GET_SLICE(UNIT(s)), parent) : !!UNIT_GET_SLICE(UNIT(s))) return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Located outside of parent slice. Refusing."); return 0; @@ -346,15 +341,11 @@ static void slice_enumerate_perpetual(Manager *m) { static bool slice_freezer_action_supported_by_children(Unit *s) { Unit *member; - void *v; + int r; assert(s); - HASHMAP_FOREACH_KEY(v, member, s->dependencies[UNIT_BEFORE]) { - int r; - - if (UNIT_DEREF(member->slice) != s) - continue; + UNIT_FOREACH_DEPENDENCY(member, s, UNIT_ATOM_SLICE_OF) { if (member->type == UNIT_SLICE) { r = slice_freezer_action_supported_by_children(member); @@ -371,7 +362,6 @@ static bool slice_freezer_action_supported_by_children(Unit *s) { static int slice_freezer_action(Unit *s, FreezerAction action) { Unit *member; - void *v; int r; assert(s); @@ -382,15 +372,11 @@ static int slice_freezer_action(Unit *s, FreezerAction action) { return 0; } - HASHMAP_FOREACH_KEY(v, member, s->dependencies[UNIT_BEFORE]) { - if (UNIT_DEREF(member->slice) != s) - continue; - + UNIT_FOREACH_DEPENDENCY(member, s, UNIT_ATOM_SLICE_OF) { if (action == FREEZER_FREEZE) r = UNIT_VTABLE(member)->freeze(member); else r = UNIT_VTABLE(member)->thaw(member); - if (r < 0) return r; } diff --git a/src/core/socket.c b/src/core/socket.c index 016986401b..8be01aa8e1 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -2340,11 +2340,9 @@ static void socket_enter_running(Socket *s, int cfd_in) { if (cfd < 0) { bool pending = false; Unit *other; - void *v; - /* If there's already a start pending don't bother to - * do anything */ - HASHMAP_FOREACH_KEY(v, other, UNIT(s)->dependencies[UNIT_TRIGGERS]) + /* If there's already a start pending don't bother to do anything */ + UNIT_FOREACH_DEPENDENCY(other, UNIT(s), UNIT_ATOM_TRIGGERS) if (unit_active_or_pending(other)) { pending = true; break; diff --git a/src/core/target.c b/src/core/target.c index 5755f26615..99a9374e1f 100644 --- a/src/core/target.c +++ b/src/core/target.c @@ -35,34 +35,31 @@ static void target_set_state(Target *t, TargetState state) { } static int target_add_default_dependencies(Target *t) { - - static const UnitDependency deps[] = { - UNIT_REQUIRES, - UNIT_REQUISITE, - UNIT_WANTS, - UNIT_BINDS_TO, - UNIT_PART_OF - }; - - int r; + _cleanup_free_ Unit **others = NULL; + int r, n_others; assert(t); if (!UNIT(t)->default_dependencies) return 0; - /* Imply ordering for requirement dependencies on target units. Note that when the user created a contradicting - * ordering manually we won't add anything in here to make sure we don't create a loop. */ + /* Imply ordering for requirement dependencies on target units. Note that when the user created a + * contradicting ordering manually we won't add anything in here to make sure we don't create a + * loop. + * + * Note that quite likely iterating through these dependencies will add new dependencies, which + * conflicts with the hashmap-based iteration logic. Hence, instead of iterating through the + * dependencies and acting on them as we go, first take an "atomic snapshot" of sorts and iterate + * through that. */ - for (size_t k = 0; k < ELEMENTSOF(deps); k++) { - Unit *other; - void *v; + n_others = unit_get_dependency_array(UNIT(t), UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE, &others); + if (n_others < 0) + return n_others; - HASHMAP_FOREACH_KEY(v, other, UNIT(t)->dependencies[deps[k]]) { - r = unit_add_default_target_dependency(other, UNIT(t)); - if (r < 0) - return r; - } + for (int i = 0; i < n_others; i++) { + r = unit_add_default_target_dependency(others[i], UNIT(t)); + if (r < 0) + return r; } if (unit_has_name(UNIT(t), SPECIAL_SHUTDOWN_TARGET)) diff --git a/src/core/transaction.c b/src/core/transaction.c index 4a55c075b4..51232e4ab1 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -347,21 +347,20 @@ static char* merge_unit_ids(const char* unit_log_field, char **pairs) { } static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsigned generation, sd_bus_error *e) { - Unit *u; - void *v; - int r; - static const UnitDependency directions[] = { - UNIT_BEFORE, - UNIT_AFTER, + + static const UnitDependencyAtom directions[] = { + UNIT_ATOM_BEFORE, + UNIT_ATOM_AFTER, }; - size_t d; + + int r; assert(tr); assert(j); assert(!j->transaction_prev); - /* Does a recursive sweep through the ordering graph, looking - * for a cycle. If we find a cycle we try to break it. */ + /* Does a recursive sweep through the ordering graph, looking for a cycle. If we find a cycle we try + * to break it. */ /* Have we seen this before? */ if (j->generation == generation) { @@ -369,18 +368,14 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi _cleanup_free_ char **array = NULL, *unit_ids = NULL; char **unit_id, **job_type; - /* If the marker is NULL we have been here already and - * decided the job was loop-free from here. Hence - * shortcut things and return right-away. */ + /* If the marker is NULL we have been here already and decided the job was loop-free from + * here. Hence shortcut things and return right-away. */ if (!j->marker) return 0; - /* So, the marker is not NULL and we already have been here. We have - * a cycle. Let's try to break it. We go backwards in our path and - * try to find a suitable job to remove. We use the marker to find - * our way back, since smart how we are we stored our way back in - * there. */ - + /* So, the marker is not NULL and we already have been here. We have a cycle. Let's try to + * break it. We go backwards in our path and try to find a suitable job to remove. We use the + * marker to find our way back, since smart how we are we stored our way back in there. */ for (k = from; k; k = ((k->generation == generation && k->marker != k) ? k->marker : NULL)) { /* For logging below */ @@ -449,16 +444,17 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi * the graph over 'before' edges in the actual job execution order. We traverse over both unit * ordering dependencies and we test with job_compare() whether it is the 'before' edge in the job * execution ordering. */ - for (d = 0; d < ELEMENTSOF(directions); d++) { - HASHMAP_FOREACH_KEY(v, u, j->unit->dependencies[directions[d]]) { + for (size_t d = 0; d < ELEMENTSOF(directions); d++) { + Unit *u; + + UNIT_FOREACH_DEPENDENCY(u, j->unit, directions[d]) { Job *o; /* Is there a job for this unit? */ o = hashmap_get(tr->jobs, u); if (!o) { - /* Ok, there is no job for this in the - * transaction, but maybe there is already one - * running? */ + /* Ok, there is no job for this in the transaction, but maybe there is + * already one running? */ o = u->job; if (!o) continue; @@ -879,13 +875,12 @@ static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependen void transaction_add_propagate_reload_jobs(Transaction *tr, Unit *unit, Job *by, bool ignore_order, sd_bus_error *e) { JobType nt; Unit *dep; - void *v; int r; assert(tr); assert(unit); - HASHMAP_FOREACH_KEY(v, dep, unit->dependencies[UNIT_PROPAGATES_RELOAD_TO]) { + UNIT_FOREACH_DEPENDENCY(dep, unit, UNIT_ATOM_PROPAGATES_RELOAD_TO) { nt = job_type_collapse(JOB_TRY_RELOAD, dep); if (nt == JOB_NOP) continue; @@ -914,7 +909,6 @@ int transaction_add_job_and_dependencies( bool is_new; Unit *dep; Job *ret; - void *v; int r; assert(tr); @@ -1006,7 +1000,7 @@ int transaction_add_job_and_dependencies( /* Finally, recursively add in all dependencies. */ if (IN_SET(type, JOB_START, JOB_RESTART)) { - HASHMAP_FOREACH_KEY(v, dep, ret->unit->dependencies[UNIT_REQUIRES]) { + UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START) { r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e); if (r < 0) { if (r != -EBADR) /* job type not applicable */ @@ -1016,17 +1010,7 @@ int transaction_add_job_and_dependencies( } } - HASHMAP_FOREACH_KEY(v, dep, ret->unit->dependencies[UNIT_BINDS_TO]) { - r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - - HASHMAP_FOREACH_KEY(v, dep, ret->unit->dependencies[UNIT_WANTS]) { + UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START_IGNORED) { r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, ignore_order, e); if (r < 0) { /* unit masked, job type not applicable and unit not found are not considered as errors. */ @@ -1038,7 +1022,7 @@ int transaction_add_job_and_dependencies( } } - HASHMAP_FOREACH_KEY(v, dep, ret->unit->dependencies[UNIT_REQUISITE]) { + UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_VERIFY) { r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, false, false, ignore_order, e); if (r < 0) { if (r != -EBADR) /* job type not applicable */ @@ -1048,7 +1032,7 @@ int transaction_add_job_and_dependencies( } } - HASHMAP_FOREACH_KEY(v, dep, ret->unit->dependencies[UNIT_CONFLICTS]) { + UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_STOP) { r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, true, false, ignore_order, e); if (r < 0) { if (r != -EBADR) /* job type not applicable */ @@ -1058,7 +1042,7 @@ int transaction_add_job_and_dependencies( } } - HASHMAP_FOREACH_KEY(v, dep, ret->unit->dependencies[UNIT_CONFLICTED_BY]) { + UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_STOP_IGNORED) { r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, false, false, ignore_order, e); if (r < 0) { log_unit_warning(dep, @@ -1067,41 +1051,37 @@ int transaction_add_job_and_dependencies( sd_bus_error_free(e); } } - } if (IN_SET(type, JOB_STOP, JOB_RESTART)) { - static const UnitDependency propagate_deps[] = { - UNIT_REQUIRED_BY, - UNIT_REQUISITE_OF, - UNIT_BOUND_BY, - UNIT_CONSISTS_OF, - }; - + UnitDependencyAtom atom; JobType ptype; - unsigned j; - /* We propagate STOP as STOP, but RESTART only - * as TRY_RESTART, in order not to start + /* We propagate STOP as STOP, but RESTART only as TRY_RESTART, in order not to start * dependencies that are not around. */ - ptype = type == JOB_RESTART ? JOB_TRY_RESTART : type; + if (type == JOB_RESTART) { + atom = UNIT_ATOM_PROPAGATE_RESTART; + ptype = JOB_TRY_RESTART; + } else { + ptype = JOB_STOP; + atom = UNIT_ATOM_PROPAGATE_STOP; + } - for (j = 0; j < ELEMENTSOF(propagate_deps); j++) - HASHMAP_FOREACH_KEY(v, dep, ret->unit->dependencies[propagate_deps[j]]) { - JobType nt; + UNIT_FOREACH_DEPENDENCY(dep, ret->unit, atom) { + JobType nt; - nt = job_type_collapse(ptype, dep); - if (nt == JOB_NOP) - continue; + nt = job_type_collapse(ptype, dep); + if (nt == JOB_NOP) + continue; - r = transaction_add_job_and_dependencies(tr, nt, dep, ret, true, false, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; + r = transaction_add_job_and_dependencies(tr, nt, dep, ret, true, false, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; - sd_bus_error_free(e); - } + sd_bus_error_free(e); } + } } if (type == JOB_RELOAD) @@ -1150,14 +1130,14 @@ int transaction_add_isolate_jobs(Transaction *tr, Manager *m) { } int transaction_add_triggering_jobs(Transaction *tr, Unit *u) { - void *v; Unit *trigger; int r; assert(tr); assert(u); - HASHMAP_FOREACH_KEY(v, trigger, u->dependencies[UNIT_TRIGGERED_BY]) { + UNIT_FOREACH_DEPENDENCY(trigger, u, UNIT_ATOM_TRIGGERED_BY) { + /* No need to stop inactive jobs */ if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(trigger)) && !trigger->job) continue; diff --git a/src/core/unit-dependency-atom.c b/src/core/unit-dependency-atom.c new file mode 100644 index 0000000000..761835828a --- /dev/null +++ b/src/core/unit-dependency-atom.c @@ -0,0 +1,240 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "unit-dependency-atom.h" + +static const UnitDependencyAtom atom_map[_UNIT_DEPENDENCY_MAX] = { + /* A table that maps high-level dependency types to low-level dependency "atoms". The latter actually + * describe specific facets of dependency behaviour. The former combine them into one user-facing + * concept. Atoms are a bit mask, though a bunch of dependency types have only a single bit set. + * + * Typically when the user configures a dependency they go via dependency type, but when we act on + * them we go by atom. + * + * NB: when you add a new dependency type here, make sure to also add one to the (best-effort) + * reverse table in unit_dependency_from_unique_atom() further down. */ + + [UNIT_REQUIRES] = UNIT_ATOM_PULL_IN_START | + UNIT_ATOM_RETROACTIVE_START_REPLACE | + UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE | + UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE, + + [UNIT_REQUISITE] = UNIT_ATOM_PULL_IN_VERIFY | + UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE | + UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE, + + [UNIT_WANTS] = UNIT_ATOM_PULL_IN_START_IGNORED | + UNIT_ATOM_RETROACTIVE_START_FAIL | + UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE | + UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE, + + [UNIT_BINDS_TO] = UNIT_ATOM_PULL_IN_START | + UNIT_ATOM_RETROACTIVE_START_REPLACE | + UNIT_ATOM_CANNOT_BE_ACTIVE_WITHOUT | + UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE | + UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE, + + [UNIT_PART_OF] = UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE, + + [UNIT_UPHOLDS] = UNIT_ATOM_PULL_IN_START_IGNORED | + UNIT_ATOM_RETROACTIVE_START_REPLACE | + UNIT_ATOM_ADD_START_WHEN_UPHELD_QUEUE | + UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE | + UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE, + + [UNIT_REQUIRED_BY] = UNIT_ATOM_PROPAGATE_STOP | + UNIT_ATOM_PROPAGATE_RESTART | + UNIT_ATOM_PROPAGATE_START_FAILURE | + UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED | + UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES, + + [UNIT_REQUISITE_OF] = UNIT_ATOM_PROPAGATE_STOP | + UNIT_ATOM_PROPAGATE_RESTART | + UNIT_ATOM_PROPAGATE_START_FAILURE | + UNIT_ATOM_PROPAGATE_INACTIVE_START_AS_FAILURE | + UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED | + UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES, + + [UNIT_WANTED_BY] = UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES | + UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED, + + [UNIT_BOUND_BY] = UNIT_ATOM_RETROACTIVE_STOP_ON_STOP | + UNIT_ATOM_PROPAGATE_STOP | + UNIT_ATOM_PROPAGATE_RESTART | + UNIT_ATOM_PROPAGATE_START_FAILURE | + UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED | + UNIT_ATOM_ADD_CANNOT_BE_ACTIVE_WITHOUT_QUEUE | + UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES, + + [UNIT_UPHELD_BY] = UNIT_ATOM_START_STEADILY | + UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES | + UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED, + + [UNIT_CONSISTS_OF] = UNIT_ATOM_PROPAGATE_STOP | + UNIT_ATOM_PROPAGATE_RESTART, + + [UNIT_CONFLICTS] = UNIT_ATOM_PULL_IN_STOP | + UNIT_ATOM_RETROACTIVE_STOP_ON_START, + + [UNIT_CONFLICTED_BY] = UNIT_ATOM_PULL_IN_STOP_IGNORED | + UNIT_ATOM_RETROACTIVE_STOP_ON_START | + UNIT_ATOM_PROPAGATE_STOP_FAILURE, + + [UNIT_PROPAGATES_STOP_TO] = UNIT_ATOM_RETROACTIVE_STOP_ON_STOP | + UNIT_ATOM_PROPAGATE_STOP, + + /* These are simple dependency types: they consist of a single atom only */ + [UNIT_BEFORE] = UNIT_ATOM_BEFORE, + [UNIT_AFTER] = UNIT_ATOM_AFTER, + [UNIT_ON_SUCCESS] = UNIT_ATOM_ON_SUCCESS, + [UNIT_ON_FAILURE] = UNIT_ATOM_ON_FAILURE, + [UNIT_TRIGGERS] = UNIT_ATOM_TRIGGERS, + [UNIT_TRIGGERED_BY] = UNIT_ATOM_TRIGGERED_BY, + [UNIT_PROPAGATES_RELOAD_TO] = UNIT_ATOM_PROPAGATES_RELOAD_TO, + [UNIT_JOINS_NAMESPACE_OF] = UNIT_ATOM_JOINS_NAMESPACE_OF, + [UNIT_REFERENCES] = UNIT_ATOM_REFERENCES, + [UNIT_REFERENCED_BY] = UNIT_ATOM_REFERENCED_BY, + [UNIT_IN_SLICE] = UNIT_ATOM_IN_SLICE, + [UNIT_SLICE_OF] = UNIT_ATOM_SLICE_OF, + + /* These are dependency types without effect on our state engine. We maintain them only to make + * things discoverable/debuggable as they are the inverse dependencies to some of the above. As they + * have no effect of their own, they all map to no atoms at all, i.e. the value 0. */ + [UNIT_RELOAD_PROPAGATED_FROM] = 0, + [UNIT_ON_SUCCESS_OF] = 0, + [UNIT_ON_FAILURE_OF] = 0, + [UNIT_STOP_PROPAGATED_FROM] = 0, +}; + +UnitDependencyAtom unit_dependency_to_atom(UnitDependency d) { + if (d < 0) + return _UNIT_DEPENDENCY_ATOM_INVALID; + + assert(d < _UNIT_DEPENDENCY_MAX); + + return atom_map[d]; +} + +UnitDependency unit_dependency_from_unique_atom(UnitDependencyAtom atom) { + + /* This is a "best-effort" function that maps the specified 'atom' mask to a dependency type that is + * is equal to or has a superset of bits set if that's uniquely possible. The idea is that this + * function is used when iterating through deps that have a specific atom: if there's exactly one + * dependency type of the specific atom we don't need iterate through all deps a unit has, but can + * pinpoint things directly. + * + * This function will return _UNIT_DEPENDENCY_INVALID in case the specified value is not known or not + * uniquely defined, i.e. there are multiple dependencies with the atom or the combination set. */ + + switch ((int64_t) atom) { + + /* Note that we can't list UNIT_REQUIRES here since it's a true subset of UNIT_BINDS_TO, and + * hence its atom bits not uniquely mappable. */ + + case UNIT_ATOM_PULL_IN_VERIFY | + UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE | + UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE: + case UNIT_ATOM_PULL_IN_VERIFY: /* a single dep type uses this atom */ + return UNIT_REQUISITE; + + case UNIT_ATOM_PULL_IN_START_IGNORED | + UNIT_ATOM_RETROACTIVE_START_FAIL | + UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE | + UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE: + case UNIT_ATOM_RETROACTIVE_START_FAIL: + return UNIT_WANTS; + + case UNIT_ATOM_PULL_IN_START | + UNIT_ATOM_RETROACTIVE_START_REPLACE | + UNIT_ATOM_CANNOT_BE_ACTIVE_WITHOUT | + UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE | + UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE: + case UNIT_ATOM_CANNOT_BE_ACTIVE_WITHOUT: + return UNIT_BINDS_TO; + + case UNIT_ATOM_PULL_IN_START_IGNORED | + UNIT_ATOM_RETROACTIVE_START_REPLACE | + UNIT_ATOM_ADD_START_WHEN_UPHELD_QUEUE | + UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE | + UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE: + case UNIT_ATOM_ADD_START_WHEN_UPHELD_QUEUE: + return UNIT_UPHOLDS; + + case UNIT_ATOM_PROPAGATE_STOP | + UNIT_ATOM_PROPAGATE_RESTART | + UNIT_ATOM_PROPAGATE_START_FAILURE | + UNIT_ATOM_PROPAGATE_INACTIVE_START_AS_FAILURE | + UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED | + UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES: + case UNIT_ATOM_PROPAGATE_INACTIVE_START_AS_FAILURE: + return UNIT_REQUISITE_OF; + + case UNIT_ATOM_RETROACTIVE_STOP_ON_STOP | + UNIT_ATOM_PROPAGATE_STOP | + UNIT_ATOM_PROPAGATE_RESTART | + UNIT_ATOM_PROPAGATE_START_FAILURE | + UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED | + UNIT_ATOM_ADD_CANNOT_BE_ACTIVE_WITHOUT_QUEUE | + UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES: + case UNIT_ATOM_ADD_CANNOT_BE_ACTIVE_WITHOUT_QUEUE: + return UNIT_BOUND_BY; + + case UNIT_ATOM_START_STEADILY | + UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES | + UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED: + case UNIT_ATOM_START_STEADILY: + return UNIT_UPHELD_BY; + + case UNIT_ATOM_PULL_IN_STOP | + UNIT_ATOM_RETROACTIVE_STOP_ON_START: + case UNIT_ATOM_PULL_IN_STOP: + return UNIT_CONFLICTS; + + case UNIT_ATOM_PULL_IN_STOP_IGNORED | + UNIT_ATOM_RETROACTIVE_STOP_ON_START | + UNIT_ATOM_PROPAGATE_STOP_FAILURE: + case UNIT_ATOM_PULL_IN_STOP_IGNORED: + case UNIT_ATOM_PROPAGATE_STOP_FAILURE: + return UNIT_CONFLICTED_BY; + + /* And now, the simple ones */ + + case UNIT_ATOM_BEFORE: + return UNIT_BEFORE; + + case UNIT_ATOM_AFTER: + return UNIT_AFTER; + + case UNIT_ATOM_ON_SUCCESS: + return UNIT_ON_SUCCESS; + + case UNIT_ATOM_ON_FAILURE: + return UNIT_ON_FAILURE; + + case UNIT_ATOM_TRIGGERS: + return UNIT_TRIGGERS; + + case UNIT_ATOM_TRIGGERED_BY: + return UNIT_TRIGGERED_BY; + + case UNIT_ATOM_PROPAGATES_RELOAD_TO: + return UNIT_PROPAGATES_RELOAD_TO; + + case UNIT_ATOM_JOINS_NAMESPACE_OF: + return UNIT_JOINS_NAMESPACE_OF; + + case UNIT_ATOM_REFERENCES: + return UNIT_REFERENCES; + + case UNIT_ATOM_REFERENCED_BY: + return UNIT_REFERENCED_BY; + + case UNIT_ATOM_IN_SLICE: + return UNIT_IN_SLICE; + + case UNIT_ATOM_SLICE_OF: + return UNIT_SLICE_OF; + + default: + return _UNIT_DEPENDENCY_INVALID; + } +} diff --git a/src/core/unit-dependency-atom.h b/src/core/unit-dependency-atom.h new file mode 100644 index 0000000000..c5f8f5d400 --- /dev/null +++ b/src/core/unit-dependency-atom.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "unit-def.h" + +/* Flags that identify the various "atomic" behaviours a specific dependency type implies. Each dependency is + * a combination of one or more of these flags that define what they actually entail. */ +typedef enum UnitDependencyAtom { + + /* This unit pulls in the other unit as JOB_START job into the transaction, and if that doesn't work + * the transaction fails. */ + UNIT_ATOM_PULL_IN_START = UINT64_C(1) << 0, + /* Similar, but if it doesn't work, ignore. */ + UNIT_ATOM_PULL_IN_START_IGNORED = UINT64_C(1) << 1, + /* Pull in a JOB_VERIFY job into the transaction, i.e. pull in JOB_VERIFY rather than + * JOB_START. i.e. check the unit is started but don't pull it in. */ + UNIT_ATOM_PULL_IN_VERIFY = UINT64_C(1) << 2, + + /* Pull in a JOB_STOP job for the other job into transactions, and fail if that doesn't work. */ + UNIT_ATOM_PULL_IN_STOP = UINT64_C(1) << 3, + /* Same, but don't fail, ignore it. */ + UNIT_ATOM_PULL_IN_STOP_IGNORED = UINT64_C(1) << 4, + + /* If our enters inactive state, add the other unit to the StopWhenUneeded= queue */ + UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE = UINT64_C(1) << 5, + /* Pin the other unit i.e. ensure StopWhenUneeded= won't trigger for the other unit as long as we are + * not in inactive state */ + UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED = UINT64_C(1) << 6, + + /* Stop our unit if the other unit happens to inactive */ + UNIT_ATOM_CANNOT_BE_ACTIVE_WITHOUT = UINT64_C(1) << 7, + /* If our unit enters inactive state, add the other unit to the BoundBy= queue */ + UNIT_ATOM_ADD_CANNOT_BE_ACTIVE_WITHOUT_QUEUE = UINT64_C(1) << 8, + + /* Start this unit whenever we find it inactive and the other unit active */ + UNIT_ATOM_START_STEADILY = UINT64_C(1) << 9, + /* Whenever our unit becomes active, add other unit to start_when_upheld_queue */ + UNIT_ATOM_ADD_START_WHEN_UPHELD_QUEUE = UINT64_C(1) << 10, + + /* If our unit unexpectedly becomes active, retroactively start the other unit too, in "replace" job + * mode */ + UNIT_ATOM_RETROACTIVE_START_REPLACE = UINT64_C(1) << 11, + /* Similar, but in "fail" job mode */ + UNIT_ATOM_RETROACTIVE_START_FAIL = UINT64_C(1) << 12, + /* If our unit unexpectedly becomes active, retroactively stop the other unit too */ + UNIT_ATOM_RETROACTIVE_STOP_ON_START = UINT64_C(1) << 13, + /* If our unit unexpectedly becomes inactive, retroactively stop the other unit too */ + UNIT_ATOM_RETROACTIVE_STOP_ON_STOP = UINT64_C(1) << 14, + + /* If a start job for this unit fails, propagate the failure to start job of other unit too */ + UNIT_ATOM_PROPAGATE_START_FAILURE = UINT64_C(1) << 15, + /* If a stop job for this unit fails, propagate the failure to any stop job of the other unit too */ + UNIT_ATOM_PROPAGATE_STOP_FAILURE = UINT64_C(1) << 16, + /* If our start job succeeded but the unit is inactive then (think: oneshot units), propagate this as + * failure to the other unit. */ + UNIT_ATOM_PROPAGATE_INACTIVE_START_AS_FAILURE = UINT64_C(1) << 17, + /* When putting together a transaction, propagate JOB_STOP from our unit to the other. */ + UNIT_ATOM_PROPAGATE_STOP = UINT64_C(1) << 18, + /* When putting together a transaction, propagate JOB_RESTART from our unit to the other. */ + UNIT_ATOM_PROPAGATE_RESTART = UINT64_C(1) << 19, + + /* Add the other unit to the default target dependency queue */ + UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE = UINT64_C(1) << 20, + /* Recheck default target deps on other units (which are target units) */ + UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES = UINT64_C(1) << 21, + + /* The remaining atoms map 1:1 to the equally named high-level deps */ + UNIT_ATOM_BEFORE = UINT64_C(1) << 22, + UNIT_ATOM_AFTER = UINT64_C(1) << 23, + UNIT_ATOM_ON_SUCCESS = UINT64_C(1) << 24, + UNIT_ATOM_ON_FAILURE = UINT64_C(1) << 25, + UNIT_ATOM_TRIGGERS = UINT64_C(1) << 26, + UNIT_ATOM_TRIGGERED_BY = UINT64_C(1) << 27, + UNIT_ATOM_PROPAGATES_RELOAD_TO = UINT64_C(1) << 28, + UNIT_ATOM_JOINS_NAMESPACE_OF = UINT64_C(1) << 29, + UNIT_ATOM_REFERENCES = UINT64_C(1) << 30, + UNIT_ATOM_REFERENCED_BY = UINT64_C(1) << 31, + UNIT_ATOM_IN_SLICE = UINT64_C(1) << 32, + UNIT_ATOM_SLICE_OF = UINT64_C(1) << 33, + _UNIT_DEPENDENCY_ATOM_MAX = (UINT64_C(1) << 34) - 1, + _UNIT_DEPENDENCY_ATOM_INVALID = -EINVAL, +} UnitDependencyAtom; + +UnitDependencyAtom unit_dependency_to_atom(UnitDependency d); +UnitDependency unit_dependency_from_unique_atom(UnitDependencyAtom atom); diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c index ee8b0b3de8..113dd1cc8a 100644 --- a/src/core/unit-printf.c +++ b/src/core/unit-printf.c @@ -132,18 +132,15 @@ static int specifier_cgroup_root(char specifier, const void *data, const void *u } static int specifier_cgroup_slice(char specifier, const void *data, const void *userdata, char **ret) { - const Unit *u = userdata; + const Unit *u = userdata, *slice; char *n; assert(u); bad_specifier(u, specifier); - if (UNIT_ISSET(u->slice)) { - const Unit *slice; - - slice = UNIT_DEREF(u->slice); - + slice = UNIT_GET_SLICE(u); + if (slice) { if (slice->cgroup_path) n = strdup(slice->cgroup_path); else diff --git a/src/core/unit-serialize.c b/src/core/unit-serialize.c index 509210ad5e..7da0d8a3db 100644 --- a/src/core/unit-serialize.c +++ b/src/core/unit-serialize.c @@ -604,10 +604,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tNeed Daemon Reload: %s\n" "%s\tTransient: %s\n" "%s\tPerpetual: %s\n" - "%s\tGarbage Collection Mode: %s\n" - "%s\tSlice: %s\n" - "%s\tCGroup: %s\n" - "%s\tCGroup realized: %s\n", + "%s\tGarbage Collection Mode: %s\n", prefix, unit_description(u), prefix, strna(u->instance), prefix, unit_load_state_to_string(u->load_state), @@ -621,10 +618,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, yes_no(unit_need_daemon_reload(u)), prefix, yes_no(u->transient), prefix, yes_no(u->perpetual), - prefix, collect_mode_to_string(u->collect_mode), - prefix, strna(unit_slice_name(u)), - prefix, strna(u->cgroup_path), - prefix, yes_no(u->cgroup_realized)); + prefix, collect_mode_to_string(u->collect_mode)); if (u->markers != 0) { fprintf(f, "%s\tMarkers:", prefix); @@ -635,37 +629,47 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { fputs("\n", f); } - if (u->cgroup_realized_mask != 0) { - _cleanup_free_ char *s = NULL; - (void) cg_mask_to_string(u->cgroup_realized_mask, &s); - fprintf(f, "%s\tCGroup realized mask: %s\n", prefix, strnull(s)); - } + if (UNIT_HAS_CGROUP_CONTEXT(u)) { + fprintf(f, + "%s\tSlice: %s\n" + "%s\tCGroup: %s\n" + "%s\tCGroup realized: %s\n", + prefix, strna(unit_slice_name(u)), + prefix, strna(u->cgroup_path), + prefix, yes_no(u->cgroup_realized)); - if (u->cgroup_enabled_mask != 0) { - _cleanup_free_ char *s = NULL; - (void) cg_mask_to_string(u->cgroup_enabled_mask, &s); - fprintf(f, "%s\tCGroup enabled mask: %s\n", prefix, strnull(s)); - } + if (u->cgroup_realized_mask != 0) { + _cleanup_free_ char *s = NULL; + (void) cg_mask_to_string(u->cgroup_realized_mask, &s); + fprintf(f, "%s\tCGroup realized mask: %s\n", prefix, strnull(s)); + } - m = unit_get_own_mask(u); - if (m != 0) { - _cleanup_free_ char *s = NULL; - (void) cg_mask_to_string(m, &s); - fprintf(f, "%s\tCGroup own mask: %s\n", prefix, strnull(s)); - } + if (u->cgroup_enabled_mask != 0) { + _cleanup_free_ char *s = NULL; + (void) cg_mask_to_string(u->cgroup_enabled_mask, &s); + fprintf(f, "%s\tCGroup enabled mask: %s\n", prefix, strnull(s)); + } - m = unit_get_members_mask(u); - if (m != 0) { - _cleanup_free_ char *s = NULL; - (void) cg_mask_to_string(m, &s); - fprintf(f, "%s\tCGroup members mask: %s\n", prefix, strnull(s)); - } + m = unit_get_own_mask(u); + if (m != 0) { + _cleanup_free_ char *s = NULL; + (void) cg_mask_to_string(m, &s); + fprintf(f, "%s\tCGroup own mask: %s\n", prefix, strnull(s)); + } - m = unit_get_delegate_mask(u); - if (m != 0) { - _cleanup_free_ char *s = NULL; - (void) cg_mask_to_string(m, &s); - fprintf(f, "%s\tCGroup delegate mask: %s\n", prefix, strnull(s)); + m = unit_get_members_mask(u); + if (m != 0) { + _cleanup_free_ char *s = NULL; + (void) cg_mask_to_string(m, &s); + fprintf(f, "%s\tCGroup members mask: %s\n", prefix, strnull(s)); + } + + m = unit_get_delegate_mask(u); + if (m != 0) { + _cleanup_free_ char *s = NULL; + (void) cg_mask_to_string(m, &s); + fprintf(f, "%s\tCGroup delegate mask: %s\n", prefix, strnull(s)); + } } if (!sd_id128_is_null(u->invocation_id)) @@ -735,7 +739,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { UnitDependencyInfo di; Unit *other; - HASHMAP_FOREACH_KEY(di.data, other, u->dependencies[d]) { + HASHMAP_FOREACH_KEY(di.data, other, unit_get_dependencies(u, d)) { bool space = false; fprintf(f, "%s\t%s: %s (", prefix, unit_dependency_to_string(d), other->id); @@ -770,12 +774,14 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tRefuseManualStart: %s\n" "%s\tRefuseManualStop: %s\n" "%s\tDefaultDependencies: %s\n" + "%s\tOnSuccessJobMode: %s\n" "%s\tOnFailureJobMode: %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_success_job_mode), prefix, job_mode_to_string(u->on_failure_job_mode), prefix, yes_no(u->ignore_on_isolate)); diff --git a/src/core/unit.c b/src/core/unit.c index fc63f3bee5..9c97a92746 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -85,8 +85,6 @@ const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { [UNIT_SCOPE] = &scope_vtable, }; -static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency); - Unit* unit_new(Manager *m, size_t size) { Unit *u; @@ -103,6 +101,7 @@ 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->on_success_job_mode = JOB_FAIL; u->cgroup_control_inotify_wd = -1; u->cgroup_memory_inotify_wd = -1; u->job_timeout = USEC_INFINITY; @@ -123,7 +122,7 @@ Unit* unit_new(Manager *m, size_t size) { u->last_section_private = -1; u->start_ratelimit = (RateLimit) { m->default_start_limit_interval, m->default_start_limit_burst }; - u->auto_stop_ratelimit = (RateLimit) { 10 * USEC_PER_SEC, 16 }; + u->auto_start_stop_ratelimit = (RateLimit) { 10 * USEC_PER_SEC, 16 }; for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++) u->io_accounting_last[i] = UINT64_MAX; @@ -508,22 +507,58 @@ void unit_submit_to_stop_when_unneeded_queue(Unit *u) { u->in_stop_when_unneeded_queue = true; } -static void bidi_set_free(Unit *u, Hashmap *h) { - Unit *other; - void *v; - +void unit_submit_to_start_when_upheld_queue(Unit *u) { assert(u); - /* Frees the hashmap and makes sure we are dropped from the inverse pointers */ + if (u->in_start_when_upheld_queue) + return; - HASHMAP_FOREACH_KEY(v, other, h) { - for (UnitDependency d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - hashmap_remove(other->dependencies[d], u); + if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u))) + return; - unit_add_to_gc_queue(other); + if (!unit_has_dependency(u, UNIT_ATOM_START_STEADILY, NULL)) + return; + + LIST_PREPEND(start_when_upheld_queue, u->manager->start_when_upheld_queue, u); + u->in_start_when_upheld_queue = true; +} + +void unit_submit_to_stop_when_bound_queue(Unit *u) { + assert(u); + + if (u->in_stop_when_bound_queue) + return; + + if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) + return; + + if (!unit_has_dependency(u, UNIT_ATOM_CANNOT_BE_ACTIVE_WITHOUT, NULL)) + return; + + LIST_PREPEND(stop_when_bound_queue, u->manager->stop_when_bound_queue, u); + u->in_stop_when_bound_queue = true; +} + +static void unit_clear_dependencies(Unit *u) { + assert(u); + + /* Removes all dependencies configured on u and their reverse dependencies. */ + + for (Hashmap *deps; (deps = hashmap_steal_first(u->dependencies));) { + + for (Unit *other; (other = hashmap_steal_first_key(deps));) { + Hashmap *other_deps; + + HASHMAP_FOREACH(other_deps, other->dependencies) + hashmap_remove(other_deps, u); + + unit_add_to_gc_queue(other); + } + + hashmap_free(deps); } - hashmap_free(h); + u->dependencies = hashmap_free(u->dependencies); } static void unit_remove_transient(Unit *u) { @@ -613,6 +648,7 @@ static void unit_done(Unit *u) { } Unit* unit_free(Unit *u) { + Unit *slice; char *t; if (!u) @@ -656,13 +692,12 @@ Unit* unit_free(Unit *u) { job_free(j); } - for (UnitDependency d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - bidi_set_free(u, u->dependencies[d]); - /* A unit is being dropped from the tree, make sure our family is realized properly. Do this after we * detach the unit from slice tree in order to eliminate its effect on controller masks. */ - if (UNIT_ISSET(u->slice)) - unit_add_family_to_cgroup_realize_queue(UNIT_DEREF(u->slice)); + slice = UNIT_GET_SLICE(u); + unit_clear_dependencies(u); + if (slice) + unit_add_family_to_cgroup_realize_queue(slice); if (u->on_console) manager_unref_console(u->manager); @@ -686,7 +721,6 @@ Unit* unit_free(Unit *u) { unit_unwatch_all_pids(u); - unit_ref_unset(&u->slice); while (u->refs_by_target) unit_ref_unset(u->refs_by_target); @@ -717,6 +751,12 @@ Unit* unit_free(Unit *u) { if (u->in_stop_when_unneeded_queue) LIST_REMOVE(stop_when_unneeded_queue, u->manager->stop_when_unneeded_queue, u); + if (u->in_start_when_upheld_queue) + LIST_REMOVE(start_when_upheld_queue, u->manager->start_when_upheld_queue, u); + + if (u->in_stop_when_bound_queue) + LIST_REMOVE(stop_when_bound_queue, u->manager->stop_when_bound_queue, u); + safe_close(u->ip_accounting_ingress_map_fd); safe_close(u->ip_accounting_egress_map_fd); @@ -809,22 +849,7 @@ const char* unit_sub_state_to_string(Unit *u) { return UNIT_VTABLE(u)->sub_state_to_string(u); } -static int hashmap_complete_move(Hashmap **s, Hashmap **other) { - assert(s); - assert(other); - - if (!*other) - return 0; - - if (*s) - return hashmap_move(*s, *other); - else - *s = TAKE_PTR(*other); - - return 0; -} - -static int merge_names(Unit *u, Unit *other) { +static int unit_merge_names(Unit *u, Unit *other) { char *name; int r; @@ -850,87 +875,253 @@ static int merge_names(Unit *u, Unit *other) { return 0; } -static int reserve_dependencies(Unit *u, Unit *other, UnitDependency d) { - unsigned n_reserve; - - assert(u); - assert(other); - assert(d < _UNIT_DEPENDENCY_MAX); - - /* - * If u does not have this dependency set allocated, there is no need - * to reserve anything. In that case other's set will be transferred - * as a whole to u by complete_move(). - */ - if (!u->dependencies[d]) - return 0; - - /* merge_dependencies() will skip a u-on-u dependency */ - n_reserve = hashmap_size(other->dependencies[d]) - !!hashmap_get(other->dependencies[d], u); - - return hashmap_reserve(u->dependencies[d], n_reserve); -} - -static void merge_dependencies(Unit *u, Unit *other, const char *other_id, UnitDependency d) { - Unit *back; - void *v; +static int unit_reserve_dependencies(Unit *u, Unit *other) { + size_t n_reserve; + Hashmap* deps; + void *d; int r; - /* Merges all dependencies of type 'd' of the unit 'other' into the deps of the unit 'u' */ + assert(u); + assert(other); + + /* Let's reserve some space in the dependency hashmaps so that later on merging the units cannot + * fail. + * + * First make some room in the per dependency type hashmaps. Using the summed size of both unit's + * hashmaps is an estimate that is likely too high since they probably use some of the same + * types. But it's never too low, and that's all we need. */ + + n_reserve = MIN(hashmap_size(other->dependencies), LESS_BY((size_t) _UNIT_DEPENDENCY_MAX, hashmap_size(u->dependencies))); + if (n_reserve > 0) { + r = hashmap_ensure_allocated(&u->dependencies, NULL); + if (r < 0) + return r; + + r = hashmap_reserve(u->dependencies, n_reserve); + if (r < 0) + return r; + } + + /* Now, enlarge our per dependency type hashmaps by the number of entries in the same hashmap of the + * other unit's dependencies. + * + * NB: If u does not have a dependency set allocated for some dependency type, there is no need to + * reserve anything for. In that case other's set will be transferred as a whole to u by + * complete_move(). */ + + HASHMAP_FOREACH_KEY(deps, d, u->dependencies) { + Hashmap *other_deps; + + other_deps = hashmap_get(other->dependencies, d); + + r = hashmap_reserve(deps, hashmap_size(other_deps)); + if (r < 0) + return r; + } + + return 0; +} + +static void unit_maybe_warn_about_dependency( + Unit *u, + const char *other_id, + UnitDependency dependency) { + + assert(u); + + /* Only warn about some unit types */ + if (!IN_SET(dependency, + UNIT_CONFLICTS, + UNIT_CONFLICTED_BY, + UNIT_BEFORE, + UNIT_AFTER, + UNIT_ON_SUCCESS, + UNIT_ON_FAILURE, + UNIT_TRIGGERS, + UNIT_TRIGGERED_BY)) + return; + + if (streq_ptr(u->id, other_id)) + log_unit_warning(u, "Dependency %s=%s dropped", unit_dependency_to_string(dependency), u->id); + else + log_unit_warning(u, "Dependency %s=%s dropped, merged into %s", unit_dependency_to_string(dependency), strna(other_id), u->id); +} + +static int unit_per_dependency_type_hashmap_update( + Hashmap *per_type, + Unit *other, + UnitDependencyMask origin_mask, + UnitDependencyMask destination_mask) { + + UnitDependencyInfo info; + int r; + + assert(other); + assert_cc(sizeof(void*) == sizeof(info)); + + /* Acquire the UnitDependencyInfo entry for the Unit* we are interested in, and update it if it + * exists, or insert it anew if not. */ + + info.data = hashmap_get(per_type, other); + if (info.data) { + /* Entry already exists. Add in our mask. */ + + if (FLAGS_SET(origin_mask, info.origin_mask) && + FLAGS_SET(destination_mask, info.destination_mask)) + return 0; /* NOP */ + + info.origin_mask |= origin_mask; + info.destination_mask |= destination_mask; + + r = hashmap_update(per_type, other, info.data); + } else { + info = (UnitDependencyInfo) { + .origin_mask = origin_mask, + .destination_mask = destination_mask, + }; + + r = hashmap_put(per_type, other, info.data); + } + if (r < 0) + return r; + + + return 1; +} + +static int unit_add_dependency_hashmap( + Hashmap **dependencies, + UnitDependency d, + Unit *other, + UnitDependencyMask origin_mask, + UnitDependencyMask destination_mask) { + + Hashmap *per_type; + int r; + + assert(dependencies); + assert(other); + assert(origin_mask < _UNIT_DEPENDENCY_MASK_FULL); + assert(destination_mask < _UNIT_DEPENDENCY_MASK_FULL); + assert(origin_mask > 0 || destination_mask > 0); + + /* Ensure the top-level dependency hashmap exists that maps UnitDependency β†’ Hashmap(Unit* β†’ + * UnitDependencyInfo) */ + r = hashmap_ensure_allocated(dependencies, NULL); + if (r < 0) + return r; + + /* Acquire the inner hashmap, that maps Unit* β†’ UnitDependencyInfo, for the specified dependency + * type, and if it's missing allocate it and insert it. */ + per_type = hashmap_get(*dependencies, UNIT_DEPENDENCY_TO_PTR(d)); + if (!per_type) { + per_type = hashmap_new(NULL); + if (!per_type) + return -ENOMEM; + + r = hashmap_put(*dependencies, UNIT_DEPENDENCY_TO_PTR(d), per_type); + if (r < 0) { + hashmap_free(per_type); + return r; + } + } + + return unit_per_dependency_type_hashmap_update(per_type, other, origin_mask, destination_mask); +} + +static void unit_merge_dependencies( + Unit *u, + Unit *other) { + + int r; assert(u); assert(other); - assert(d < _UNIT_DEPENDENCY_MAX); - /* Fix backwards pointers. Let's iterate through all dependent units of the other unit. */ - HASHMAP_FOREACH_KEY(v, back, other->dependencies[d]) + if (u == other) + return; + + for (;;) { + _cleanup_(hashmap_freep) Hashmap *other_deps = NULL; + UnitDependencyInfo di_back; + Unit *back; + void *dt; /* Actually of type UnitDependency, except that we don't bother casting it here, + * since the hashmaps all want it as void pointer. */ + + /* Let's focus on one dependency type at a time, that 'other' has defined. */ + other_deps = hashmap_steal_first_key_and_value(other->dependencies, &dt); + if (!other_deps) + break; /* done! */ + + /* Now iterate through all dependencies of this dependency type, of 'other'. We refer to the + * referenced units as 'back'. */ + HASHMAP_FOREACH_KEY(di_back.data, back, other_deps) { + Hashmap *back_deps; + void *back_dt; - /* Let's now iterate through the dependencies of that dependencies of the other units, - * looking for pointers back, and let's fix them up, to instead point to 'u'. */ - for (UnitDependency k = 0; k < _UNIT_DEPENDENCY_MAX; k++) if (back == u) { - /* Do not add dependencies between u and itself. */ - if (hashmap_remove(back->dependencies[k], other)) - maybe_warn_about_dependency(u, other_id, k); - } else { - UnitDependencyInfo di_u, di_other; - - /* Let's drop this dependency between "back" and "other", and let's create it between - * "back" and "u" instead. Let's merge the bit masks of the dependency we are moving, - * and any such dependency which might already exist */ - - di_other.data = hashmap_get(back->dependencies[k], other); - if (!di_other.data) - continue; /* dependency isn't set, let's try the next one */ - - di_u.data = hashmap_get(back->dependencies[k], u); - - UnitDependencyInfo di_merged = { - .origin_mask = di_u.origin_mask | di_other.origin_mask, - .destination_mask = di_u.destination_mask | di_other.destination_mask, - }; - - r = hashmap_remove_and_replace(back->dependencies[k], other, u, di_merged.data); - if (r < 0) - log_warning_errno(r, "Failed to remove/replace: back=%s other=%s u=%s: %m", back->id, other_id, u->id); - assert(r >= 0); - - /* assert_se(hashmap_remove_and_replace(back->dependencies[k], other, u, di_merged.data) >= 0); */ + /* This is a dependency pointing back to the unit we want to merge with? + * Suppress it (but warn) */ + unit_maybe_warn_about_dependency(u, other->id, UNIT_DEPENDENCY_FROM_PTR(dt)); + continue; } - /* Also do not move dependencies on u to itself */ - back = hashmap_remove(other->dependencies[d], u); - if (back) - maybe_warn_about_dependency(u, other_id, d); + /* Now iterate through all deps of 'back', and fix the ones pointing to 'other' to + * point to 'u' instead. */ + HASHMAP_FOREACH_KEY(back_deps, back_dt, back->dependencies) { + UnitDependencyInfo di_move; - /* The move cannot fail. The caller must have performed a reservation. */ - assert_se(hashmap_complete_move(&u->dependencies[d], &other->dependencies[d]) == 0); + di_move.data = hashmap_remove(back_deps, other); + if (!di_move.data) + continue; - other->dependencies[d] = hashmap_free(other->dependencies[d]); + assert_se(unit_per_dependency_type_hashmap_update( + back_deps, + u, + di_move.origin_mask, + di_move.destination_mask) >= 0); + } + } + + /* Now all references towards 'other' of the current type 'dt' are corrected to point to + * 'u'. Lets's now move the deps of type 'dt' from 'other' to 'u'. First, let's try to move + * them per type wholesale. */ + r = hashmap_put(u->dependencies, dt, other_deps); + if (r == -EEXIST) { + Hashmap *deps; + + /* The target unit already has dependencies of this type, let's then merge this individually. */ + + assert_se(deps = hashmap_get(u->dependencies, dt)); + + for (;;) { + UnitDependencyInfo di_move; + + /* Get first dep */ + di_move.data = hashmap_steal_first_key_and_value(other_deps, (void**) &back); + if (!di_move.data) + break; /* done */ + if (back == u) { + /* Would point back to us, ignore */ + unit_maybe_warn_about_dependency(u, other->id, UNIT_DEPENDENCY_FROM_PTR(dt)); + continue; + } + + assert_se(unit_per_dependency_type_hashmap_update(deps, back, di_move.origin_mask, di_move.destination_mask) >= 0); + } + } else { + assert_se(r >= 0); + TAKE_PTR(other_deps); + + if (hashmap_remove(other_deps, u)) + unit_maybe_warn_about_dependency(u, other->id, UNIT_DEPENDENCY_FROM_PTR(dt)); + } + } + + other->dependencies = hashmap_free(other->dependencies); } int unit_merge(Unit *u, Unit *other) { - const char *other_id = NULL; int r; assert(u); @@ -964,22 +1155,14 @@ int unit_merge(Unit *u, Unit *other) { if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) return -EEXIST; - if (other->id) - other_id = strdupa(other->id); - - /* Make reservations to ensure merge_dependencies() won't fail */ - for (UnitDependency d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { - r = reserve_dependencies(u, other, d); - /* - * We don't rollback reservations if we fail. We don't have - * a way to undo reservations. A reservation is not a leak. - */ - if (r < 0) - return r; - } + /* Make reservations to ensure merge_dependencies() won't fail. We don't rollback reservations if we + * fail. We don't have a way to undo reservations. A reservation is not a leak. */ + r = unit_reserve_dependencies(u, other); + if (r < 0) + return r; /* Merge names */ - r = merge_names(u, other); + r = unit_merge_names(u, other); if (r < 0) return r; @@ -988,8 +1171,7 @@ int unit_merge(Unit *u, Unit *other) { unit_ref_set(other->refs_by_target, other->refs_by_target->source, u); /* Merge dependencies */ - for (UnitDependency d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - merge_dependencies(u, other, other_id, d); + unit_merge_dependencies(u, other); other->load_state = UNIT_MERGED; other->merged_into = u; @@ -1248,13 +1430,14 @@ int unit_add_default_target_dependency(Unit *u, Unit *target) { return 0; /* Don't create loops */ - if (hashmap_get(target->dependencies[UNIT_BEFORE], u)) + if (unit_has_dependency(target, UNIT_ATOM_BEFORE, u)) return 0; return unit_add_dependency(target, UNIT_AFTER, u, true, UNIT_DEPENDENCY_DEFAULT); } static int unit_add_slice_dependencies(Unit *u) { + Unit *slice; assert(u); if (!UNIT_HAS_CGROUP_CONTEXT(u)) @@ -1265,8 +1448,9 @@ static int unit_add_slice_dependencies(Unit *u) { relationship). */ UnitDependencyMask mask = u->type == UNIT_SLICE ? UNIT_DEPENDENCY_IMPLICIT : UNIT_DEPENDENCY_FILE; - if (UNIT_ISSET(u->slice)) - return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT_DEREF(u->slice), true, mask); + slice = UNIT_GET_SLICE(u); + if (slice) + return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, slice, true, mask); if (unit_has_name(u, SPECIAL_ROOT_SLICE)) return 0; @@ -1363,6 +1547,31 @@ static int unit_add_startup_units(Unit *u) { return set_ensure_put(&u->manager->startup_units, NULL, u); } +static int unit_validate_on_failure_job_mode( + Unit *u, + const char *job_mode_setting, + JobMode job_mode, + const char *dependency_name, + UnitDependencyAtom atom) { + + Unit *other, *found = NULL; + + if (job_mode != JOB_ISOLATE) + return 0; + + UNIT_FOREACH_DEPENDENCY(other, u, atom) { + if (!found) + found = other; + else if (found != other) + return log_unit_error_errno( + u, SYNTHETIC_ERRNO(ENOEXEC), + "More than one %s dependencies specified but %sisolate set. Refusing.", + dependency_name, job_mode_setting); + } + + return 0; +} + int unit_load(Unit *u) { int r; @@ -1416,11 +1625,13 @@ int unit_load(Unit *u) { if (r < 0) goto fail; - if (u->on_failure_job_mode == JOB_ISOLATE && hashmap_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { - r = log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), - "More than one OnFailure= dependencies specified but OnFailureJobMode=isolate set. Refusing."); + r = unit_validate_on_failure_job_mode(u, "OnSuccessJobMode=", u->on_success_job_mode, "OnSuccess=", UNIT_ATOM_ON_SUCCESS); + if (r < 0) + goto fail; + + r = unit_validate_on_failure_job_mode(u, "OnFailureJobMode=", u->on_failure_job_mode, "OnFailure=", UNIT_ATOM_ON_FAILURE); + if (r < 0) goto fail; - } if (u->job_running_timeout != USEC_INFINITY && u->job_running_timeout > u->job_timeout) log_unit_warning(u, "JobRunningTimeoutSec= is greater than JobTimeoutSec=, it has no effect."); @@ -1577,18 +1788,18 @@ bool unit_shall_confirm_spawn(Unit *u) { static bool unit_verify_deps(Unit *u) { Unit *other; - void *v; assert(u); - /* Checks whether all BindsTo= dependencies of this unit are fulfilled β€” if they are also combined with - * After=. We do not check Requires= or Requisite= here as they only should have an effect on the job - * processing, but do not have any effect afterwards. We don't check BindsTo= dependencies that are not used in - * conjunction with After= as for them any such check would make things entirely racy. */ + /* Checks whether all BindsTo= dependencies of this unit are fulfilled β€” if they are also combined + * with After=. We do not check Requires= or Requisite= here as they only should have an effect on + * the job processing, but do not have any effect afterwards. We don't check BindsTo= dependencies + * that are not used in conjunction with After= as for them any such check would make things entirely + * racy. */ - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO]) { + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_CANNOT_BE_ACTIVE_WITHOUT) { - if (!hashmap_contains(u->dependencies[UNIT_AFTER], other)) + if (!unit_has_dependency(u, UNIT_ATOM_AFTER, other)) continue; if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(other))) { @@ -1802,197 +2013,208 @@ bool unit_can_reload(Unit *u) { if (UNIT_VTABLE(u)->can_reload) return UNIT_VTABLE(u)->can_reload(u); - if (!hashmap_isempty(u->dependencies[UNIT_PROPAGATES_RELOAD_TO])) + if (unit_has_dependency(u, UNIT_ATOM_PROPAGATES_RELOAD_TO, NULL)) return true; return UNIT_VTABLE(u)->reload; } bool unit_is_unneeded(Unit *u) { - static const UnitDependency deps[] = { - UNIT_REQUIRED_BY, - UNIT_REQUISITE_OF, - UNIT_WANTED_BY, - UNIT_BOUND_BY, - }; - + Unit *other; assert(u); if (!u->stop_when_unneeded) return false; /* Don't clean up while the unit is transitioning or is even inactive. */ - if (!UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) + if (unit_active_state(u) != UNIT_ACTIVE) return false; if (u->job) return false; - for (size_t j = 0; j < ELEMENTSOF(deps); j++) { - Unit *other; - void *v; - + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_PINS_STOP_WHEN_UNNEEDED) { /* If a dependent unit has a job queued, is active or transitioning, or is marked for * restart, then don't clean this one up. */ - HASHMAP_FOREACH_KEY(v, other, u->dependencies[deps[j]]) { - if (other->job) - return false; + if (other->job) + return false; - if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) - return false; + if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) + return false; - if (unit_will_restart(other)) - return false; - } + if (unit_will_restart(other)) + return false; } return true; } +bool unit_is_upheld_by_active(Unit *u, Unit **ret_culprit) { + Unit *other; + + assert(u); + + /* Checks if the unit needs to be started because it currently is not running, but some other unit + * that is active declared an Uphold= dependencies on it */ + + if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)) || u->job) { + if (ret_culprit) + *ret_culprit = NULL; + return false; + } + + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_START_STEADILY) { + if (other->job) + continue; + + if (UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(other))) { + if (ret_culprit) + *ret_culprit = other; + return true; + } + } + + if (ret_culprit) + *ret_culprit = NULL; + return false; +} + +bool unit_is_bound_by_inactive(Unit *u, Unit **ret_culprit) { + Unit *other; + + assert(u); + + /* Checks whether this unit is bound to another unit that is inactive, i.e. whether we should stop + * because the other unit is down.*/ + + if (unit_active_state(u) != UNIT_ACTIVE || u->job) { + /* Don't clean up while the unit is transitioning or is even inactive. */ + if (ret_culprit) + *ret_culprit = NULL; + return false; + } + + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_CANNOT_BE_ACTIVE_WITHOUT) { + if (other->job) + continue; + + if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { + if (*ret_culprit) + *ret_culprit = other; + + return true; + } + } + + if (ret_culprit) + *ret_culprit = NULL; + return false; +} + static void check_unneeded_dependencies(Unit *u) { - - static const UnitDependency deps[] = { - UNIT_REQUIRES, - UNIT_REQUISITE, - UNIT_WANTS, - UNIT_BINDS_TO, - }; - + Unit *other; assert(u); /* Add all units this unit depends on to the queue that processes StopWhenUnneeded= behaviour. */ - for (size_t j = 0; j < ELEMENTSOF(deps); j++) { - Unit *other; - void *v; - - HASHMAP_FOREACH_KEY(v, other, u->dependencies[deps[j]]) - unit_submit_to_stop_when_unneeded_queue(other); - } + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_ADD_STOP_WHEN_UNNEEDED_QUEUE) + unit_submit_to_stop_when_unneeded_queue(other); } -static void unit_check_binds_to(Unit *u) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool stop = false; +static void check_uphold_dependencies(Unit *u) { Unit *other; - void *v; - int r; - assert(u); - if (u->job) - return; + /* Add all units this unit depends on to the queue that processes Uphold= behaviour. */ - if (unit_active_state(u) != UNIT_ACTIVE) - return; + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_ADD_START_WHEN_UPHELD_QUEUE) + unit_submit_to_start_when_upheld_queue(other); +} - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO]) { - if (other->job) - continue; +static void check_bound_by_dependencies(Unit *u) { + Unit *other; + assert(u); - if (!other->coldplugged) - /* We might yet create a job for the other unit… */ - continue; + /* Add all units this unit depends on to the queue that processes BindsTo= stop behaviour. */ - if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) - continue; - - stop = true; - break; - } - - if (!stop) - return; - - /* 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_below(&u->auto_stop_ratelimit)) { - log_unit_warning(u, "Unit is bound to inactive unit %s, but not stopping since we tried this too often recently.", other->id); - return; - } - - assert(other); - 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, NULL, &error, NULL); - if (r < 0) - log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_ADD_CANNOT_BE_ACTIVE_WITHOUT_QUEUE) + unit_submit_to_stop_when_bound_queue(other); } static void retroactively_start_dependencies(Unit *u) { Unit *other; - void *v; assert(u); assert(UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))); - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_REQUIRES]) - if (!hashmap_get(u->dependencies[UNIT_AFTER], other) && + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_RETROACTIVE_START_REPLACE) /* Requires= + BindsTo= */ + if (!unit_has_dependency(u, UNIT_ATOM_AFTER, other) && !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL, NULL); - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BINDS_TO]) - if (!hashmap_get(u->dependencies[UNIT_AFTER], other) && - !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL, NULL); - - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_WANTS]) - if (!hashmap_get(u->dependencies[UNIT_AFTER], other) && + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_RETROACTIVE_START_FAIL) /* Wants= */ + if (!unit_has_dependency(u, UNIT_ATOM_AFTER, other) && !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL, NULL); - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_CONFLICTS]) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL); - - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_CONFLICTED_BY]) + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_RETROACTIVE_STOP_ON_START) /* Conflicts= (and inverse) */ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL); } static void retroactively_stop_dependencies(Unit *u) { Unit *other; - void *v; assert(u); assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); /* Pull down units which are bound to us recursively if enabled */ - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_BOUND_BY]) + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_RETROACTIVE_STOP_ON_STOP) /* BoundBy= */ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL); } -void unit_start_on_failure(Unit *u) { +void unit_start_on_failure( + Unit *u, + const char *dependency_name, + UnitDependencyAtom atom, + JobMode job_mode) { + + bool logged = false; Unit *other; - void *v; int r; assert(u); + assert(dependency_name); + assert(IN_SET(atom, UNIT_ATOM_ON_SUCCESS, UNIT_ATOM_ON_FAILURE)); - if (hashmap_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) - return; + /* Act on OnFailure= and OnSuccess= dependencies */ - log_unit_info(u, "Triggering OnFailure= dependencies."); - - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_ON_FAILURE]) { + UNIT_FOREACH_DEPENDENCY(other, u, atom) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, NULL, &error, NULL); + if (!logged) { + log_unit_info(u, "Triggering %s dependencies.", dependency_name); + logged = true; + } + + r = manager_add_job(u->manager, JOB_START, other, job_mode, NULL, &error, NULL); if (r < 0) - log_unit_warning_errno(u, r, "Failed to enqueue OnFailure= job, ignoring: %s", bus_error_message(&error, r)); + log_unit_warning_errno( + u, r, "Failed to enqueue %s job, ignoring: %s", + dependency_name, bus_error_message(&error, r)); } + + if (logged) + log_unit_debug(u, "Triggering %s dependencies done.", dependency_name); } void unit_trigger_notify(Unit *u) { Unit *other; - void *v; assert(u); - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_TRIGGERED_BY]) + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_TRIGGERED_BY) if (UNIT_VTABLE(other)->trigger_notify) UNIT_VTABLE(other)->trigger_notify(other, u); } @@ -2444,15 +2666,11 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag retroactively_stop_dependencies(u); } - /* stop unneeded units regardless if going down was expected or not */ - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - check_unneeded_dependencies(u); - if (ns != os && ns == UNIT_FAILED) { log_unit_debug(u, "Unit entered failed state."); if (!(flags & UNIT_NOTIFY_WILL_AUTO_RESTART)) - unit_start_on_failure(u); + unit_start_on_failure(u, "OnFailure=", UNIT_ATOM_ON_FAILURE, u->on_failure_job_mode); } if (UNIT_IS_ACTIVE_OR_RELOADING(ns) && !UNIT_IS_ACTIVE_OR_RELOADING(os)) { @@ -2468,6 +2686,10 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag unit_emit_audit_stop(u, ns); unit_log_resources(u); } + + if (ns == UNIT_INACTIVE && !IN_SET(os, UNIT_FAILED, UNIT_INACTIVE, UNIT_MAINTENANCE) && + !(flags & UNIT_NOTIFY_WILL_AUTO_RESTART)) + unit_start_on_failure(u, "OnSuccess=", UNIT_ATOM_ON_SUCCESS, u->on_success_job_mode); } manager_recheck_journal(m); @@ -2476,14 +2698,6 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag unit_trigger_notify(u); if (!MANAGER_IS_RELOADING(m)) { - /* Maybe we finished startup and are now ready for being stopped because unneeded? */ - unit_submit_to_stop_when_unneeded_queue(u); - - /* Maybe we finished startup, but something we needed has vanished? Let's die then. (This happens when - * something BindsTo= to a Type=oneshot unit, as these units go directly from starting to inactive, - * without ever entering started.) */ - unit_check_binds_to(u); - if (os != UNIT_FAILED && ns == UNIT_FAILED) { reason = strjoina("unit ", u->id, " failed"); emergency_action(m, u->failure_action, 0, u->reboot_arg, unit_failure_action_exit_status(u), reason); @@ -2493,7 +2707,36 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, UnitNotifyFlag } } - unit_add_to_gc_queue(u); + /* And now, add the unit or depending units to various queues that will act on the new situation if + * needed. These queues generally check for continous state changes rather than events (like most of + * the state propagation above), and do work deferred instead of instantly, since they typically + * don't want to run during reloading, and usually involve checking combined state of multiple units + * at once. */ + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) { + /* Stop unneeded units and bound-by units regardless if going down was expected or not */ + check_unneeded_dependencies(u); + check_bound_by_dependencies(u); + + /* Maybe someone wants us to remain up? */ + unit_submit_to_start_when_upheld_queue(u); + + /* Maybe the unit should be GC'ed now? */ + unit_add_to_gc_queue(u); + } + + if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { + /* Start uphold units regardless if going up was expected or not */ + check_uphold_dependencies(u); + + /* Maybe we finished startup and are now ready for being stopped because unneeded? */ + unit_submit_to_stop_when_unneeded_queue(u); + + /* Maybe we finished startup, but something we needed has vanished? Let's die then. (This happens + * when something BindsTo= to a Type=oneshot unit, as these units go directly from starting to + * inactive, without ever entering started.) */ + unit_submit_to_stop_when_bound_queue(u); + } } int unit_watch_pid(Unit *u, pid_t pid, bool exclusive) { @@ -2733,66 +2976,6 @@ bool unit_job_is_applicable(Unit *u, JobType j) { } } -static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency) { - assert(u); - - /* Only warn about some unit types */ - if (!IN_SET(dependency, UNIT_CONFLICTS, UNIT_CONFLICTED_BY, UNIT_BEFORE, UNIT_AFTER, UNIT_ON_FAILURE, UNIT_TRIGGERS, UNIT_TRIGGERED_BY)) - return; - - if (streq_ptr(u->id, other)) - log_unit_warning(u, "Dependency %s=%s dropped", unit_dependency_to_string(dependency), u->id); - else - log_unit_warning(u, "Dependency %s=%s dropped, merged into %s", unit_dependency_to_string(dependency), strna(other), u->id); -} - -static int unit_add_dependency_hashmap( - Hashmap **h, - Unit *other, - UnitDependencyMask origin_mask, - UnitDependencyMask destination_mask) { - - UnitDependencyInfo info; - int r; - - assert(h); - assert(other); - assert(origin_mask < _UNIT_DEPENDENCY_MASK_FULL); - assert(destination_mask < _UNIT_DEPENDENCY_MASK_FULL); - assert(origin_mask > 0 || destination_mask > 0); - - r = hashmap_ensure_allocated(h, NULL); - if (r < 0) - return r; - - assert_cc(sizeof(void*) == sizeof(info)); - - info.data = hashmap_get(*h, other); - if (info.data) { - /* Entry already exists. Add in our mask. */ - - if (FLAGS_SET(origin_mask, info.origin_mask) && - FLAGS_SET(destination_mask, info.destination_mask)) - return 0; /* NOP */ - - info.origin_mask |= origin_mask; - info.destination_mask |= destination_mask; - - r = hashmap_update(*h, other, info.data); - } else { - info = (UnitDependencyInfo) { - .origin_mask = origin_mask, - .destination_mask = destination_mask, - }; - - r = hashmap_put(*h, other, info.data); - } - if (r < 0) - return r; - - return 1; -} - int unit_add_dependency( Unit *u, UnitDependency d, @@ -2802,33 +2985,44 @@ int unit_add_dependency( static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = { [UNIT_REQUIRES] = UNIT_REQUIRED_BY, - [UNIT_WANTS] = UNIT_WANTED_BY, [UNIT_REQUISITE] = UNIT_REQUISITE_OF, + [UNIT_WANTS] = UNIT_WANTED_BY, [UNIT_BINDS_TO] = UNIT_BOUND_BY, [UNIT_PART_OF] = UNIT_CONSISTS_OF, + [UNIT_UPHOLDS] = UNIT_UPHELD_BY, [UNIT_REQUIRED_BY] = UNIT_REQUIRES, [UNIT_REQUISITE_OF] = UNIT_REQUISITE, [UNIT_WANTED_BY] = UNIT_WANTS, [UNIT_BOUND_BY] = UNIT_BINDS_TO, [UNIT_CONSISTS_OF] = UNIT_PART_OF, + [UNIT_UPHELD_BY] = UNIT_UPHOLDS, [UNIT_CONFLICTS] = UNIT_CONFLICTED_BY, [UNIT_CONFLICTED_BY] = UNIT_CONFLICTS, [UNIT_BEFORE] = UNIT_AFTER, [UNIT_AFTER] = UNIT_BEFORE, - [UNIT_ON_FAILURE] = _UNIT_DEPENDENCY_INVALID, - [UNIT_REFERENCES] = UNIT_REFERENCED_BY, - [UNIT_REFERENCED_BY] = UNIT_REFERENCES, + [UNIT_ON_SUCCESS] = UNIT_ON_SUCCESS_OF, + [UNIT_ON_SUCCESS_OF] = UNIT_ON_SUCCESS, + [UNIT_ON_FAILURE] = UNIT_ON_FAILURE_OF, + [UNIT_ON_FAILURE_OF] = UNIT_ON_FAILURE, [UNIT_TRIGGERS] = UNIT_TRIGGERED_BY, [UNIT_TRIGGERED_BY] = UNIT_TRIGGERS, [UNIT_PROPAGATES_RELOAD_TO] = UNIT_RELOAD_PROPAGATED_FROM, [UNIT_RELOAD_PROPAGATED_FROM] = UNIT_PROPAGATES_RELOAD_TO, - [UNIT_JOINS_NAMESPACE_OF] = UNIT_JOINS_NAMESPACE_OF, + [UNIT_PROPAGATES_STOP_TO] = UNIT_STOP_PROPAGATED_FROM, + [UNIT_STOP_PROPAGATED_FROM] = UNIT_PROPAGATES_STOP_TO, + [UNIT_JOINS_NAMESPACE_OF] = UNIT_JOINS_NAMESPACE_OF, /* symmetric! πŸ‘“ */ + [UNIT_REFERENCES] = UNIT_REFERENCED_BY, + [UNIT_REFERENCED_BY] = UNIT_REFERENCES, + [UNIT_IN_SLICE] = UNIT_SLICE_OF, + [UNIT_SLICE_OF] = UNIT_IN_SLICE, }; Unit *original_u = u, *original_other = other; + UnitDependencyAtom a; int r; - /* Helper to know whether sending a notification is necessary or not: - * if the dependency is already there, no need to notify! */ - bool noop = true; + + /* Helper to know whether sending a notification is necessary or not: if the dependency is already + * there, no need to notify! */ + bool noop; assert(u); assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); @@ -2836,63 +3030,79 @@ int unit_add_dependency( u = unit_follow_merge(u); other = unit_follow_merge(other); + a = unit_dependency_to_atom(d); + assert(a >= 0); - /* We won't allow dependencies on ourselves. We will not - * consider them an error however. */ + /* We won't allow dependencies on ourselves. We will not consider them an error however. */ if (u == other) { - maybe_warn_about_dependency(original_u, original_other->id, d); + unit_maybe_warn_about_dependency(original_u, original_other->id, d); return 0; } - /* Note that ordering a device unit after a unit is permitted since it - * allows to start its job running timeout at a specific time. */ - if (d == UNIT_BEFORE && other->type == UNIT_DEVICE) { + /* Note that ordering a device unit after a unit is permitted since it allows to start its job + * running timeout at a specific time. */ + if (FLAGS_SET(a, UNIT_ATOM_BEFORE) && other->type == UNIT_DEVICE) { log_unit_warning(u, "Dependency Before=%s ignored (.device units cannot be delayed)", other->id); return 0; } - if (d == UNIT_ON_FAILURE && !UNIT_VTABLE(u)->can_fail) { + if (FLAGS_SET(a, UNIT_ATOM_ON_FAILURE) && !UNIT_VTABLE(u)->can_fail) { log_unit_warning(u, "Requested dependency OnFailure=%s ignored (%s units cannot fail).", other->id, unit_type_to_string(u->type)); return 0; } - if (d == UNIT_TRIGGERS && !UNIT_VTABLE(u)->can_trigger) + if (FLAGS_SET(a, UNIT_ATOM_TRIGGERS) && !UNIT_VTABLE(u)->can_trigger) return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), "Requested dependency Triggers=%s refused (%s units cannot trigger other units).", other->id, unit_type_to_string(u->type)); - if (d == UNIT_TRIGGERED_BY && !UNIT_VTABLE(other)->can_trigger) + if (FLAGS_SET(a, UNIT_ATOM_TRIGGERED_BY) && !UNIT_VTABLE(other)->can_trigger) return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), "Requested dependency TriggeredBy=%s refused (%s units cannot trigger other units).", other->id, unit_type_to_string(other->type)); - r = unit_add_dependency_hashmap(u->dependencies + d, other, mask, 0); + if (FLAGS_SET(a, UNIT_ATOM_IN_SLICE) && other->type != UNIT_SLICE) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), + "Requested dependency Slice=%s refused (%s is not a slice unit).", other->id, other->id); + if (FLAGS_SET(a, UNIT_ATOM_SLICE_OF) && u->type != UNIT_SLICE) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), + "Requested dependency SliceOf=%s refused (%s is not a slice unit).", other->id, u->id); + + if (FLAGS_SET(a, UNIT_ATOM_IN_SLICE) && !UNIT_HAS_CGROUP_CONTEXT(u)) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), + "Requested dependency Slice=%s refused (%s is not a cgroup unit).", other->id, u->id); + + if (FLAGS_SET(a, UNIT_ATOM_SLICE_OF) && !UNIT_HAS_CGROUP_CONTEXT(other)) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), + "Requested dependency SliceOf=%s refused (%s is not a cgroup unit).", other->id, other->id); + + r = unit_add_dependency_hashmap(&u->dependencies, d, other, mask, 0); if (r < 0) return r; - else if (r > 0) - noop = false; + noop = !r; if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID && inverse_table[d] != d) { - r = unit_add_dependency_hashmap(other->dependencies + inverse_table[d], u, 0, mask); + r = unit_add_dependency_hashmap(&other->dependencies, inverse_table[d], u, 0, mask); if (r < 0) return r; - else if (r > 0) + if (r) noop = false; } if (add_reference) { - r = unit_add_dependency_hashmap(u->dependencies + UNIT_REFERENCES, other, mask, 0); + r = unit_add_dependency_hashmap(&u->dependencies, UNIT_REFERENCES, other, mask, 0); if (r < 0) return r; - else if (r > 0) + if (r) noop = false; - r = unit_add_dependency_hashmap(other->dependencies + UNIT_REFERENCED_BY, u, 0, mask); + r = unit_add_dependency_hashmap(&other->dependencies, UNIT_REFERENCED_BY, u, 0, mask); if (r < 0) return r; - else if (r > 0) + if (r) noop = false; } if (!noop) unit_add_to_dbus_queue(u); + return 0; } @@ -3041,15 +3251,15 @@ reset: return r; } -int unit_set_slice(Unit *u, Unit *slice) { +int unit_set_slice(Unit *u, Unit *slice, UnitDependencyMask mask) { + int r; + assert(u); assert(slice); - /* 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. */ + /* 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_HAS_CGROUP_CONTEXT(u)) return -EOPNOTSUPP; @@ -3067,14 +3277,17 @@ int unit_set_slice(Unit *u, Unit *slice) { !unit_has_name(slice, SPECIAL_ROOT_SLICE)) return -EPERM; - if (UNIT_DEREF(u->slice) == slice) + if (UNIT_GET_SLICE(u) == slice) return 0; /* Disallow slice changes if @u is already bound to cgroups */ - if (UNIT_ISSET(u->slice) && u->cgroup_realized) + if (UNIT_GET_SLICE(u) && u->cgroup_realized) return -EBUSY; - unit_ref_set(&u->slice, u, slice); + r = unit_add_dependency(u, UNIT_IN_SLICE, slice, true, mask); + if (r < 0) + return r; + return 1; } @@ -3085,7 +3298,7 @@ int unit_set_default_slice(Unit *u) { assert(u); - if (UNIT_ISSET(u->slice)) + if (UNIT_GET_SLICE(u)) return 0; if (u->instance) { @@ -3124,16 +3337,18 @@ int unit_set_default_slice(Unit *u) { if (r < 0) return r; - return unit_set_slice(u, slice); + return unit_set_slice(u, slice, UNIT_DEPENDENCY_FILE); } const char *unit_slice_name(Unit *u) { + Unit *slice; assert(u); - if (!UNIT_ISSET(u->slice)) + slice = UNIT_GET_SLICE(u); + if (!slice) return NULL; - return UNIT_DEREF(u->slice)->id; + return slice->id; } int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { @@ -4443,7 +4658,6 @@ int unit_setup_exec_runtime(Unit *u) { ExecRuntime **rt; size_t offset; Unit *other; - void *v; int r; offset = UNIT_VTABLE(u)->exec_runtime_offset; @@ -4455,7 +4669,7 @@ int unit_setup_exec_runtime(Unit *u) { return 0; /* Try to get it from somebody else */ - HASHMAP_FOREACH_KEY(v, other, u->dependencies[UNIT_JOINS_NAMESPACE_OF]) { + UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_JOINS_NAMESPACE_OF) { r = exec_runtime_acquire(u->manager, NULL, other->id, false, rt); if (r == 1) return 1; @@ -4830,22 +5044,20 @@ int unit_fork_and_watch_rm_rf(Unit *u, char **paths, pid_t *ret_pid) { return 0; } -static void unit_update_dependency_mask(Unit *u, UnitDependency d, Unit *other, UnitDependencyInfo di) { - assert(u); - assert(d >= 0); - assert(d < _UNIT_DEPENDENCY_MAX); +static void unit_update_dependency_mask(Hashmap *deps, Unit *other, UnitDependencyInfo di) { + assert(deps); assert(other); - if (di.origin_mask == 0 && di.destination_mask == 0) { + if (di.origin_mask == 0 && di.destination_mask == 0) /* No bit set anymore, let's drop the whole entry */ - assert_se(hashmap_remove(u->dependencies[d], other)); - log_unit_debug(u, "lost dependency %s=%s", unit_dependency_to_string(d), other->id); - } else + assert_se(hashmap_remove(deps, other)); + else /* Mask was reduced, let's update the entry */ - assert_se(hashmap_update(u->dependencies[d], other, di.data) == 0); + assert_se(hashmap_update(deps, other, di.data) == 0); } void unit_remove_dependencies(Unit *u, UnitDependencyMask mask) { + Hashmap *deps; assert(u); /* Removes all dependencies u has on other units marked for ownership by 'mask'. */ @@ -4853,7 +5065,7 @@ void unit_remove_dependencies(Unit *u, UnitDependencyMask mask) { if (mask == 0) return; - for (UnitDependency d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + HASHMAP_FOREACH(deps, u->dependencies) { bool done; do { @@ -4862,26 +5074,29 @@ void unit_remove_dependencies(Unit *u, UnitDependencyMask mask) { done = true; - HASHMAP_FOREACH_KEY(di.data, other, u->dependencies[d]) { + HASHMAP_FOREACH_KEY(di.data, other, deps) { + Hashmap *other_deps; + if (FLAGS_SET(~mask, di.origin_mask)) continue; + di.origin_mask &= ~mask; - unit_update_dependency_mask(u, d, other, di); + unit_update_dependency_mask(deps, other, di); - /* We updated the dependency from our unit to the other unit now. But most dependencies - * imply a reverse dependency. Hence, let's delete that one too. For that we go through - * all dependency types on the other unit and delete all those which point to us and - * have the right mask set. */ + /* We updated the dependency from our unit to the other unit now. But most + * dependencies imply a reverse dependency. Hence, let's delete that one + * too. For that we go through all dependency types on the other unit and + * delete all those which point to us and have the right mask set. */ - for (UnitDependency q = 0; q < _UNIT_DEPENDENCY_MAX; q++) { + HASHMAP_FOREACH(other_deps, other->dependencies) { UnitDependencyInfo dj; - dj.data = hashmap_get(other->dependencies[q], u); + dj.data = hashmap_get(other_deps, u); if (FLAGS_SET(~mask, dj.destination_mask)) continue; - dj.destination_mask &= ~mask; - unit_update_dependency_mask(other, q, u, dj); + dj.destination_mask &= ~mask; + unit_update_dependency_mask(other_deps, u, dj); } unit_add_to_gc_queue(other); @@ -5589,3 +5804,44 @@ static const char* const collect_mode_table[_COLLECT_MODE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(collect_mode, CollectMode); + +Unit* unit_has_dependency(const Unit *u, UnitDependencyAtom atom, Unit *other) { + Unit *i; + + assert(u); + + /* Checks if the unit has a dependency on 'other' with the specified dependency atom. If 'other' is + * NULL checks if the unit has *any* dependency of that atom. Returns 'other' if found (or if 'other' + * is NULL the first entry found), or NULL if not found. */ + + UNIT_FOREACH_DEPENDENCY(i, u, atom) + if (!other || other == i) + return i; + + return NULL; +} + +int unit_get_dependency_array(const Unit *u, UnitDependencyAtom atom, Unit ***ret_array) { + _cleanup_free_ Unit **array = NULL; + size_t n = 0; + Unit *other; + + assert(u); + assert(ret_array); + + /* Gets a list of units matching a specific atom as array. This is useful when iterating through + * dependencies while modifying them: the array is an "atomic snapshot" of sorts, that can be read + * while the dependency table is continously updated. */ + + UNIT_FOREACH_DEPENDENCY(other, u, atom) { + if (!GREEDY_REALLOC(array, n + 1)) + return -ENOMEM; + + array[n++] = other; + } + + *ret_array = TAKE_PTR(array); + + assert(n <= INT_MAX); + return (int) n; +} diff --git a/src/core/unit.h b/src/core/unit.h index abd8aecc1a..d34d647845 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -102,6 +102,16 @@ typedef union UnitDependencyInfo { } _packed_; } UnitDependencyInfo; +/* Newer LLVM versions don't like implicit casts from large pointer types to smaller enums, hence let's add + * explicit type-safe helpers for that. */ +static inline UnitDependency UNIT_DEPENDENCY_FROM_PTR(const void *p) { + return PTR_TO_INT(p); +} + +static inline void* UNIT_DEPENDENCY_TO_PTR(UnitDependency d) { + return INT_TO_PTR(d); +} + #include "job.h" struct UnitRef { @@ -125,11 +135,13 @@ typedef struct Unit { Set *aliases; /* All the other names. */ - /* For each dependency type we maintain a Hashmap whose key is the Unit* object, and the value encodes why the - * dependency exists, using the UnitDependencyInfo type */ - Hashmap *dependencies[_UNIT_DEPENDENCY_MAX]; + /* For each dependency type we can look up another Hashmap with this, whose key is a Unit* object, + * and whose value encodes why the dependency exists, using the UnitDependencyInfo type. i.e. a + * Hashmap(UnitDependency β†’ Hashmap(Unit* β†’ UnitDependencyInfo)) */ + Hashmap *dependencies; - /* Similar, for RequiresMountsFor= path dependencies. The key is the path, the value the UnitDependencyInfo type */ + /* Similar, for RequiresMountsFor= path dependencies. The key is the path, the value the + * UnitDependencyInfo type */ Hashmap *requires_mounts_for; char *description; @@ -190,8 +202,6 @@ typedef struct Unit { dual_timestamp active_exit_timestamp; dual_timestamp inactive_enter_timestamp; - UnitRef slice; - /* Per type list */ LIST_FIELDS(Unit, units_by_type); @@ -219,9 +229,15 @@ typedef struct Unit { /* Target dependencies queue */ LIST_FIELDS(Unit, target_deps_queue); - /* Queue of units with StopWhenUnneeded set that shell be checked for clean-up. */ + /* Queue of units with StopWhenUnneeded= set that shall be checked for clean-up. */ LIST_FIELDS(Unit, stop_when_unneeded_queue); + /* Queue of units that have an Uphold= dependency from some other unit, and should be checked for starting */ + LIST_FIELDS(Unit, start_when_upheld_queue); + + /* Queue of units that have a BindTo= dependency on some other unit, and should possibly be shut down */ + LIST_FIELDS(Unit, stop_when_bound_queue); + /* PIDs we keep an eye on. Note that a unit might have many * more, but these are the ones we care enough about to * process SIGCHLD for */ @@ -250,8 +266,8 @@ typedef struct Unit { int success_action_exit_status, failure_action_exit_status; char *reboot_arg; - /* Make sure we never enter endless loops with the check unneeded logic, or the BindsTo= logic */ - RateLimit auto_stop_ratelimit; + /* Make sure we never enter endless loops with the StopWhenUnneeded=, BindsTo=, Uphold= logic */ + RateLimit auto_start_stop_ratelimit; /* Reference to a specific UID/GID */ uid_t ref_uid; @@ -324,7 +340,8 @@ typedef struct Unit { * ones which might have appeared. */ sd_event_source *rewatch_pids_event_source; - /* How to start OnFailure units */ + /* How to start OnSuccess=/OnFailure= units */ + JobMode on_success_job_mode; JobMode on_failure_job_mode; /* Tweaking the GC logic */ @@ -372,6 +389,8 @@ typedef struct Unit { bool in_cgroup_oom_queue:1; bool in_target_deps_queue:1; bool in_stop_when_unneeded_queue:1; + bool in_start_when_upheld_queue:1; + bool in_stop_when_bound_queue:1; bool sent_dbus_new_signal:1; @@ -683,8 +702,19 @@ static inline const UnitVTable* UNIT_VTABLE(const Unit *u) { #define UNIT_HAS_CGROUP_CONTEXT(u) (UNIT_VTABLE(u)->cgroup_context_offset > 0) #define UNIT_HAS_KILL_CONTEXT(u) (UNIT_VTABLE(u)->kill_context_offset > 0) +Unit* unit_has_dependency(const Unit *u, UnitDependencyAtom atom, Unit *other); +int unit_get_dependency_array(const Unit *u, UnitDependencyAtom atom, Unit ***ret_array); + +static inline Hashmap* unit_get_dependencies(Unit *u, UnitDependency d) { + return hashmap_get(u->dependencies, UNIT_DEPENDENCY_TO_PTR(d)); +} + static inline Unit* UNIT_TRIGGER(Unit *u) { - return hashmap_first_key(u->dependencies[UNIT_TRIGGERS]); + return unit_has_dependency(u, UNIT_ATOM_TRIGGERS, NULL); +} + +static inline Unit* UNIT_GET_SLICE(const Unit *u) { + return unit_has_dependency(u, UNIT_ATOM_IN_SLICE, NULL); } Unit* unit_new(Manager *m, size_t size); @@ -718,6 +748,8 @@ void unit_add_to_cleanup_queue(Unit *u); void unit_add_to_gc_queue(Unit *u); void unit_add_to_target_deps_queue(Unit *u); void unit_submit_to_stop_when_unneeded_queue(Unit *u); +void unit_submit_to_start_when_upheld_queue(Unit *u); +void unit_submit_to_stop_when_bound_queue(Unit *u); int unit_merge(Unit *u, Unit *other); int unit_merge_by_name(Unit *u, const char *other); @@ -727,7 +759,7 @@ Unit *unit_follow_merge(Unit *u) _pure_; int unit_load_fragment_and_dropin(Unit *u, bool fragment_required); int unit_load(Unit *unit); -int unit_set_slice(Unit *u, Unit *slice); +int unit_set_slice(Unit *u, Unit *slice, UnitDependencyMask mask); int unit_set_default_slice(Unit *u); const char *unit_description(Unit *u) _pure_; @@ -807,7 +839,7 @@ bool unit_will_restart(Unit *u); int unit_add_default_target_dependency(Unit *u, Unit *target); -void unit_start_on_failure(Unit *u); +void unit_start_on_failure(Unit *u, const char *dependency_name, UnitDependencyAtom atom, JobMode job_mode); void unit_trigger_notify(Unit *u); UnitFileState unit_get_unit_file_state(Unit *u); @@ -847,6 +879,8 @@ bool unit_type_supported(UnitType t); bool unit_is_pristine(Unit *u); bool unit_is_unneeded(Unit *u); +bool unit_is_upheld_by_active(Unit *u, Unit **ret_culprit); +bool unit_is_bound_by_inactive(Unit *u, Unit **ret_culprit); pid_t unit_control_pid(Unit *u); pid_t unit_main_pid(Unit *u); @@ -990,3 +1024,54 @@ int unit_thaw_vtable_common(Unit *u); const char* collect_mode_to_string(CollectMode m) _const_; CollectMode collect_mode_from_string(const char *s) _pure_; + +typedef struct UnitForEachDependencyData { + /* Stores state for the FOREACH macro below for iterating through all deps that have any of the + * specified dependency atom bits set */ + UnitDependencyAtom match_atom; + Hashmap *by_type, *by_unit; + void *current_type; + Iterator by_type_iterator, by_unit_iterator; + Unit **current_unit; +} UnitForEachDependencyData; + +/* Iterates through all dependencies that have a specific atom in the dependency type set. This tries to be + * smart: if the atom is unique, we'll directly go to right entry. Otherwise we'll iterate through the + * per-dependency type hashmap and match all dep that have the right atom set. */ +#define _UNIT_FOREACH_DEPENDENCY(other, u, ma, data) \ + for (UnitForEachDependencyData data = { \ + .match_atom = (ma), \ + .by_type = (u)->dependencies, \ + .by_type_iterator = ITERATOR_FIRST, \ + .current_unit = &(other), \ + }; \ + ({ \ + UnitDependency _dt = _UNIT_DEPENDENCY_INVALID; \ + bool _found; \ + \ + if (data.by_type && ITERATOR_IS_FIRST(data.by_type_iterator)) { \ + _dt = unit_dependency_from_unique_atom(data.match_atom); \ + if (_dt >= 0) { \ + data.by_unit = hashmap_get(data.by_type, UNIT_DEPENDENCY_TO_PTR(_dt)); \ + data.current_type = UNIT_DEPENDENCY_TO_PTR(_dt); \ + data.by_type = NULL; \ + _found = !!data.by_unit; \ + } \ + } \ + if (_dt < 0) \ + _found = hashmap_iterate(data.by_type, \ + &data.by_type_iterator, \ + (void**)&(data.by_unit), \ + (const void**) &(data.current_type)); \ + _found; \ + }); ) \ + if ((unit_dependency_to_atom(UNIT_DEPENDENCY_FROM_PTR(data.current_type)) & data.match_atom) != 0) \ + for (data.by_unit_iterator = ITERATOR_FIRST; \ + hashmap_iterate(data.by_unit, \ + &data.by_unit_iterator, \ + NULL, \ + (const void**) data.current_unit); ) + +/* Note: this matches deps that have *any* of the atoms specified in match_atom set */ +#define UNIT_FOREACH_DEPENDENCY(other, u, match_atom) \ + _UNIT_FOREACH_DEPENDENCY(other, u, match_atom, UNIQ_T(data, UNIQ)) diff --git a/src/test/test-cgroup-mask.c b/src/test/test-cgroup-mask.c index 3edc2754e1..19e159b9ff 100644 --- a/src/test/test-cgroup-mask.c +++ b/src/test/test-cgroup-mask.c @@ -70,13 +70,13 @@ static int test_cgroup_mask(void) { assert_se(manager_load_startable_unit_or_warn(m, "parent-deep.slice", NULL, &parent_deep) >= 0); assert_se(manager_load_startable_unit_or_warn(m, "nomem.slice", NULL, &nomem_parent) >= 0); assert_se(manager_load_startable_unit_or_warn(m, "nomemleaf.service", NULL, &nomem_leaf) >= 0); - assert_se(UNIT_DEREF(son->slice) == parent); - assert_se(UNIT_DEREF(daughter->slice) == parent); - assert_se(UNIT_DEREF(parent_deep->slice) == parent); - assert_se(UNIT_DEREF(grandchild->slice) == parent_deep); - assert_se(UNIT_DEREF(nomem_leaf->slice) == nomem_parent); - root = UNIT_DEREF(parent->slice); - assert_se(UNIT_DEREF(nomem_parent->slice) == root); + assert_se(UNIT_GET_SLICE(son) == parent); + assert_se(UNIT_GET_SLICE(daughter) == parent); + assert_se(UNIT_GET_SLICE(parent_deep) == parent); + assert_se(UNIT_GET_SLICE(grandchild) == parent_deep); + assert_se(UNIT_GET_SLICE(nomem_leaf) == nomem_parent); + root = UNIT_GET_SLICE(parent); + assert_se(UNIT_GET_SLICE(nomem_parent) == root); /* Verify per-unit cgroups settings. */ ASSERT_CGROUP_MASK_JOINED(unit_get_own_mask(son), CGROUP_MASK_CPU); diff --git a/src/test/test-cgroup-unit-default.c b/src/test/test-cgroup-unit-default.c index b03f6ff12e..225d138e41 100644 --- a/src/test/test-cgroup-unit-default.c +++ b/src/test/test-cgroup-unit-default.c @@ -91,28 +91,28 @@ static int test_default_memory_low(void) { assert_se(manager_load_startable_unit_or_warn(m, "dml.slice", NULL, &dml) >= 0); assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough.slice", NULL, &dml_passthrough) >= 0); - assert_se(UNIT_DEREF(dml_passthrough->slice) == dml); + assert_se(UNIT_GET_SLICE(dml_passthrough) == dml); assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-empty.service", NULL, &dml_passthrough_empty) >= 0); - assert_se(UNIT_DEREF(dml_passthrough_empty->slice) == dml_passthrough); + assert_se(UNIT_GET_SLICE(dml_passthrough_empty) == dml_passthrough); assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-dml.service", NULL, &dml_passthrough_set_dml) >= 0); - assert_se(UNIT_DEREF(dml_passthrough_set_dml->slice) == dml_passthrough); + assert_se(UNIT_GET_SLICE(dml_passthrough_set_dml) == dml_passthrough); assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-ml.service", NULL, &dml_passthrough_set_ml) >= 0); - assert_se(UNIT_DEREF(dml_passthrough_set_ml->slice) == dml_passthrough); + assert_se(UNIT_GET_SLICE(dml_passthrough_set_ml) == dml_passthrough); assert_se(manager_load_startable_unit_or_warn(m, "dml-override.slice", NULL, &dml_override) >= 0); - assert_se(UNIT_DEREF(dml_override->slice) == dml); + assert_se(UNIT_GET_SLICE(dml_override) == dml); assert_se(manager_load_startable_unit_or_warn(m, "dml-override-empty.service", NULL, &dml_override_empty) >= 0); - assert_se(UNIT_DEREF(dml_override_empty->slice) == dml_override); + assert_se(UNIT_GET_SLICE(dml_override_empty) == dml_override); assert_se(manager_load_startable_unit_or_warn(m, "dml-discard.slice", NULL, &dml_discard) >= 0); - assert_se(UNIT_DEREF(dml_discard->slice) == dml); + assert_se(UNIT_GET_SLICE(dml_discard) == dml); assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-empty.service", NULL, &dml_discard_empty) >= 0); - assert_se(UNIT_DEREF(dml_discard_empty->slice) == dml_discard); + assert_se(UNIT_GET_SLICE(dml_discard_empty) == dml_discard); assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-set-ml.service", NULL, &dml_discard_set_ml) >= 0); - assert_se(UNIT_DEREF(dml_discard_set_ml->slice) == dml_discard); + assert_se(UNIT_GET_SLICE(dml_discard_set_ml) == dml_discard); - root = UNIT_DEREF(dml->slice); - assert_se(!UNIT_ISSET(root->slice)); + assert_se(root = UNIT_GET_SLICE(dml)); + assert_se(!UNIT_GET_SLICE(root)); assert_se(unit_get_ancestor_memory_low(root) == CGROUP_LIMIT_MIN); diff --git a/src/test/test-engine.c b/src/test/test-engine.c index cd7cfd9f09..1039709b89 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -6,16 +6,75 @@ #include "bus-util.h" #include "manager.h" #include "rm-rf.h" +#include "service.h" +#include "special.h" #include "strv.h" #include "tests.h" -#include "service.h" +#include "unit-serialize.h" + +static void verify_dependency_atoms(void) { + UnitDependencyAtom combined = 0, multi_use_atoms = 0; + + /* Let's guarantee that our dependency type/atom translation tables are fully correct */ + + for (UnitDependency d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + UnitDependencyAtom a; + UnitDependency reverse; + bool has_superset = false; + + assert_se((a = unit_dependency_to_atom(d)) >= 0); + + for (UnitDependency t = 0; t < _UNIT_DEPENDENCY_MAX; t++) { + UnitDependencyAtom b; + + if (t == d) + continue; + + assert_se((b = unit_dependency_to_atom(t)) >= 0); + + if ((a & b) == a) { + has_superset = true; + break; + } + } + + reverse = unit_dependency_from_unique_atom(a); + assert_se(reverse == _UNIT_DEPENDENCY_INVALID || reverse >= 0); + + assert_se((reverse < 0) == has_superset); /* If one dependency type is a superset of another, + * then the reverse mapping is not unique, verify + * that. */ + + log_info("Verified dependency type: %s", unit_dependency_to_string(d)); + + multi_use_atoms |= combined & a; + combined |= a; + } + + /* Make sure all atoms are used, i.e. there's at least one dependency type that references it. */ + assert_se(combined == _UNIT_DEPENDENCY_ATOM_MAX); + + for (UnitDependencyAtom a = 1; a <= _UNIT_DEPENDENCY_ATOM_MAX; a <<= 1) { + + if (multi_use_atoms & a) { + /* If an atom is used by multiple dep types, then mapping the atom to a dependency is + * not unique and *must* fail */ + assert_se(unit_dependency_from_unique_atom(a) == _UNIT_DEPENDENCY_INVALID); + continue; + } + + /* If only a single dep type uses specific atom, let's guarantee our mapping table is + complete, and thus the atom can be mapped to the single dep type that is used. */ + assert_se(unit_dependency_from_unique_atom(a) >= 0); + } +} int main(int argc, char *argv[]) { _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; _cleanup_(manager_freep) Manager *m = NULL; Unit *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *g = NULL, - *h = NULL, *i = NULL, *a_conj = NULL, *unit_with_multiple_dashes = NULL; + *h = NULL, *i = NULL, *a_conj = NULL, *unit_with_multiple_dashes = NULL, *stub = NULL; Job *j; int r; @@ -122,37 +181,83 @@ int main(int argc, char *argv[]) { assert_se(manager_add_job(m, JOB_START, a_conj, JOB_REPLACE, NULL, NULL, &j) == -EDEADLK); manager_dump_jobs(m, stdout, "\t"); - assert_se(!hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], b)); - assert_se(!hashmap_get(b->dependencies[UNIT_RELOAD_PROPAGATED_FROM], a)); - assert_se(!hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], c)); - assert_se(!hashmap_get(c->dependencies[UNIT_RELOAD_PROPAGATED_FROM], a)); + assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + assert_se(!hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); assert_se(unit_add_dependency(a, UNIT_PROPAGATES_RELOAD_TO, b, true, UNIT_DEPENDENCY_UDEV) == 0); assert_se(unit_add_dependency(a, UNIT_PROPAGATES_RELOAD_TO, c, true, UNIT_DEPENDENCY_PROC_SWAP) == 0); - assert_se(hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], b)); - assert_se(hashmap_get(b->dependencies[UNIT_RELOAD_PROPAGATED_FROM], a)); - assert_se(hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], c)); - assert_se(hashmap_get(c->dependencies[UNIT_RELOAD_PROPAGATED_FROM], a)); + assert_se(hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + assert_se(hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + assert_se(hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + assert_se(hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); unit_remove_dependencies(a, UNIT_DEPENDENCY_UDEV); - assert_se(!hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], b)); - assert_se(!hashmap_get(b->dependencies[UNIT_RELOAD_PROPAGATED_FROM], a)); - assert_se(hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], c)); - assert_se(hashmap_get(c->dependencies[UNIT_RELOAD_PROPAGATED_FROM], a)); + assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + assert_se(hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + assert_se(hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); unit_remove_dependencies(a, UNIT_DEPENDENCY_PROC_SWAP); - assert_se(!hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], b)); - assert_se(!hashmap_get(b->dependencies[UNIT_RELOAD_PROPAGATED_FROM], a)); - assert_se(!hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], c)); - assert_se(!hashmap_get(c->dependencies[UNIT_RELOAD_PROPAGATED_FROM], a)); + assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + assert_se(!hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); assert_se(manager_load_unit(m, "unit-with-multiple-dashes.service", NULL, NULL, &unit_with_multiple_dashes) >= 0); assert_se(strv_equal(unit_with_multiple_dashes->documentation, STRV_MAKE("man:test", "man:override2", "man:override3"))); assert_se(streq_ptr(unit_with_multiple_dashes->description, "override4")); + /* Now merge a synthetic unit into the existing one */ + assert_se(unit_new_for_name(m, sizeof(Service), "merged.service", &stub) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_AFTER, SPECIAL_BASIC_TARGET, true, UNIT_DEPENDENCY_FILE) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_AFTER, "quux.target", true, UNIT_DEPENDENCY_FILE) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_AFTER, SPECIAL_ROOT_SLICE, true, UNIT_DEPENDENCY_FILE) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_REQUIRES, "non-existing.mount", true, UNIT_DEPENDENCY_FILE) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_ON_FAILURE, "non-existing-on-failure.target", true, UNIT_DEPENDENCY_FILE) >= 0); + + log_info("/* Merging a+stub, dumps before */"); + unit_dump(a, stderr, NULL); + unit_dump(stub, stderr, NULL); + assert_se(unit_merge(a, stub) >= 0); + log_info("/* Dump of merged a+stub */"); + unit_dump(a, stderr, NULL); + + assert_se(unit_has_dependency(a, UNIT_ATOM_AFTER, manager_get_unit(m, SPECIAL_BASIC_TARGET))); + assert_se(unit_has_dependency(a, UNIT_ATOM_AFTER, manager_get_unit(m, "quux.target"))); + assert_se(unit_has_dependency(a, UNIT_ATOM_AFTER, manager_get_unit(m, SPECIAL_ROOT_SLICE))); + assert_se(unit_has_dependency(a, UNIT_ATOM_PULL_IN_START, manager_get_unit(m, "non-existing.mount"))); + assert_se(unit_has_dependency(a, UNIT_ATOM_RETROACTIVE_START_REPLACE, manager_get_unit(m, "non-existing.mount"))); + assert_se(unit_has_dependency(a, UNIT_ATOM_ON_FAILURE, manager_get_unit(m, "non-existing-on-failure.target"))); + assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE, manager_get_unit(m, "basic.target"))); + assert_se(!unit_has_dependency(a, UNIT_ATOM_PROPAGATES_RELOAD_TO, manager_get_unit(m, "non-existing-on-failure.target"))); + + assert_se(unit_has_name(a, "a.service")); + assert_se(unit_has_name(a, "merged.service")); + + unsigned mm = 1; + Unit *other; + + UNIT_FOREACH_DEPENDENCY(other, a, UNIT_ATOM_AFTER) { + mm *= unit_has_name(other, SPECIAL_BASIC_TARGET) ? 3 : 1; + mm *= unit_has_name(other, "quux.target") ? 5 : 1; + mm *= unit_has_name(other, SPECIAL_ROOT_SLICE) ? 7 : 1; + } + + UNIT_FOREACH_DEPENDENCY(other, a, UNIT_ATOM_ON_FAILURE) + mm *= unit_has_name(other, "non-existing-on-failure.target") ? 11 : 1; + + UNIT_FOREACH_DEPENDENCY(other, a, UNIT_ATOM_PULL_IN_START) + mm *= unit_has_name(other, "non-existing.mount") ? 13 : 1; + + assert_se(mm == 3U*5U*7U*11U*13U); + + verify_dependency_atoms(); + return 0; } diff --git a/test/TEST-57-ONSUCCESS-UPHOLD/Makefile b/test/TEST-57-ONSUCCESS-UPHOLD/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-57-ONSUCCESS-UPHOLD/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-57-ONSUCCESS-UPHOLD/test.sh b/test/TEST-57-ONSUCCESS-UPHOLD/test.sh new file mode 100755 index 0000000000..145c88fcd8 --- /dev/null +++ b/test/TEST-57-ONSUCCESS-UPHOLD/test.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -e + +TEST_DESCRIPTION="test OnSuccess= + Uphold= + PropagatesStopTo= + BindsTo=" +. $TEST_BASE_DIR/test-functions + +do_test "$@" 57 diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service index 73e1707c5c..1ce6967c65 100644 --- a/test/fuzz/fuzz-unit-file/directives.service +++ b/test/fuzz/fuzz-unit-file/directives.service @@ -76,10 +76,13 @@ JoinsNamespaceOf= OnFailure= OnFailureIsolate= OnFailureJobMode= +OnSuccess= +OnSuccessJobMode= PartOf= PropagateReloadFrom= PropagateReloadTo= PropagatesReloadTo= +PropagatesStopTo= RebootArgument= RefuseManualStart= RefuseManualStop= @@ -97,8 +100,10 @@ StartLimitBurst= StartLimitInterval= StartLimitIntervalSec= StopWhenUnneeded= +StopPropagatedFrom= SuccessAction= SuccessActionExitStatus= +Upholds= Wants= [Install] Alias= diff --git a/test/units/testsuite-57-binds-to.service b/test/units/testsuite-57-binds-to.service new file mode 100644 index 0000000000..e4b6ee3c29 --- /dev/null +++ b/test/units/testsuite-57-binds-to.service @@ -0,0 +1,8 @@ +[Unit] +Description=Unit with BindsTo= +BindsTo=testsuite-57-bound-by.service +After=testsuite-57-bound-by.service + +[Service] +ExecStart=/bin/sleep infinity +ExecStopPost=systemctl kill --kill-who=main -sRTMIN+1 testsuite-57.service diff --git a/test/units/testsuite-57-bound-by.service b/test/units/testsuite-57-bound-by.service new file mode 100644 index 0000000000..df66a8fe84 --- /dev/null +++ b/test/units/testsuite-57-bound-by.service @@ -0,0 +1,5 @@ +[Unit] +Description=Unit with BoundBy= + +[Service] +ExecStart=/bin/sleep 0.7 diff --git a/test/units/testsuite-57-fail.service b/test/units/testsuite-57-fail.service new file mode 100644 index 0000000000..07d8ddde9d --- /dev/null +++ b/test/units/testsuite-57-fail.service @@ -0,0 +1,6 @@ +[Unit] +Description=Failing unit +OnFailure=testsuite-57-uphold.service + +[Service] +ExecStart=/bin/false diff --git a/test/units/testsuite-57-prop-stop-one.service b/test/units/testsuite-57-prop-stop-one.service new file mode 100644 index 0000000000..cdca7d4451 --- /dev/null +++ b/test/units/testsuite-57-prop-stop-one.service @@ -0,0 +1,9 @@ +[Unit] +Description=Stop Propagation Receiver +Wants=testsuite-57-prop-stop-two.service +After=testsuite-57-prop-stop-two.service +StopPropagatedFrom=testsuite-57-prop-stop-two.service + +[Service] +ExecStart=/bin/sleep infinity +ExecStopPost=systemctl kill --kill-who=main -sUSR2 testsuite-57.service diff --git a/test/units/testsuite-57-prop-stop-two.service b/test/units/testsuite-57-prop-stop-two.service new file mode 100644 index 0000000000..a462faf353 --- /dev/null +++ b/test/units/testsuite-57-prop-stop-two.service @@ -0,0 +1,5 @@ +[Unit] +Description=Stop Propagation Sender + +[Service] +ExecStart=/bin/sleep 1.5 diff --git a/test/units/testsuite-57-short-lived.service b/test/units/testsuite-57-short-lived.service new file mode 100644 index 0000000000..7fa268247f --- /dev/null +++ b/test/units/testsuite-57-short-lived.service @@ -0,0 +1,10 @@ +[Unit] +Description=Shortlived Unit +StopWhenUnneeded=yes + +# Bump up the start limit logic, so that we can be restarted frequently enough +StartLimitBurst=15 +StartLimitIntervalSec=1h + +[Service] +ExecStart=/usr/lib/systemd/tests/testdata/units/testsuite-57-short-lived.sh diff --git a/test/units/testsuite-57-short-lived.sh b/test/units/testsuite-57-short-lived.sh new file mode 100755 index 0000000000..8c48d3a2d5 --- /dev/null +++ b/test/units/testsuite-57-short-lived.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -ex + +if [ -f /tmp/testsuite-57.counter ] ; then + read -r counter < /tmp/testsuite-57.counter + counter=$(("$counter" + 1)) +else + counter=0 +fi + +echo "$counter" > /tmp/testsuite-57.counter + +if [ "$counter" -eq 5 ] ; then + systemctl kill --kill-who=main -sUSR1 testsuite-57.service +fi + +exec sleep 1.5 diff --git a/test/units/testsuite-57-success.service b/test/units/testsuite-57-success.service new file mode 100644 index 0000000000..ac1071fbba --- /dev/null +++ b/test/units/testsuite-57-success.service @@ -0,0 +1,6 @@ +[Unit] +Description=Succeeding unit +OnSuccess=testsuite-57-fail.service + +[Service] +ExecStart=/bin/true diff --git a/test/units/testsuite-57-uphold.service b/test/units/testsuite-57-uphold.service new file mode 100644 index 0000000000..d2b3828375 --- /dev/null +++ b/test/units/testsuite-57-uphold.service @@ -0,0 +1,6 @@ +[Unit] +Description=Upholding Unit +Upholds=testsuite-57-short-lived.service + +[Service] +ExecStart=/bin/sleep infinity diff --git a/test/units/testsuite-57.service b/test/units/testsuite-57.service new file mode 100644 index 0000000000..cab23afb44 --- /dev/null +++ b/test/units/testsuite-57.service @@ -0,0 +1,7 @@ +[Unit] +Description=TEST-57-ONSUCCESS-UPHOLD + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-57.sh b/test/units/testsuite-57.sh new file mode 100755 index 0000000000..5db82b8437 --- /dev/null +++ b/test/units/testsuite-57.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +set -eux +set -o pipefail + +systemd-analyze log-level debug +systemd-analyze log-target journal + +# Idea is this: +# 1. we start testsuite-57-success.service +# 2. which through OnSuccess= starts testsuite-57-fail.service, +# 3. which through OnFailure= starts testsuite-57-uphold.service, +# 4. which through Uphold= starts/keeps testsuite-57-short-lived.service running, +# 5. which will sleep 1s when invoked, and on the 5th invocation send us a SIGUSR1 +# 6. once we got that we finish cleanly + +sigusr1=0 +trap sigusr1=1 SIGUSR1 + +systemctl start testsuite-57-success.service + +while [ "$sigusr1" -eq 0 ] ; do + sleep .5 +done + +systemctl stop testsuite-57-uphold.service + +# Idea is this: +# 1. we start testsuite-57-prop-stop-one.service +# 2. which through Wants=/After= pulls in testsuite-57-prop-stop-two.service as well +# 3. testsuite-57-prop-stop-one.service then sleeps indefinitely +# 4. testsuite-57-prop-stop-two.service sleeps a short time and exits +# 5. the StopPropagatedFrom= dependency between the two should ensure *both* will exit as result +# 6. an ExecStopPost= line on testsuite-57-prop-stop-one.service will send us a SIGUSR2 +# 7. once we got that we finish cleanly + +sigusr2=0 +trap sigusr2=1 SIGUSR2 + +systemctl start testsuite-57-prop-stop-one.service + +while [ "$sigusr2" -eq 0 ] ; do + sleep .5 +done + + +# Idea is this: +# 1. we start testsuite-57-binds-to.service +# 2. which through BindsTo=/After= pulls in testsuite-57-bound-by.service as well +# 3. testsuite-57-bound-by.service suddenly dies +# 4. testsuite-57-binds-to.service should then also be pulled down (it otherwise just hangs) +# 6. an ExecStopPost= line on testsuite-57-binds-to.service will send us a SIGRTMIN1+1 +# 7. once we got that we finish cleanly + +sigrtmin1=0 +trap sigrtmin1=1 SIGRTMIN+1 + +systemctl start testsuite-57-binds-to.service + +while [ "$sigrtmin1" -eq 0 ] ; do + sleep .5 +done + +systemd-analyze log-level info + +echo OK >/testok + +exit 0