summaryrefslogtreecommitdiffstats
path: root/shared/hash.c
diff options
context:
space:
mode:
Diffstat (limited to 'shared/hash.c')
-rw-r--r--shared/hash.c341
1 files changed, 341 insertions, 0 deletions
diff --git a/shared/hash.c b/shared/hash.c
new file mode 100644
index 0000000..a87bc50
--- /dev/null
+++ b/shared/hash.c
@@ -0,0 +1,341 @@
+/*
+ * libkmod - interface to kernel module operations
+ *
+ * Copyright (C) 2011-2013 ProFUSION embedded systems
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <shared/hash.h>
+#include <shared/util.h>
+
+struct hash_entry {
+ const char *key;
+ const void *value;
+};
+
+struct hash_bucket {
+ struct hash_entry *entries;
+ unsigned int used;
+ unsigned int total;
+};
+
+struct hash {
+ unsigned int count;
+ unsigned int step;
+ unsigned int n_buckets;
+ void (*free_value)(void *value);
+ struct hash_bucket buckets[];
+};
+
+struct hash *hash_new(unsigned int n_buckets,
+ void (*free_value)(void *value))
+{
+ struct hash *hash;
+
+ n_buckets = ALIGN_POWER2(n_buckets);
+ hash = calloc(1, sizeof(struct hash) +
+ n_buckets * sizeof(struct hash_bucket));
+ if (hash == NULL)
+ return NULL;
+ hash->n_buckets = n_buckets;
+ hash->free_value = free_value;
+ hash->step = n_buckets / 32;
+ if (hash->step == 0)
+ hash->step = 4;
+ else if (hash->step > 64)
+ hash->step = 64;
+ return hash;
+}
+
+void hash_free(struct hash *hash)
+{
+ struct hash_bucket *bucket, *bucket_end;
+
+ if (hash == NULL)
+ return;
+
+ bucket = hash->buckets;
+ bucket_end = bucket + hash->n_buckets;
+ for (; bucket < bucket_end; bucket++) {
+ if (hash->free_value) {
+ struct hash_entry *entry, *entry_end;
+ entry = bucket->entries;
+ entry_end = entry + bucket->used;
+ for (; entry < entry_end; entry++)
+ hash->free_value((void *)entry->value);
+ }
+ free(bucket->entries);
+ }
+ free(hash);
+}
+
+static inline unsigned int hash_superfast(const char *key, unsigned int len)
+{
+ /* Paul Hsieh (http://www.azillionmonkeys.com/qed/hash.html)
+ * used by WebCore (http://webkit.org/blog/8/hashtables-part-2/)
+ * EFL's eina and possible others.
+ */
+ unsigned int tmp, hash = len, rem = len & 3;
+
+ len /= 4;
+
+ /* Main loop */
+ for (; len > 0; len--) {
+ hash += get_unaligned((uint16_t *) key);
+ tmp = (get_unaligned((uint16_t *)(key + 2)) << 11) ^ hash;
+ hash = (hash << 16) ^ tmp;
+ key += 4;
+ hash += hash >> 11;
+ }
+
+ /* Handle end cases */
+ switch (rem) {
+ case 3:
+ hash += get_unaligned((uint16_t *) key);
+ hash ^= hash << 16;
+ hash ^= key[2] << 18;
+ hash += hash >> 11;
+ break;
+
+ case 2:
+ hash += get_unaligned((uint16_t *) key);
+ hash ^= hash << 11;
+ hash += hash >> 17;
+ break;
+
+ case 1:
+ hash += *key;
+ hash ^= hash << 10;
+ hash += hash >> 1;
+ }
+
+ /* Force "avalanching" of final 127 bits */
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 4;
+ hash += hash >> 17;
+ hash ^= hash << 25;
+ hash += hash >> 6;
+
+ return hash;
+}
+
+/*
+ * add or replace key in hash map.
+ *
+ * none of key or value are copied, just references are remembered as is,
+ * make sure they are live while pair exists in hash!
+ */
+int hash_add(struct hash *hash, const char *key, const void *value)
+{
+ unsigned int keylen = strlen(key);
+ unsigned int hashval = hash_superfast(key, keylen);
+ unsigned int pos = hashval & (hash->n_buckets - 1);
+ struct hash_bucket *bucket = hash->buckets + pos;
+ struct hash_entry *entry, *entry_end;
+
+ if (bucket->used + 1 >= bucket->total) {
+ unsigned new_total = bucket->total + hash->step;
+ size_t size = new_total * sizeof(struct hash_entry);
+ struct hash_entry *tmp = realloc(bucket->entries, size);
+ if (tmp == NULL)
+ return -errno;
+ bucket->entries = tmp;
+ bucket->total = new_total;
+ }
+
+ entry = bucket->entries;
+ entry_end = entry + bucket->used;
+ for (; entry < entry_end; entry++) {
+ int c = strcmp(key, entry->key);
+ if (c == 0) {
+ if (hash->free_value)
+ hash->free_value((void *)entry->value);
+ entry->key = key;
+ entry->value = value;
+ return 0;
+ } else if (c < 0) {
+ memmove(entry + 1, entry,
+ (entry_end - entry) * sizeof(struct hash_entry));
+ break;
+ }
+ }
+
+ entry->key = key;
+ entry->value = value;
+ bucket->used++;
+ hash->count++;
+ return 0;
+}
+
+/* similar to hash_add(), but fails if key already exists */
+int hash_add_unique(struct hash *hash, const char *key, const void *value)
+{
+ unsigned int keylen = strlen(key);
+ unsigned int hashval = hash_superfast(key, keylen);
+ unsigned int pos = hashval & (hash->n_buckets - 1);
+ struct hash_bucket *bucket = hash->buckets + pos;
+ struct hash_entry *entry, *entry_end;
+
+ if (bucket->used + 1 >= bucket->total) {
+ unsigned new_total = bucket->total + hash->step;
+ size_t size = new_total * sizeof(struct hash_entry);
+ struct hash_entry *tmp = realloc(bucket->entries, size);
+ if (tmp == NULL)
+ return -errno;
+ bucket->entries = tmp;
+ bucket->total = new_total;
+ }
+
+ entry = bucket->entries;
+ entry_end = entry + bucket->used;
+ for (; entry < entry_end; entry++) {
+ int c = strcmp(key, entry->key);
+ if (c == 0)
+ return -EEXIST;
+ else if (c < 0) {
+ memmove(entry + 1, entry,
+ (entry_end - entry) * sizeof(struct hash_entry));
+ break;
+ }
+ }
+
+ entry->key = key;
+ entry->value = value;
+ bucket->used++;
+ hash->count++;
+ return 0;
+}
+
+static int hash_entry_cmp(const void *pa, const void *pb)
+{
+ const struct hash_entry *a = pa;
+ const struct hash_entry *b = pb;
+ return strcmp(a->key, b->key);
+}
+
+void *hash_find(const struct hash *hash, const char *key)
+{
+ unsigned int keylen = strlen(key);
+ unsigned int hashval = hash_superfast(key, keylen);
+ unsigned int pos = hashval & (hash->n_buckets - 1);
+ const struct hash_bucket *bucket = hash->buckets + pos;
+ const struct hash_entry se = {
+ .key = key,
+ .value = NULL
+ };
+ const struct hash_entry *entry;
+
+ if (!bucket->entries)
+ return NULL;
+
+ entry = bsearch(&se, bucket->entries, bucket->used,
+ sizeof(struct hash_entry), hash_entry_cmp);
+
+ return entry ? (void *)entry->value : NULL;
+}
+
+int hash_del(struct hash *hash, const char *key)
+{
+ unsigned int keylen = strlen(key);
+ unsigned int hashval = hash_superfast(key, keylen);
+ unsigned int pos = hashval & (hash->n_buckets - 1);
+ unsigned int steps_used, steps_total;
+ struct hash_bucket *bucket = hash->buckets + pos;
+ struct hash_entry *entry, *entry_end;
+ const struct hash_entry se = {
+ .key = key,
+ .value = NULL
+ };
+
+ entry = bsearch(&se, bucket->entries, bucket->used,
+ sizeof(struct hash_entry), hash_entry_cmp);
+ if (entry == NULL)
+ return -ENOENT;
+
+ if (hash->free_value)
+ hash->free_value((void *)entry->value);
+
+ entry_end = bucket->entries + bucket->used;
+ memmove(entry, entry + 1,
+ (entry_end - entry) * sizeof(struct hash_entry));
+
+ bucket->used--;
+ hash->count--;
+
+ steps_used = bucket->used / hash->step;
+ steps_total = bucket->total / hash->step;
+ if (steps_used + 1 < steps_total) {
+ size_t size = (steps_used + 1) *
+ hash->step * sizeof(struct hash_entry);
+ struct hash_entry *tmp = realloc(bucket->entries, size);
+ if (tmp) {
+ bucket->entries = tmp;
+ bucket->total = (steps_used + 1) * hash->step;
+ }
+ }
+
+ return 0;
+}
+
+unsigned int hash_get_count(const struct hash *hash)
+{
+ return hash->count;
+}
+
+void hash_iter_init(const struct hash *hash, struct hash_iter *iter)
+{
+ iter->hash = hash;
+ iter->bucket = 0;
+ iter->entry = -1;
+}
+
+bool hash_iter_next(struct hash_iter *iter, const char **key,
+ const void **value)
+{
+ const struct hash_bucket *b = iter->hash->buckets + iter->bucket;
+ const struct hash_entry *e;
+
+ iter->entry++;
+
+ if (iter->entry >= b->used) {
+ iter->entry = 0;
+
+ for (iter->bucket++; iter->bucket < iter->hash->n_buckets;
+ iter->bucket++) {
+ b = iter->hash->buckets + iter->bucket;
+
+ if (b->used > 0)
+ break;
+ }
+
+ if (iter->bucket >= iter->hash->n_buckets)
+ return false;
+ }
+
+ e = b->entries + iter->entry;
+
+ if (value != NULL)
+ *value = e->value;
+ if (key != NULL)
+ *key = e->key;
+
+ return true;
+}