diff --git a/src/basic/strv.c b/src/basic/strv.c index d817140cc98..1eea73fa7b7 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -1063,6 +1063,23 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) { return 0; } +void string_strv_hashmap_remove(Hashmap *h, const char *key, const char *value) { + assert(key); + + if (value) { + char **l = hashmap_get(h, key); + if (!l) + return; + + strv_remove(l, value); + if (!strv_isempty(l)) + return; + } + + _unused_ _cleanup_free_ char *key_free = NULL; + strv_free(hashmap_remove2(h, key, (void**) &key_free)); +} + static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const char *value) { char **l; int r; diff --git a/src/basic/strv.h b/src/basic/strv.h index 86ba06f835a..5cdc801f35a 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -258,6 +258,10 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space); #define strv_free_and_replace(a, b) \ free_and_replace_full(a, b, strv_free) +void string_strv_hashmap_remove(Hashmap *h, const char *key, const char *value); +static inline void string_strv_ordered_hashmap_remove(OrderedHashmap *h, const char *key, const char *value) { + string_strv_hashmap_remove(PLAIN_HASHMAP(h), key, value); +} int _string_strv_hashmap_put(Hashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS); int _string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value HASHMAP_DEBUG_PARAMS); #define string_strv_hashmap_put(h, k, v) _string_strv_hashmap_put(h, k, v HASHMAP_DEBUG_SRC_ARGS) diff --git a/src/core/unit.c b/src/core/unit.c index 7685ab1996b..e6c5a45dcf2 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -646,13 +646,11 @@ static void unit_clear_dependencies(Unit *u) { static void unit_remove_transient(Unit *u) { assert(u); + assert(u->manager); if (!u->transient) return; - if (u->fragment_path) - (void) unlink(u->fragment_path); - STRV_FOREACH(i, u->dropin_paths) { _cleanup_free_ char *p = NULL, *pp = NULL; @@ -669,6 +667,17 @@ static void unit_remove_transient(Unit *u) { (void) unlink(*i); (void) rmdir(p); } + + if (u->fragment_path) { + (void) unlink(u->fragment_path); + (void) unit_file_remove_from_name_map( + &u->manager->lookup_paths, + &u->manager->unit_cache_timestamp_hash, + &u->manager->unit_id_map, + &u->manager->unit_name_map, + &u->manager->unit_path_cache, + u->fragment_path); + } } static void unit_free_mounts_for(Unit *u) { diff --git a/src/shared/unit-file.c b/src/shared/unit-file.c index 5bb580285cc..01e10c596d6 100644 --- a/src/shared/unit-file.c +++ b/src/shared/unit-file.c @@ -616,6 +616,41 @@ int unit_file_build_name_map( return 1; } +int unit_file_remove_from_name_map( + const LookupPaths *lp, + uint64_t *cache_timestamp_hash, + Hashmap **unit_ids_map, + Hashmap **unit_names_map, + Set **path_cache, + const char *path) { + + int r; + + assert(path); + + /* This assumes the specified path is already removed, and drops the relevant entries from the maps. */ + + /* If one of the lookup paths we are monitoring is already changed, let's rebuild the map. Then, the + * new map should not contain entries relevant to the specified path. */ + r = unit_file_build_name_map(lp, cache_timestamp_hash, unit_ids_map, unit_names_map, path_cache); + if (r != 0) + return r; + + /* If not, drop the relevant entries. */ + + _cleanup_free_ char *name = NULL; + r = path_extract_filename(path, &name); + if (r < 0) + return log_warning_errno(r, "Failed to extract file name from '%s': %m", path); + + _unused_ _cleanup_free_ char *key = NULL; + free(hashmap_remove2(*unit_ids_map, name, (void**) &key)); + string_strv_hashmap_remove(*unit_names_map, name, name); + free(set_remove(*path_cache, path)); + + return 0; +} + static int add_name( const char *unit_name, Set **names, diff --git a/src/shared/unit-file.h b/src/shared/unit-file.h index 1c89a92c7d9..dd7dc57d15e 100644 --- a/src/shared/unit-file.h +++ b/src/shared/unit-file.h @@ -68,6 +68,14 @@ int unit_file_build_name_map( Hashmap **unit_names_map, Set **path_cache); +int unit_file_remove_from_name_map( + const LookupPaths *lp, + uint64_t *cache_timestamp_hash, + Hashmap **unit_ids_map, + Hashmap **unit_names_map, + Set **path_cache, + const char *path); + int unit_file_find_fragment( Hashmap *unit_ids_map, Hashmap *unit_name_map, diff --git a/src/test/test-hashmap-plain.c b/src/test/test-hashmap-plain.c index 6b6058b08ac..6bcf353887f 100644 --- a/src/test/test-hashmap-plain.c +++ b/src/test/test-hashmap-plain.c @@ -973,6 +973,22 @@ TEST(string_strv_hashmap) { s = hashmap_get(m, "xxx"); assert_se(strv_equal(s, STRV_MAKE("bar", "BAR"))); + + string_strv_hashmap_remove(m, "foo", "bar"); + ASSERT_NOT_NULL(s = hashmap_get(m, "foo")); + ASSERT_TRUE(strv_equal(s, STRV_MAKE("BAR"))); + + string_strv_hashmap_remove(m, "foo", "BAR"); + ASSERT_NULL(hashmap_get(m, "foo")); + + string_strv_hashmap_remove(m, "xxx", "BAR"); + ASSERT_NOT_NULL(s = hashmap_get(m, "xxx")); + ASSERT_TRUE(strv_equal(s, STRV_MAKE("bar"))); + + string_strv_hashmap_remove(m, "xxx", "bar"); + ASSERT_NULL(hashmap_get(m, "xxx")); + + ASSERT_TRUE(hashmap_isempty(m)); } TEST(hashmap_dump_sorted) { diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index 63e500003c5..a84e6aaf975 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -1,11 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "fileio.h" #include "initrd-util.h" #include "path-lookup.h" +#include "path-util.h" +#include "random-util.h" +#include "rm-rf.h" #include "set.h" #include "special.h" #include "strv.h" #include "tests.h" +#include "tmpfile-util.h" #include "unit-file.h" TEST(unit_validate_alias_symlink_and_warn) { @@ -86,6 +91,82 @@ TEST(unit_file_build_name_map) { } } +static bool test_unit_file_remove_from_name_map_trail(const LookupPaths *lp, size_t trial) { + int r; + + log_debug("/* %s(trial=%zu) */", __func__, trial); + + _cleanup_hashmap_free_ Hashmap *unit_ids = NULL, *unit_names = NULL; + _cleanup_set_free_ Set *path_cache = NULL; + ASSERT_OK_POSITIVE(unit_file_build_name_map(lp, NULL, &unit_ids, &unit_names, &path_cache)); + + _cleanup_free_ char *name = NULL; + for (size_t i = 0; i < 100; i++) { + ASSERT_OK(asprintf(&name, "test-unit-file-%"PRIx64".service", random_u64())); + if (!hashmap_contains(unit_ids, name)) + break; + name = mfree(name); + } + ASSERT_NOT_NULL(name); + + _cleanup_free_ char *path = path_join(lp->transient, name); + ASSERT_NOT_NULL(path); + ASSERT_OK(write_string_file(path, "[Unit]\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755)); + + uint64_t cache_timestamp_hash = 0; + ASSERT_OK_POSITIVE(unit_file_build_name_map(lp, &cache_timestamp_hash, &unit_ids, &unit_names, &path_cache)); + + ASSERT_STREQ(hashmap_get(unit_ids, name), path); + ASSERT_TRUE(strv_equal(hashmap_get(unit_names, name), STRV_MAKE(name))); + ASSERT_TRUE(set_contains(path_cache, path)); + + ASSERT_OK_ERRNO(unlink(path)); + + ASSERT_OK(r = unit_file_remove_from_name_map(lp, &cache_timestamp_hash, &unit_ids, &unit_names, &path_cache, path)); + if (r > 0) + return false; /* someone touches unit files. Retrying. */ + + ASSERT_FALSE(hashmap_contains(unit_ids, name)); + ASSERT_FALSE(hashmap_contains(unit_names, path)); + ASSERT_FALSE(set_contains(path_cache, path)); + + _cleanup_hashmap_free_ Hashmap *unit_ids_2 = NULL, *unit_names_2 = NULL; + _cleanup_set_free_ Set *path_cache_2 = NULL; + ASSERT_OK_POSITIVE(unit_file_build_name_map(lp, NULL, &unit_ids_2, &unit_names_2, &path_cache_2)); + + if (hashmap_size(unit_ids) != hashmap_size(unit_ids_2) || + hashmap_size(unit_names) != hashmap_size(unit_names_2) || + !set_equal(path_cache, path_cache_2)) + return false; + + const char *k, *v; + HASHMAP_FOREACH_KEY(v, k, unit_ids) + if (!streq_ptr(hashmap_get(unit_ids_2, k), v)) + return false; + + char **l; + HASHMAP_FOREACH_KEY(l, k, unit_names) + if (!strv_equal_ignore_order(hashmap_get(unit_names_2, k), l)) + return false; + + return true; +} + + +TEST(unit_file_remove_from_name_map) { + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + + _cleanup_(lookup_paths_done) LookupPaths lp = {}; + ASSERT_OK(lookup_paths_init(&lp, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_TEMPORARY_GENERATED, NULL)); + ASSERT_NOT_NULL(d = strdup(lp.temporary_dir)); + + for (size_t i = 0; i < 10; i++) + if (test_unit_file_remove_from_name_map_trail(&lp, i)) + return; + + assert_not_reached(); +} + TEST(runlevel_to_target) { in_initrd_force(false); ASSERT_STREQ(runlevel_to_target(NULL), NULL); diff --git a/test/units/TEST-07-PID1.transient.sh b/test/units/TEST-07-PID1.transient.sh new file mode 100755 index 00000000000..ae71a381434 --- /dev/null +++ b/test/units/TEST-07-PID1.transient.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +journalctl --sync +TS="$(date '+%H:%M:%S')" + +systemd-run -u hogehoge.service sleep infinity +systemctl daemon-reload +systemctl stop hogehoge.service + +journalctl --sync +[[ -z "$(journalctl -b -q --since "$TS" -u hogehoge.service -p notice)" ]]