From: Lennart Poettering Date: Thu, 8 Feb 2018 17:31:15 +0000 (+0100) Subject: hash-func: add generic hash_ops implementation for hashing paths X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=743ac495b83a83e7336f55321d38a4ebe7389e58;p=elogind.git hash-func: add generic hash_ops implementation for hashing paths 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. (cherry picked from commit 46e16b347f83d809ed3d34f26286f580dfd086ce) --- diff --git a/src/basic/hash-funcs.c b/src/basic/hash-funcs.c index 1716401d1..763c1f61d 100644 --- a/src/basic/hash-funcs.c +++ b/src/basic/hash-funcs.c @@ -22,6 +22,7 @@ //#include #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); } diff --git a/src/basic/hash-funcs.h b/src/basic/hash-funcs.h index c31ef0cc3..945b4c251 100644 --- a/src/basic/hash-funcs.h +++ b/src/basic/hash-funcs.h @@ -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; diff --git a/src/test/test-hashmap.c b/src/test/test-hashmap.c index dd9195425..f43834a84 100644 --- a/src/test/test-hashmap.c +++ b/src/test/test-hashmap.c @@ -80,6 +80,34 @@ static void test_string_compare_func(void) { assert_se(string_compare_func("fred", "fred") == 0); } +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(); @@ -89,4 +117,7 @@ int main(int argc, const char *argv[]) { test_uint64_compare_func(); test_trivial_compare_func(); test_string_compare_func(); + test_path_hashmap(); + + return 0; }