diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index d603ec9744..fcd1f914a8 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -856,16 +856,18 @@ JoinsNamespaceOf= For units that start processes (such as service units), lists one or more other units - whose network and/or temporary file namespace to join. This only applies to unit types which support - the PrivateNetwork=, NetworkNamespacePath=, + whose network and/or temporary file namespace to join. If this is specified on a unit (say, a.service + has JoinsNamespaceOf=b.service), then this the inverse dependency + (JoinsNamespaceOf=a.service for b.service) is implied. This only applies to unit + types which support the PrivateNetwork=, NetworkNamespacePath=, PrivateIPC=, IPCNamespacePath=, and PrivateTmp= directives (see systemd.exec5 for details). If a unit that has this setting set is started, its processes will see the same /tmp/, /var/tmp/, IPC namespace and network namespace as - one listed unit that is started. If multiple listed units are already started, it is not defined - which namespace is joined. Note that this setting only has an effect if - PrivateNetwork=/NetworkNamespacePath=, + one listed unit that is started. If multiple listed units are already started and these do not share + their namespace, then it is not defined which namespace is joined. Note that this setting only has an + effect if PrivateNetwork=/NetworkNamespacePath=, PrivateIPC=/IPCNamespacePath= and/or PrivateTmp= is enabled for both the unit that joins the namespace and the unit whose namespace is joined. diff --git a/src/core/unit.c b/src/core/unit.c index 6c2682f0aa..90f87a95f5 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1053,50 +1053,9 @@ static int unit_per_dependency_type_hashmap_update( 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) { Hashmap *deps; void *dt; /* Actually of type UnitDependency, except that we don't bother casting it here, @@ -3104,11 +3063,38 @@ bool unit_job_is_applicable(Unit *u, JobType j) { } } -int unit_add_dependency( +static Hashmap *unit_get_dependency_hashmap_per_type(Unit *u, UnitDependency d) { + Hashmap *deps; + + assert(u); + assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); + + deps = hashmap_get(u->dependencies, UNIT_DEPENDENCY_TO_PTR(d)); + if (!deps) { + _cleanup_hashmap_free_ Hashmap *h = NULL; + + h = hashmap_new(NULL); + if (!h) + return NULL; + + if (hashmap_ensure_put(&u->dependencies, NULL, UNIT_DEPENDENCY_TO_PTR(d), h) < 0) + return NULL; + + deps = TAKE_PTR(h); + } + + return deps; +} + +typedef enum NotifyDependencyFlags { + NOTIFY_DEPENDENCY_UPDATE_FROM = 1 << 0, + NOTIFY_DEPENDENCY_UPDATE_TO = 1 << 1, +} NotifyDependencyFlags; + +static int unit_add_dependency_impl( Unit *u, UnitDependency d, Unit *other, - bool add_reference, UnitDependencyMask mask) { static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = { @@ -3144,12 +3130,78 @@ int unit_add_dependency( [UNIT_IN_SLICE] = UNIT_SLICE_OF, [UNIT_SLICE_OF] = UNIT_IN_SLICE, }; + + Hashmap *u_deps, *other_deps; + UnitDependencyInfo u_info, u_info_old, other_info, other_info_old; + NotifyDependencyFlags flags = 0; + int r; + + assert(u); + assert(other); + assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); + assert(inverse_table[d] >= 0 && inverse_table[d] < _UNIT_DEPENDENCY_MAX); + assert(mask > 0 && mask < _UNIT_DEPENDENCY_MASK_FULL); + + /* Ensure the following two hashmaps for each unit exist: + * - the top-level dependency hashmap that maps UnitDependency → Hashmap(Unit* → UnitDependencyInfo), + * - the inner hashmap, that maps Unit* → UnitDependencyInfo, for the specified dependency type. */ + u_deps = unit_get_dependency_hashmap_per_type(u, d); + if (!u_deps) + return -ENOMEM; + + other_deps = unit_get_dependency_hashmap_per_type(other, inverse_table[d]); + if (!other_deps) + return -ENOMEM; + + /* Save the original dependency info. */ + u_info.data = u_info_old.data = hashmap_get(u_deps, other); + other_info.data = other_info_old.data = hashmap_get(other_deps, u); + + /* Update dependency info. */ + u_info.origin_mask |= mask; + other_info.destination_mask |= mask; + + /* Save updated dependency info. */ + if (u_info.data != u_info_old.data) { + r = hashmap_replace(u_deps, other, u_info.data); + if (r < 0) + return r; + + flags = NOTIFY_DEPENDENCY_UPDATE_FROM; + } + + if (other_info.data != other_info_old.data) { + r = hashmap_replace(other_deps, u, other_info.data); + if (r < 0) { + if (u_info.data != u_info_old.data) { + /* Restore the old dependency. */ + if (u_info_old.data) + (void) hashmap_update(u_deps, other, u_info_old.data); + else + hashmap_remove(u_deps, other); + } + return r; + } + + flags |= NOTIFY_DEPENDENCY_UPDATE_TO; + } + + return flags; +} + +int unit_add_dependency( + Unit *u, + UnitDependency d, + Unit *other, + bool add_reference, + UnitDependencyMask mask) { + 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 notify, notify_other = false; + NotifyDependencyFlags notify_flags; assert(u); assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); @@ -3205,36 +3257,24 @@ int unit_add_dependency( 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); + r = unit_add_dependency_impl(u, d, other, mask); if (r < 0) return r; - notify = r > 0; - - if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID && inverse_table[d] != d) { - r = unit_add_dependency_hashmap(&other->dependencies, inverse_table[d], u, 0, mask); - if (r < 0) - return r; - notify_other = r > 0; - } + notify_flags = r; if (add_reference) { - r = unit_add_dependency_hashmap(&u->dependencies, UNIT_REFERENCES, other, mask, 0); + r = unit_add_dependency_impl(u, UNIT_REFERENCES, other, mask); if (r < 0) return r; - notify = notify || r > 0; - - r = unit_add_dependency_hashmap(&other->dependencies, UNIT_REFERENCED_BY, u, 0, mask); - if (r < 0) - return r; - notify_other = notify_other || r > 0; + notify_flags |= r; } - if (notify) + if (FLAGS_SET(notify_flags, NOTIFY_DEPENDENCY_UPDATE_FROM)) unit_add_to_dbus_queue(u); - if (notify_other) + if (FLAGS_SET(notify_flags, NOTIFY_DEPENDENCY_UPDATE_TO)) unit_add_to_dbus_queue(other); - return notify || notify_other; + return notify_flags != 0; } int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference, UnitDependencyMask mask) { @@ -4890,6 +4930,7 @@ int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask) int unit_setup_exec_runtime(Unit *u) { _cleanup_(exec_shared_runtime_unrefp) ExecSharedRuntime *esr = NULL; _cleanup_(dynamic_creds_unrefp) DynamicCreds *dcreds = NULL; + _cleanup_set_free_ Set *units = NULL; ExecRuntime **rt; ExecContext *ec; size_t offset; @@ -4907,8 +4948,12 @@ int unit_setup_exec_runtime(Unit *u) { ec = unit_get_exec_context(u); assert(ec); + r = unit_get_transitive_dependency_set(u, UNIT_ATOM_JOINS_NAMESPACE_OF, &units); + if (r < 0) + return r; + /* Try to get it from somebody else */ - UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_JOINS_NAMESPACE_OF) { + SET_FOREACH(other, units) { r = exec_shared_runtime_acquire(u->manager, NULL, other->id, false, &esr); if (r < 0) return r; @@ -6113,6 +6158,33 @@ int unit_get_dependency_array(const Unit *u, UnitDependencyAtom atom, Unit ***re return (int) n; } +int unit_get_transitive_dependency_set(Unit *u, UnitDependencyAtom atom, Set **ret) { + _cleanup_set_free_ Set *units = NULL, *queue = NULL; + Unit *other; + int r; + + assert(u); + assert(ret); + + /* Similar to unit_get_dependency_array(), but also search the same dependency in other units. */ + + do { + UNIT_FOREACH_DEPENDENCY(other, u, atom) { + r = set_ensure_put(&units, NULL, other); + if (r < 0) + return r; + if (r == 0) + continue; + r = set_ensure_put(&queue, NULL, other); + if (r < 0) + return r; + } + } while ((u = set_steal_first(queue))); + + *ret = TAKE_PTR(units); + return 0; +} + const ActivationDetailsVTable * const activation_details_vtable[_UNIT_TYPE_MAX] = { [UNIT_PATH] = &activation_details_path_vtable, [UNIT_TIMER] = &activation_details_timer_vtable, diff --git a/src/core/unit.h b/src/core/unit.h index 09af8915dc..f2d4fd6a4b 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -810,6 +810,7 @@ static inline const UnitVTable* UNIT_VTABLE(const Unit *u) { Unit* unit_has_dependency(const Unit *u, UnitDependencyAtom atom, Unit *other); int unit_get_dependency_array(const Unit *u, UnitDependencyAtom atom, Unit ***ret_array); +int unit_get_transitive_dependency_set(Unit *u, UnitDependencyAtom atom, Set **ret); static inline Hashmap* unit_get_dependencies(Unit *u, UnitDependency d) { return hashmap_get(u->dependencies, UNIT_DEPENDENCY_TO_PTR(d)); diff --git a/test/testsuite-23.units/testsuite-23-joins-namespace-of-1.service b/test/testsuite-23.units/testsuite-23-joins-namespace-of-1.service new file mode 100644 index 0000000000..9919a9fa82 --- /dev/null +++ b/test/testsuite-23.units/testsuite-23-joins-namespace-of-1.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Service] +Type=notify +NotifyAccess=all +MountAPIVFS=yes +PrivateTmp=yes +ExecStart=/bin/bash -c 'touch /tmp/shared-private-file && systemd-notify --ready && sleep infinity' diff --git a/test/testsuite-23.units/testsuite-23-joins-namespace-of-2.service b/test/testsuite-23.units/testsuite-23-joins-namespace-of-2.service new file mode 100644 index 0000000000..36b4c272fd --- /dev/null +++ b/test/testsuite-23.units/testsuite-23-joins-namespace-of-2.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +JoinsNamespaceOf=testsuite-23-joins-namespace-of-1.service + +[Service] +Type=oneshot +MountAPIVFS=yes +PrivateTmp=yes +ExecStart=test -e /tmp/shared-private-file +ExecStart=touch /tmp/hoge diff --git a/test/testsuite-23.units/testsuite-23-joins-namespace-of-3.service b/test/testsuite-23.units/testsuite-23-joins-namespace-of-3.service new file mode 100644 index 0000000000..9094445020 --- /dev/null +++ b/test/testsuite-23.units/testsuite-23-joins-namespace-of-3.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +JoinsNamespaceOf=testsuite-23-joins-namespace-of-1.service + +[Service] +Type=oneshot +MountAPIVFS=yes +PrivateTmp=yes +ExecStart=test -e /tmp/shared-private-file +ExecStart=test -e /tmp/hoge diff --git a/test/testsuite-23.units/testsuite-23-joins-namespace-of-4.service b/test/testsuite-23.units/testsuite-23-joins-namespace-of-4.service new file mode 100644 index 0000000000..5e823a1778 --- /dev/null +++ b/test/testsuite-23.units/testsuite-23-joins-namespace-of-4.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +JoinsNamespaceOf=testsuite-23-joins-namespace-of-5.service + +[Service] +Type=notify +NotifyAccess=all +MountAPIVFS=yes +PrivateTmp=yes +ExecStart=/bin/bash -c 'touch /tmp/shared-private-file && systemd-notify --ready && sleep infinity' diff --git a/test/testsuite-23.units/testsuite-23-joins-namespace-of-5.service b/test/testsuite-23.units/testsuite-23-joins-namespace-of-5.service new file mode 100644 index 0000000000..c3d316bfa2 --- /dev/null +++ b/test/testsuite-23.units/testsuite-23-joins-namespace-of-5.service @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Service] +Type=oneshot +MountAPIVFS=yes +PrivateTmp=yes +ExecStart=test -e /tmp/shared-private-file diff --git a/test/testsuite-23.units/testsuite-23-joins-namespace-of-6.service b/test/testsuite-23.units/testsuite-23-joins-namespace-of-6.service new file mode 100644 index 0000000000..bbbfd7c67d --- /dev/null +++ b/test/testsuite-23.units/testsuite-23-joins-namespace-of-6.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +JoinsNamespaceOf=testsuite-23-joins-namespace-of-8.service + +[Service] +Type=notify +NotifyAccess=all +MountAPIVFS=yes +PrivateTmp=yes +ExecStart=/bin/bash -c 'touch /tmp/shared-private-file-x && systemd-notify --ready && sleep infinity' diff --git a/test/testsuite-23.units/testsuite-23-joins-namespace-of-7.service b/test/testsuite-23.units/testsuite-23-joins-namespace-of-7.service new file mode 100644 index 0000000000..60c083a3f4 --- /dev/null +++ b/test/testsuite-23.units/testsuite-23-joins-namespace-of-7.service @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +JoinsNamespaceOf=testsuite-23-joins-namespace-of-8.service + +[Service] +Type=oneshot +MountAPIVFS=yes +PrivateTmp=yes +ExecStart=test -e /tmp/shared-private-file-x +ExecStart=test ! -e /tmp/shared-private-file-y +ExecStart=touch /tmp/hoge diff --git a/test/testsuite-23.units/testsuite-23-joins-namespace-of-8.service b/test/testsuite-23.units/testsuite-23-joins-namespace-of-8.service new file mode 100644 index 0000000000..dac1cea7bd --- /dev/null +++ b/test/testsuite-23.units/testsuite-23-joins-namespace-of-8.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Service] +Type=notify +NotifyAccess=all +MountAPIVFS=yes +PrivateTmp=yes +ExecStartPre=test -e /tmp/shared-private-file-x +ExecStartPre=test -e /tmp/hoge +ExecStart=/bin/bash -c 'touch /tmp/shared-private-file-y && systemd-notify --ready && sleep infinity' diff --git a/test/testsuite-23.units/testsuite-23-joins-namespace-of-9.service b/test/testsuite-23.units/testsuite-23-joins-namespace-of-9.service new file mode 100644 index 0000000000..6c64873b24 --- /dev/null +++ b/test/testsuite-23.units/testsuite-23-joins-namespace-of-9.service @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +JoinsNamespaceOf=testsuite-23-joins-namespace-of-8.service + +[Service] +Type=oneshot +MountAPIVFS=yes +PrivateTmp=yes +ExecStart=test -e /tmp/shared-private-file-x +ExecStart=test -e /tmp/shared-private-file-y +ExecStart=test -e /tmp/hoge diff --git a/test/units/testsuite-23.JoinsNamespaceOf.sh b/test/units/testsuite-23.JoinsNamespaceOf.sh new file mode 100755 index 0000000000..68ba465072 --- /dev/null +++ b/test/units/testsuite-23.JoinsNamespaceOf.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +set -eux +set -o pipefail + +# Test JoinsNamespaceOf= with PrivateTmp=yes + +systemd-analyze log-level debug +systemd-analyze log-target journal + +# simple case +systemctl start testsuite-23-joins-namespace-of-1.service +systemctl start testsuite-23-joins-namespace-of-2.service +systemctl start testsuite-23-joins-namespace-of-3.service +systemctl stop testsuite-23-joins-namespace-of-1.service + +# inverse dependency +systemctl start testsuite-23-joins-namespace-of-4.service +systemctl start testsuite-23-joins-namespace-of-5.service +systemctl stop testsuite-23-joins-namespace-of-4.service + +# transitive dependency +systemctl start testsuite-23-joins-namespace-of-6.service +systemctl start testsuite-23-joins-namespace-of-7.service +systemctl start testsuite-23-joins-namespace-of-8.service +systemctl start testsuite-23-joins-namespace-of-9.service +systemctl stop testsuite-23-joins-namespace-of-6.service +systemctl stop testsuite-23-joins-namespace-of-8.service + +systemd-analyze log-level info