summaryrefslogtreecommitdiffstats
path: root/src/lib/hash2.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib/hash2.c242
1 files changed, 242 insertions, 0 deletions
diff --git a/src/lib/hash2.c b/src/lib/hash2.c
new file mode 100644
index 0000000..a8a23e0
--- /dev/null
+++ b/src/lib/hash2.c
@@ -0,0 +1,242 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "primes.h"
+#include "hash2.h"
+
+#define HASH_TABLE_MIN_SIZE 131
+
+struct hash2_value {
+ struct hash2_value *next;
+ unsigned int key_hash;
+ /* user_data[value_size] */
+};
+ARRAY_DEFINE_TYPE(hash2_value, struct hash2_value *);
+
+struct hash2_table {
+ unsigned int count;
+ unsigned int initial_size;
+ unsigned int hash_table_size;
+ unsigned int value_size;
+
+ pool_t value_pool;
+ struct hash2_value *deleted_values;
+
+ ARRAY_TYPE(hash2_value) hash_table;
+
+ hash2_key_callback_t *key_hash_cb;
+ hash2_cmp_callback_t *key_compare_cb;
+ void *context;
+};
+
+static void hash2_alloc_table(struct hash2_table *hash, unsigned int size)
+{
+ hash->hash_table_size = size;
+
+ i_array_init(&hash->hash_table, hash->hash_table_size);
+ (void)array_idx_get_space(&hash->hash_table, hash->hash_table_size-1);
+}
+
+struct hash2_table *
+hash2_create(unsigned int initial_size, unsigned int value_size,
+ hash2_key_callback_t *key_hash_cb,
+ hash2_cmp_callback_t *key_compare_cb, void *context)
+{
+ struct hash2_table *hash;
+
+ hash = i_new(struct hash2_table, 1);
+ hash->initial_size = I_MAX(initial_size, HASH_TABLE_MIN_SIZE);
+ hash->value_size = value_size;
+ hash->key_hash_cb = key_hash_cb;
+ hash->key_compare_cb = key_compare_cb;
+ hash->context = context;
+
+ hash->value_pool = pool_alloconly_create("hash2 value pool", 16384);
+ hash2_alloc_table(hash, hash->initial_size);
+ return hash;
+}
+
+void hash2_destroy(struct hash2_table **_hash)
+{
+ struct hash2_table *hash = *_hash;
+
+ *_hash = NULL;
+ array_free(&hash->hash_table);
+ pool_unref(&hash->value_pool);
+ i_free(hash);
+}
+
+void hash2_clear(struct hash2_table *hash)
+{
+ array_free(&hash->hash_table);
+ hash2_alloc_table(hash, hash->initial_size);
+ p_clear(hash->value_pool);
+ hash->count = 0;
+ hash->deleted_values = NULL;
+}
+
+static void hash2_resize(struct hash2_table *hash, bool grow)
+{
+ ARRAY_TYPE(hash2_value) old_hash_table;
+ struct hash2_value *old_hash, *value, **valuep, *next;
+ unsigned int next_size, old_count, i, idx;
+ float nodes_per_list;
+
+ nodes_per_list = (float)hash->count / (float)hash->hash_table_size;
+ if (nodes_per_list > 0.3 && nodes_per_list < 2.0)
+ return;
+
+ next_size = I_MAX(primes_closest(hash->count + 1), hash->initial_size);
+ if (hash->hash_table_size >= next_size &&
+ (grow || next_size == hash->hash_table_size))
+ return;
+
+ old_hash_table = hash->hash_table;
+ hash2_alloc_table(hash, next_size);
+
+ old_count = array_count(&old_hash_table);
+ for (i = 0; i < old_count; i++) {
+ old_hash = array_idx_elem(&old_hash_table, i);
+ for (value = old_hash; value != NULL; value = next) {
+ next = value->next;
+
+ idx = value->key_hash % hash->hash_table_size;
+ valuep = array_idx_modifiable(&hash->hash_table, idx);
+ value->next = *valuep;
+ *valuep = value;
+ }
+ }
+ array_free(&old_hash_table);
+}
+
+void *hash2_lookup(const struct hash2_table *hash, const void *key)
+{
+ unsigned int key_hash = hash->key_hash_cb(key);
+ struct hash2_value *value;
+ void *user_value;
+
+ value = array_idx_elem(&hash->hash_table,
+ key_hash % hash->hash_table_size);
+ while (value != NULL) {
+ if (value->key_hash == key_hash) {
+ user_value = value + 1;
+ if (hash->key_compare_cb(key, user_value,
+ hash->context))
+ return user_value;
+ }
+ value = value->next;
+ }
+ return NULL;
+}
+
+void *hash2_iterate(const struct hash2_table *hash,
+ unsigned int key_hash, struct hash2_iter *iter)
+{
+ struct hash2_value *value;
+
+ if (iter->value == NULL) {
+ iter->key_hash = key_hash;
+ value = array_idx_elem(&hash->hash_table,
+ key_hash % hash->hash_table_size);
+ iter->next_value = value;
+ }
+ while (iter->next_value != NULL) {
+ if (iter->next_value->key_hash == key_hash) {
+ iter->value = iter->next_value;
+ iter->next_value = iter->next_value->next;
+ return iter->value + 1;
+ }
+ iter->next_value = iter->next_value->next;
+ }
+ return NULL;
+}
+
+void *hash2_insert(struct hash2_table *hash, const void *key)
+{
+ return hash2_insert_hash(hash, hash->key_hash_cb(key));
+}
+
+void *hash2_insert_hash(struct hash2_table *hash, unsigned int key_hash)
+{
+ struct hash2_value *value, **valuep;
+
+ hash2_resize(hash, TRUE);
+
+ if (hash->deleted_values != NULL) {
+ value = hash->deleted_values;
+ hash->deleted_values = value->next;
+ value->next = NULL;
+ memset(value + 1, 0, hash->value_size);
+ } else {
+ value = p_malloc(hash->value_pool,
+ sizeof(*value) + hash->value_size);
+ }
+ value->key_hash = key_hash;
+
+ valuep = array_idx_modifiable(&hash->hash_table,
+ key_hash % hash->hash_table_size);
+ value->next = *valuep;
+ *valuep = value;
+
+ hash->count++;
+ return value + 1;
+}
+
+static void
+hash2_remove_value_p(struct hash2_table *hash, struct hash2_value **valuep)
+{
+ struct hash2_value *deleted_value;
+
+ deleted_value = *valuep;
+ *valuep = deleted_value->next;
+
+ deleted_value->next = hash->deleted_values;
+ hash->deleted_values = deleted_value;
+
+ hash->count--;
+}
+
+void hash2_remove(struct hash2_table *hash, const void *key)
+{
+ unsigned int key_hash = hash->key_hash_cb(key);
+ struct hash2_value **valuep;
+
+ valuep = array_idx_modifiable(&hash->hash_table,
+ key_hash % hash->hash_table_size);
+ while (*valuep != NULL) {
+ if ((*valuep)->key_hash == key_hash &&
+ hash->key_compare_cb(key, (*valuep) + 1, hash->context)) {
+ hash2_remove_value_p(hash, valuep);
+ hash2_resize(hash, FALSE);
+ return;
+ }
+ valuep = &(*valuep)->next;
+ }
+ i_panic("hash2_remove(): key not found");
+}
+
+void hash2_remove_iter(struct hash2_table *hash, struct hash2_iter *iter)
+{
+ struct hash2_value **valuep, *next;
+
+ valuep = array_idx_modifiable(&hash->hash_table,
+ iter->key_hash % hash->hash_table_size);
+ while (*valuep != NULL) {
+ if (*valuep == iter->value) {
+ next = (*valuep)->next;
+ /* don't allow resizing, otherwise iterating would
+ break completely */
+ hash2_remove_value_p(hash, valuep);
+ iter->next_value = next;
+ return;
+ }
+ valuep = &(*valuep)->next;
+ }
+ i_panic("hash2_remove_value(): key/value not found");
+}
+
+unsigned int hash2_count(const struct hash2_table *hash)
+{
+ return hash->count;
+}