]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
hash-func: add generic hash_ops implementation for hashing paths
authorLennart Poettering <lennart@poettering.net>
Thu, 8 Feb 2018 17:31:15 +0000 (18:31 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 12 Feb 2018 10:07:55 +0000 (11:07 +0100)
This is similar to string_hash_ops but operates one file system paths
specifically. It will ensure that "/foo//bar" and "///foo/bar" are
considered to be the same path for hashmap purposes.

This makes use of the existing path_compare() API, and adds a matching
hashing function for it.

Note that relative and absolute paths will hash to different values,
however whether the path is suffixed with a slash or not is not
detected. This matches the existing path_compare() behaviour, and
follows the logic that on Linux there can't be two different objects at
path /foo/bar and /foo/bar/ either.

src/basic/hash-funcs.c
src/basic/hash-funcs.h
src/test/test-hashmap.c

index 5267758769c94aaff3fe87738ca4626c1077cb1d..947bcfd584331cb64f7cbcb5d853ec58bdb89219 100644 (file)
@@ -22,6 +22,7 @@
 #include <string.h>
 
 #include "hash-funcs.h"
+#include "path-util.h"
 
 void string_hash_func(const void *p, struct siphash *state) {
         siphash24_compress(p, strlen(p) + 1, state);
@@ -36,6 +37,55 @@ const struct hash_ops string_hash_ops = {
         .compare = string_compare_func
 };
 
+
+void path_hash_func(const void *p, struct siphash *state) {
+        const char *q = p;
+        size_t n;
+
+        assert(q);
+        assert(state);
+
+        /* Calculates a hash for a path in a way this duplicate inner slashes don't make a differences, and also
+         * whether there's a trailing slash or not. This fits well with the semantics of path_compare(), which does
+         * similar checks and also doesn't care for trailing slashes. Note that relative and absolute paths (i.e. those
+         * which begin in a slash or not) will hash differently though. */
+
+        n = strspn(q, "/");
+        if (n > 0) { /* Eat up initial slashes, and add one "/" to the hash for all of them */
+                siphash24_compress(q, 1, state);
+                q += n;
+        }
+
+        for (;;) {
+                /* Determine length of next component */
+                n = strcspn(q, "/");
+                if (n == 0) /* Reached the end? */
+                        break;
+
+                /* Add this component to the hash and skip over it */
+                siphash24_compress(q, n, state);
+                q += n;
+
+                /* How many slashes follow this component? */
+                n = strspn(q, "/");
+                if (q[n] == 0) /* Is this a trailing slash? If so, we are at the end, and don't care about the slashes anymore */
+                        break;
+
+                /* We are not add the end yet. Hash exactly one slash for all of the ones we just encountered. */
+                siphash24_compress(q, 1, state);
+                q += n;
+        }
+}
+
+int path_compare_func(const void *a, const void *b) {
+        return path_compare(a, b);
+}
+
+const struct hash_ops path_hash_ops = {
+        .hash = path_hash_func,
+        .compare = path_compare_func
+};
+
 void trivial_hash_func(const void *p, struct siphash *state) {
         siphash24_compress(&p, sizeof(p), state);
 }
index c31ef0cc39e6fba647ae22b9d7cd6b2266bd04fe..945b4c251c7dcfa9514647af455a7e1e9d24d0af 100644 (file)
@@ -36,9 +36,12 @@ void string_hash_func(const void *p, struct siphash *state);
 int string_compare_func(const void *a, const void *b) _pure_;
 extern const struct hash_ops string_hash_ops;
 
-/* This will compare the passed pointers directly, and will not
- * dereference them. This is hence not useful for strings or
- * suchlike. */
+void path_hash_func(const void *p, struct siphash *state);
+int path_compare_func(const void *a, const void *b) _pure_;
+extern const struct hash_ops path_hash_ops;
+
+/* This will compare the passed pointers directly, and will not dereference them. This is hence not useful for strings
+ * or suchlike. */
 void trivial_hash_func(const void *p, struct siphash *state);
 int trivial_compare_func(const void *a, const void *b) _const_;
 extern const struct hash_ops trivial_hash_ops;
index 16ca27cd5fd187803d4f841aacf508e97cd2d78d..ad0b7390e9bf2ce120e50dca209f9e1bc713189a 100644 (file)
@@ -137,6 +137,34 @@ static void test_iterated_cache(void) {
         assert_se(iterated_cache_free(c) == NULL);
 }
 
+static void test_path_hashmap(void) {
+        _cleanup_(hashmap_freep) Hashmap *h = NULL;
+
+        assert_se(h = hashmap_new(&path_hash_ops));
+
+        assert_se(hashmap_put(h, "foo", INT_TO_PTR(1)) >= 0);
+        assert_se(hashmap_put(h, "/foo", INT_TO_PTR(2)) >= 0);
+        assert_se(hashmap_put(h, "//foo", INT_TO_PTR(3)) == -EEXIST);
+        assert_se(hashmap_put(h, "//foox/", INT_TO_PTR(4)) >= 0);
+        assert_se(hashmap_put(h, "/foox////", INT_TO_PTR(5)) == -EEXIST);
+        assert_se(hashmap_put(h, "foo//////bar/quux//", INT_TO_PTR(6)) >= 0);
+        assert_se(hashmap_put(h, "foo/bar//quux/", INT_TO_PTR(8)) == -EEXIST);
+
+        assert_se(hashmap_get(h, "foo") == INT_TO_PTR(1));
+        assert_se(hashmap_get(h, "foo/") == INT_TO_PTR(1));
+        assert_se(hashmap_get(h, "foo////") == INT_TO_PTR(1));
+        assert_se(hashmap_get(h, "/foo") == INT_TO_PTR(2));
+        assert_se(hashmap_get(h, "//foo") == INT_TO_PTR(2));
+        assert_se(hashmap_get(h, "/////foo////") == INT_TO_PTR(2));
+        assert_se(hashmap_get(h, "/////foox////") == INT_TO_PTR(4));
+        assert_se(hashmap_get(h, "/foox/") == INT_TO_PTR(4));
+        assert_se(hashmap_get(h, "/foox") == INT_TO_PTR(4));
+        assert_se(!hashmap_get(h, "foox"));
+        assert_se(hashmap_get(h, "foo/bar/quux") == INT_TO_PTR(6));
+        assert_se(hashmap_get(h, "foo////bar////quux/////") == INT_TO_PTR(6));
+        assert_se(!hashmap_get(h, "/foo////bar////quux/////"));
+}
+
 int main(int argc, const char *argv[]) {
         test_hashmap_funcs();
         test_ordered_hashmap_funcs();
@@ -147,4 +175,7 @@ int main(int argc, const char *argv[]) {
         test_trivial_compare_func();
         test_string_compare_func();
         test_iterated_cache();
+        test_path_hashmap();
+
+        return 0;
 }