X Tutup
Skip to content

Commit 46e16b3

Browse files
committed
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.
1 parent 9bac7d4 commit 46e16b3

File tree

3 files changed

+87
-3
lines changed

3 files changed

+87
-3
lines changed

src/basic/hash-funcs.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <string.h>
2323

2424
#include "hash-funcs.h"
25+
#include "path-util.h"
2526

2627
void string_hash_func(const void *p, struct siphash *state) {
2728
siphash24_compress(p, strlen(p) + 1, state);
@@ -36,6 +37,55 @@ const struct hash_ops string_hash_ops = {
3637
.compare = string_compare_func
3738
};
3839

40+
41+
void path_hash_func(const void *p, struct siphash *state) {
42+
const char *q = p;
43+
size_t n;
44+
45+
assert(q);
46+
assert(state);
47+
48+
/* Calculates a hash for a path in a way this duplicate inner slashes don't make a differences, and also
49+
* whether there's a trailing slash or not. This fits well with the semantics of path_compare(), which does
50+
* similar checks and also doesn't care for trailing slashes. Note that relative and absolute paths (i.e. those
51+
* which begin in a slash or not) will hash differently though. */
52+
53+
n = strspn(q, "/");
54+
if (n > 0) { /* Eat up initial slashes, and add one "/" to the hash for all of them */
55+
siphash24_compress(q, 1, state);
56+
q += n;
57+
}
58+
59+
for (;;) {
60+
/* Determine length of next component */
61+
n = strcspn(q, "/");
62+
if (n == 0) /* Reached the end? */
63+
break;
64+
65+
/* Add this component to the hash and skip over it */
66+
siphash24_compress(q, n, state);
67+
q += n;
68+
69+
/* How many slashes follow this component? */
70+
n = strspn(q, "/");
71+
if (q[n] == 0) /* Is this a trailing slash? If so, we are at the end, and don't care about the slashes anymore */
72+
break;
73+
74+
/* We are not add the end yet. Hash exactly one slash for all of the ones we just encountered. */
75+
siphash24_compress(q, 1, state);
76+
q += n;
77+
}
78+
}
79+
80+
int path_compare_func(const void *a, const void *b) {
81+
return path_compare(a, b);
82+
}
83+
84+
const struct hash_ops path_hash_ops = {
85+
.hash = path_hash_func,
86+
.compare = path_compare_func
87+
};
88+
3989
void trivial_hash_func(const void *p, struct siphash *state) {
4090
siphash24_compress(&p, sizeof(p), state);
4191
}

src/basic/hash-funcs.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@ void string_hash_func(const void *p, struct siphash *state);
3636
int string_compare_func(const void *a, const void *b) _pure_;
3737
extern const struct hash_ops string_hash_ops;
3838

39-
/* This will compare the passed pointers directly, and will not
40-
* dereference them. This is hence not useful for strings or
41-
* suchlike. */
39+
void path_hash_func(const void *p, struct siphash *state);
40+
int path_compare_func(const void *a, const void *b) _pure_;
41+
extern const struct hash_ops path_hash_ops;
42+
43+
/* This will compare the passed pointers directly, and will not dereference them. This is hence not useful for strings
44+
* or suchlike. */
4245
void trivial_hash_func(const void *p, struct siphash *state);
4346
int trivial_compare_func(const void *a, const void *b) _const_;
4447
extern const struct hash_ops trivial_hash_ops;

src/test/test-hashmap.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,34 @@ static void test_iterated_cache(void) {
137137
assert_se(iterated_cache_free(c) == NULL);
138138
}
139139

140+
static void test_path_hashmap(void) {
141+
_cleanup_(hashmap_freep) Hashmap *h = NULL;
142+
143+
assert_se(h = hashmap_new(&path_hash_ops));
144+
145+
assert_se(hashmap_put(h, "foo", INT_TO_PTR(1)) >= 0);
146+
assert_se(hashmap_put(h, "/foo", INT_TO_PTR(2)) >= 0);
147+
assert_se(hashmap_put(h, "//foo", INT_TO_PTR(3)) == -EEXIST);
148+
assert_se(hashmap_put(h, "//foox/", INT_TO_PTR(4)) >= 0);
149+
assert_se(hashmap_put(h, "/foox////", INT_TO_PTR(5)) == -EEXIST);
150+
assert_se(hashmap_put(h, "foo//////bar/quux//", INT_TO_PTR(6)) >= 0);
151+
assert_se(hashmap_put(h, "foo/bar//quux/", INT_TO_PTR(8)) == -EEXIST);
152+
153+
assert_se(hashmap_get(h, "foo") == INT_TO_PTR(1));
154+
assert_se(hashmap_get(h, "foo/") == INT_TO_PTR(1));
155+
assert_se(hashmap_get(h, "foo////") == INT_TO_PTR(1));
156+
assert_se(hashmap_get(h, "/foo") == INT_TO_PTR(2));
157+
assert_se(hashmap_get(h, "//foo") == INT_TO_PTR(2));
158+
assert_se(hashmap_get(h, "/////foo////") == INT_TO_PTR(2));
159+
assert_se(hashmap_get(h, "/////foox////") == INT_TO_PTR(4));
160+
assert_se(hashmap_get(h, "/foox/") == INT_TO_PTR(4));
161+
assert_se(hashmap_get(h, "/foox") == INT_TO_PTR(4));
162+
assert_se(!hashmap_get(h, "foox"));
163+
assert_se(hashmap_get(h, "foo/bar/quux") == INT_TO_PTR(6));
164+
assert_se(hashmap_get(h, "foo////bar////quux/////") == INT_TO_PTR(6));
165+
assert_se(!hashmap_get(h, "/foo////bar////quux/////"));
166+
}
167+
140168
int main(int argc, const char *argv[]) {
141169
test_hashmap_funcs();
142170
test_ordered_hashmap_funcs();
@@ -147,4 +175,7 @@ int main(int argc, const char *argv[]) {
147175
test_trivial_compare_func();
148176
test_string_compare_func();
149177
test_iterated_cache();
178+
test_path_hashmap();
179+
180+
return 0;
150181
}

0 commit comments

Comments
 (0)
X Tutup