/* * Copyright (C) 2008 Philippe Gerum . * * 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 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include #include #include "boilerplate/lock.h" #include "boilerplate/hash.h" #include "boilerplate/debug.h" /* * Crunching routine borrowed from: * * lookup2.c, by Bob Jenkins, December 1996, Public Domain. * hash(), hash2(), hash3, and mix() are externally useful functions. * Routines to test the hash are included if SELF_TEST is defined. * You can use this free for any purpose. It has no warranty. */ #define __mixer(a, b, c) \ { \ a -= b; a -= c; a ^= (c>>13); \ b -= c; b -= a; b ^= (a<<8); \ c -= a; c -= b; c ^= (b>>13); \ a -= b; a -= c; a ^= (c>>12); \ b -= c; b -= a; b ^= (a<<16); \ c -= a; c -= b; c ^= (b>>5); \ a -= b; a -= c; a ^= (c>>3); \ b -= c; b -= a; b ^= (a<<10); \ c -= a; c -= b; c ^= (b>>15); \ } static inline int store_key(struct hashobj *obj, const void *key, size_t len, const struct hash_operations *hops); static inline void drop_key(struct hashobj *obj, const struct hash_operations *hops); #define GOLDEN_HASH_RATIO 0x9e3779b9 /* Arbitrary value. */ unsigned int __hash_key(const void *key, size_t length, unsigned int c) { const unsigned char *k = key; unsigned int a, b, len; len = (unsigned int)length; a = b = GOLDEN_HASH_RATIO; while (len >= 12) { a += (k[0] +((unsigned int)k[1]<<8) +((unsigned int)k[2]<<16) +((unsigned int)k[3]<<24)); b += (k[4] +((unsigned int)k[5]<<8) +((unsigned int)k[6]<<16) +((unsigned int)k[7]<<24)); c += (k[8] +((unsigned int)k[9]<<8) +((unsigned int)k[10]<<16)+((unsigned int)k[11]<<24)); __mixer(a, b, c); k += 12; len -= 12; } c += (unsigned int)length; switch (len) { case 11: c += ((unsigned int)k[10]<<24); case 10: c += ((unsigned int)k[9]<<16); case 9 : c += ((unsigned int)k[8]<<8); case 8 : b += ((unsigned int)k[7]<<24); case 7 : b += ((unsigned int)k[6]<<16); case 6 : b += ((unsigned int)k[5]<<8); case 5 : b += k[4]; case 4 : a += ((unsigned int)k[3]<<24); case 3 : a += ((unsigned int)k[2]<<16); case 2 : a += ((unsigned int)k[1]<<8); case 1 : a += k[0]; }; __mixer(a, b, c); return c; } void __hash_init(void *heap, struct hash_table *t) { pthread_mutexattr_t mattr; int n; for (n = 0; n < HASHSLOTS; n++) __list_init(heap, &t->table[n].obj_list); pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, mutex_type_attribute); pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT); pthread_mutexattr_setpshared(&mattr, mutex_scope_attribute); __RT(pthread_mutex_init(&t->lock, &mattr)); pthread_mutexattr_destroy(&mattr); } void hash_destroy(struct hash_table *t) { __RT(pthread_mutex_destroy(&t->lock)); } static struct hash_bucket *do_hash(struct hash_table *t, const void *key, size_t len) { unsigned int hash = __hash_key(key, len, 0); return &t->table[hash & (HASHSLOTS-1)]; } int __hash_enter(struct hash_table *t, const void *key, size_t len, struct hashobj *newobj, const struct hash_operations *hops, int nodup) { struct hash_bucket *bucket; struct hashobj *obj; int ret; holder_init(&newobj->link); ret = store_key(newobj, key, len, hops); if (ret) return ret; bucket = do_hash(t, key, len); write_lock_nocancel(&t->lock); if (nodup && !list_empty(&bucket->obj_list)) { list_for_each_entry(obj, &bucket->obj_list, link) { if (obj->len != newobj->len) continue; if (hops->compare(__mptr(obj->key), __mptr(newobj->key), obj->len) == 0) { drop_key(newobj, hops); ret = -EEXIST; goto out; } } } list_append(&newobj->link, &bucket->obj_list); out: write_unlock(&t->lock); return ret; } int hash_remove(struct hash_table *t, struct hashobj *delobj, const struct hash_operations *hops) { struct hash_bucket *bucket; struct hashobj *obj; int ret = -ESRCH; bucket = do_hash(t, __mptr(delobj->key), delobj->len); write_lock_nocancel(&t->lock); if (!list_empty(&bucket->obj_list)) { list_for_each_entry(obj, &bucket->obj_list, link) { if (obj == delobj) { list_remove_init(&obj->link); drop_key(obj, hops); ret = 0; goto out; } } } out: write_unlock(&t->lock); return __bt(ret); } struct hashobj *hash_search(struct hash_table *t, const void *key, size_t len, const struct hash_operations *hops) { struct hash_bucket *bucket; struct hashobj *obj; bucket = do_hash(t, key, len); read_lock_nocancel(&t->lock); if (!list_empty(&bucket->obj_list)) { list_for_each_entry(obj, &bucket->obj_list, link) { if (obj->len != len) continue; if (hops->compare(__mptr(obj->key), key, len) == 0) goto out; } } obj = NULL; out: read_unlock(&t->lock); return obj; } int hash_walk(struct hash_table *t, hash_walk_op walk, void *arg) { struct hash_bucket *bucket; struct hashobj *obj, *tmp; int ret, n; read_lock_nocancel(&t->lock); for (n = 0; n < HASHSLOTS; n++) { bucket = &t->table[n]; if (list_empty(&bucket->obj_list)) continue; list_for_each_entry_safe(obj, tmp, &bucket->obj_list, link) { read_unlock(&t->lock); ret = walk(t, obj, arg); if (ret) return __bt(ret); read_lock_nocancel(&t->lock); } } read_unlock(&t->lock); return 0; } #ifdef CONFIG_XENO_PSHARED static inline int store_key(struct hashobj *obj, const void *key, size_t len, const struct hash_operations *hops) { void *p; assert(__mchk(obj)); if (len > sizeof(obj->static_key)) { p = hops->alloc(len); if (p == NULL) return -ENOMEM; assert(__mchk(p)); } else p = obj->static_key; memcpy(p, key, len); obj->key = __moff(p); obj->len = len; return 0; } static inline void drop_key(struct hashobj *obj, const struct hash_operations *hops) { const void *key = __mptr(obj->key); if (key != obj->static_key) hops->free((void *)key); } int __hash_enter_probe(struct hash_table *t, const void *key, size_t len, struct hashobj *newobj, const struct hash_operations *hops, int nodup) { struct hash_bucket *bucket; struct hashobj *obj, *tmp; struct service svc; int ret; holder_init(&newobj->link); ret = store_key(newobj, key, len, hops); if (ret) return ret; bucket = do_hash(t, key, len); CANCEL_DEFER(svc); write_lock(&t->lock); if (!list_empty(&bucket->obj_list)) { list_for_each_entry_safe(obj, tmp, &bucket->obj_list, link) { if (obj->len != newobj->len) continue; if (hops->compare(__mptr(obj->key), __mptr(newobj->key), obj->len) == 0) { if (hops->probe(obj)) { if (nodup) { drop_key(newobj, hops); ret = -EEXIST; goto out; } continue; } list_remove_init(&obj->link); drop_key(obj, hops); } } } list_append(&newobj->link, &bucket->obj_list); out: write_unlock(&t->lock); CANCEL_RESTORE(svc); return ret; } struct hashobj *hash_search_probe(struct hash_table *t, const void *key, size_t len, const struct hash_operations *hops) { struct hash_bucket *bucket; struct hashobj *obj, *tmp; struct service svc; bucket = do_hash(t, key, len); CANCEL_DEFER(svc); write_lock(&t->lock); if (!list_empty(&bucket->obj_list)) { list_for_each_entry_safe(obj, tmp, &bucket->obj_list, link) { if (obj->len != len) continue; if (hops->compare(__mptr(obj->key), key, len) == 0) { if (!hops->probe(obj)) { list_remove_init(&obj->link); drop_key(obj, hops); continue; } goto out; } } } obj = NULL; out: write_unlock(&t->lock); CANCEL_RESTORE(svc); return obj; } void pvhash_init(struct pvhash_table *t) { pthread_mutexattr_t mattr; int n; for (n = 0; n < HASHSLOTS; n++) pvlist_init(&t->table[n].obj_list); pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, mutex_type_attribute); pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT); pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_PRIVATE); __RT(pthread_mutex_init(&t->lock, &mattr)); pthread_mutexattr_destroy(&mattr); } static struct pvhash_bucket *do_pvhash(struct pvhash_table *t, const void *key, size_t len) { unsigned int hash = __hash_key(key, len, 0); return &t->table[hash & (HASHSLOTS-1)]; } int __pvhash_enter(struct pvhash_table *t, const void *key, size_t len, struct pvhashobj *newobj, const struct pvhash_operations *hops, int nodup) { struct pvhash_bucket *bucket; struct pvhashobj *obj; int ret = 0; pvholder_init(&newobj->link); newobj->key = key; newobj->len = len; bucket = do_pvhash(t, key, len); write_lock_nocancel(&t->lock); if (nodup && !pvlist_empty(&bucket->obj_list)) { pvlist_for_each_entry(obj, &bucket->obj_list, link) { if (obj->len != newobj->len) continue; if (hops->compare(obj->key, newobj->key, len) == 0) { ret = -EEXIST; goto out; } } } pvlist_append(&newobj->link, &bucket->obj_list); out: write_unlock(&t->lock); return ret; } int pvhash_remove(struct pvhash_table *t, struct pvhashobj *delobj, const struct pvhash_operations *hops) { struct pvhash_bucket *bucket; struct pvhashobj *obj; int ret = -ESRCH; bucket = do_pvhash(t, delobj->key, delobj->len); write_lock_nocancel(&t->lock); if (!pvlist_empty(&bucket->obj_list)) { pvlist_for_each_entry(obj, &bucket->obj_list, link) { if (obj == delobj) { pvlist_remove_init(&obj->link); ret = 0; goto out; } } } out: write_unlock(&t->lock); return __bt(ret); } struct pvhashobj *pvhash_search(struct pvhash_table *t, const void *key, size_t len, const struct pvhash_operations *hops) { struct pvhash_bucket *bucket; struct pvhashobj *obj; bucket = do_pvhash(t, key, len); read_lock_nocancel(&t->lock); if (!pvlist_empty(&bucket->obj_list)) { pvlist_for_each_entry(obj, &bucket->obj_list, link) { if (obj->len != len) continue; if (hops->compare(obj->key, key, len) == 0) goto out; } } obj = NULL; out: read_unlock(&t->lock); return obj; } int pvhash_walk(struct pvhash_table *t, pvhash_walk_op walk, void *arg) { struct pvhash_bucket *bucket; struct pvhashobj *obj, *tmp; int ret, n; read_lock_nocancel(&t->lock); for (n = 0; n < HASHSLOTS; n++) { bucket = &t->table[n]; if (pvlist_empty(&bucket->obj_list)) continue; pvlist_for_each_entry_safe(obj, tmp, &bucket->obj_list, link) { read_unlock(&t->lock); ret = walk(t, obj, arg); if (ret) return __bt(ret); read_lock_nocancel(&t->lock); } } read_unlock(&t->lock); return 0; } #else /* !CONFIG_XENO_PSHARED */ static inline int store_key(struct hashobj *obj, const void *key, size_t len, const struct hash_operations *hops) { obj->key = key; obj->len = len; return 0; } static inline void drop_key(struct hashobj *obj, const struct hash_operations *hops) { } #endif /* !CONFIG_XENO_PSHARED */