927 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			927 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2016 MariaDB Corporation Ab
 | |
|  *
 | |
|  * Use of this software is governed by the Business Source License included
 | |
|  * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 | |
|  *
 | |
|  * Change Date: 2020-01-01
 | |
|  *
 | |
|  * On the date above, in accordance with the Business Source License, use
 | |
|  * of this software will be governed by version 2 or later of the General
 | |
|  * Public License.
 | |
|  */
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <unistd.h>
 | |
| #include <string.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <fcntl.h>
 | |
| #include <errno.h>
 | |
| #include <maxscale/alloc.h>
 | |
| #include <maxscale/atomic.h>
 | |
| #include <maxscale/hashtable.h>
 | |
| 
 | |
| /**
 | |
|  * @file hashtable.c General purpose hashtable routines
 | |
|  *
 | |
|  * The hashtable can be create with a custom number of hash buckets,
 | |
|  * a hash function and optional functions to call make copies of the key
 | |
|  * and value and to free them.
 | |
|  *
 | |
|  * The hashtable is arrange as a set of linked lists, the number of linked
 | |
|  * lists being the hashsize as requested by the user. Entries are hashed by
 | |
|  * calling the hash function that is passed in by the user, this is used as
 | |
|  * an index into the array of linked lists, usign modulo hashsize.
 | |
|  *
 | |
|  * The linked lists are searched using the key comparison function that is
 | |
|  * passed into the hash table creation routine.
 | |
|  *
 | |
|  * By default the hash table keeps the original pointers that are passed in
 | |
|  * for the keys and values, however two functions can be supplied to copy these
 | |
|  * a copy function and a free function. Please note the same function is used for
 | |
|  * the key and the value, if the actions required are different the called functions
 | |
|  * must understand how to differenate the key and value.
 | |
|  *
 | |
|  * The hash table implements a single write, multiple reader locking policy by
 | |
|  * using a pair of counters and a spinlock. The spinlock is used to protect the
 | |
|  * number of readers and writers counters when taking out locks. Releasing of
 | |
|  * locks uses pure atomic actions and thus does not require spinlock protection.
 | |
|  *
 | |
|  * @verbatim
 | |
|  * Revision History
 | |
|  *
 | |
|  * Date         Who                     Description
 | |
|  * 23/06/2013   Mark Riddoch            Initial implementation
 | |
|  * 23/07/2013   Mark Riddoch            Addition of hashtable iterator
 | |
|  * 08/01/2014   Massimiliano Pinto      Added copy and free funtion pointers for keys and values:
 | |
|  *                                      it's possible to copy and free different data types via
 | |
|  *                                      kcopyfn/kfreefn, vcopyfn/vfreefn
 | |
|  * 06/02/2015   Mark Riddoch            Addition of hashtable_save and hashtable_load
 | |
|  *
 | |
|  * @endverbatim
 | |
|  */
 | |
| 
 | |
| static  void hashtable_read_lock(HASHTABLE *table);
 | |
| static  void hashtable_read_unlock(HASHTABLE *table);
 | |
| static  void hashtable_write_lock(HASHTABLE *table);
 | |
| static  void hashtable_write_unlock(HASHTABLE *table);
 | |
| static HASHTABLE *hashtable_alloc_real(HASHTABLE* target,
 | |
|                                        int size,
 | |
|                                        HASHHASHFN hashfn,
 | |
|                                        HASHCMPFN cmpfn);
 | |
| 
 | |
| /**
 | |
|  * Special identity function used as default key/value copy function in the hashtable
 | |
|  * implementation. This avoids having to special case the code that manipulates
 | |
|  * the keys and values
 | |
|  *
 | |
|  * @param       data    The data pointer
 | |
|  * @return      Return the value we were called with
 | |
|  */
 | |
| static void *
 | |
| identityfn(const void *data)
 | |
| {
 | |
|     return (void*)data;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Special null function used as default free function in the hashtable
 | |
|  * implementation. This avoids having to special case the code that manipulates
 | |
|  * the keys and values
 | |
|  *
 | |
|  * @param       data    The data pointer
 | |
|  */
 | |
| static void
 | |
| nullfn(void *data)
 | |
| {
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Allocate a new hash table.
 | |
|  *
 | |
|  * The hashtable must have a size of at least one, however to be of any
 | |
|  * practical use a larger size sould be chosen as the size relates to the number
 | |
|  * of has buckets in the table.
 | |
|  *
 | |
|  * @param size          The size of the hash table, msut be > 0
 | |
|  * @param hashfn        The user supplied hash function
 | |
|  * @param cmpfn         The user supplied key comparison function
 | |
|  * @return The hashtable table
 | |
|  */
 | |
| HASHTABLE *
 | |
| hashtable_alloc(int size, HASHHASHFN hashfn, HASHCMPFN cmpfn)
 | |
| {
 | |
|     return hashtable_alloc_real(NULL, size, hashfn, cmpfn);
 | |
| }
 | |
| 
 | |
| HASHTABLE* hashtable_alloc_flat(HASHTABLE* target,
 | |
|                                 int size,
 | |
|                                 HASHHASHFN hashfn,
 | |
|                                 HASHCMPFN cmpfn)
 | |
| {
 | |
|     return hashtable_alloc_real(target, size, hashfn, cmpfn);
 | |
| }
 | |
| 
 | |
| static HASHTABLE *
 | |
| hashtable_alloc_real(HASHTABLE* target,
 | |
|                      int        size,
 | |
|                      HASHHASHFN hashfn,
 | |
|                      HASHCMPFN  cmpfn)
 | |
| {
 | |
|     HASHTABLE *rval;
 | |
| 
 | |
|     if (target == NULL)
 | |
|     {
 | |
|         if ((rval = (HASHTABLE*)MXS_MALLOC(sizeof(HASHTABLE))) == NULL)
 | |
|         {
 | |
|             return NULL;
 | |
|         }
 | |
|         rval->ht_isflat = false;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         rval = target;
 | |
|         rval->ht_isflat = true;
 | |
|     }
 | |
| 
 | |
| #if defined(SS_DEBUG)
 | |
|     rval->ht_chk_top = CHK_NUM_HASHTABLE;
 | |
|     rval->ht_chk_tail = CHK_NUM_HASHTABLE;
 | |
| #endif
 | |
|     rval->hashsize = size > 0 ? size : 1;
 | |
|     rval->hashfn = hashfn;
 | |
|     rval->cmpfn = cmpfn;
 | |
|     rval->kcopyfn = identityfn;
 | |
|     rval->vcopyfn = identityfn;
 | |
|     rval->kfreefn = nullfn;
 | |
|     rval->vfreefn = nullfn;
 | |
|     rval->n_readers = 0;
 | |
|     rval->writelock = 0;
 | |
|     rval->n_elements = 0;
 | |
|     spinlock_init(&rval->spin);
 | |
|     if ((rval->entries = (HASHENTRIES **)MXS_CALLOC(rval->hashsize, sizeof(HASHENTRIES *))) == NULL)
 | |
|     {
 | |
|         MXS_FREE(rval);
 | |
|         return NULL;
 | |
|     }
 | |
|     memset(rval->entries, 0, rval->hashsize * sizeof(HASHENTRIES *));
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Delete an entire hash table
 | |
|  *
 | |
|  * @param       table   The hash table to delete
 | |
|  */
 | |
| void
 | |
| hashtable_free(HASHTABLE *table)
 | |
| {
 | |
|     int i;
 | |
|     HASHENTRIES *entry, *ptr;
 | |
| 
 | |
|     if (table == NULL)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     hashtable_write_lock(table);
 | |
|     for (i = 0; i < table->hashsize; i++)
 | |
|     {
 | |
|         entry = table->entries[i];
 | |
|         while (entry)
 | |
|         {
 | |
|             ptr = entry->next;
 | |
|             table->kfreefn(entry->key);
 | |
|             table->vfreefn(entry->value);
 | |
|             MXS_FREE(entry);
 | |
|             entry = ptr;
 | |
|         }
 | |
|     }
 | |
|     MXS_FREE(table->entries);
 | |
| 
 | |
|     hashtable_write_unlock(table);
 | |
|     if (!table->ht_isflat)
 | |
|     {
 | |
|         MXS_FREE(table);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Provide memory management functions to the hash table. This allows
 | |
|  * function pointers to be registered that can make copies of the
 | |
|  * key and value and free them as well.
 | |
|  *
 | |
|  * @param table         The hash table
 | |
|  * @param kcopyfn       The copy function for the key
 | |
|  * @param vcopyfn       The copy function for the value
 | |
|  * @param kfreefn       The free function for the key
 | |
|  * @param vfreefn       The free function for the value
 | |
|  */
 | |
| void
 | |
| hashtable_memory_fns(HASHTABLE  *table,
 | |
|                      HASHCOPYFN kcopyfn,
 | |
|                      HASHCOPYFN vcopyfn,
 | |
|                      HASHFREEFN kfreefn,
 | |
|                      HASHFREEFN vfreefn)
 | |
| {
 | |
|     if (kcopyfn != NULL)
 | |
|     {
 | |
|         table->kcopyfn = kcopyfn;
 | |
|     }
 | |
|     if (vcopyfn != NULL)
 | |
|     {
 | |
|         table->vcopyfn = vcopyfn;
 | |
|     }
 | |
|     if (kfreefn != NULL)
 | |
|     {
 | |
|         table->kfreefn = kfreefn;
 | |
|     }
 | |
|     if (vfreefn != NULL)
 | |
|     {
 | |
|         table->vfreefn = vfreefn;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add an item to the hash table.
 | |
|  *
 | |
|  * @param table         The hash table to which to add the item
 | |
|  * @param key           The key of the item
 | |
|  * @param value         The value for the item
 | |
|  * @return      Return the number of items added
 | |
|  */
 | |
| int
 | |
| hashtable_add(HASHTABLE *table, void *key, void *value)
 | |
| {
 | |
|     unsigned int            hashkey;
 | |
|     HASHENTRIES     *entry;
 | |
| 
 | |
|     if (table == NULL || key == NULL || value == NULL)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     if (table->hashsize <= 0)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         hashkey = table->hashfn(key) % table->hashsize;
 | |
|     }
 | |
|     hashtable_write_lock(table);
 | |
|     entry = table->entries[hashkey % table->hashsize];
 | |
|     while (entry && table->cmpfn(key, entry->key) != 0)
 | |
|     {
 | |
|         entry = entry->next;
 | |
|     }
 | |
|     if (entry && table->cmpfn(key, entry->key) == 0)
 | |
|     {
 | |
|         /* Duplicate key value */
 | |
|         hashtable_write_unlock(table);
 | |
|         return 0;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         HASHENTRIES *ptr = (HASHENTRIES *)MXS_MALLOC(sizeof(HASHENTRIES));
 | |
|         if (ptr == NULL)
 | |
|         {
 | |
|             hashtable_write_unlock(table);
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         /* copy the key */
 | |
|         ptr->key = table->kcopyfn(key);
 | |
| 
 | |
|         /* check succesfull key copy */
 | |
|         if (ptr->key  == NULL)
 | |
|         {
 | |
|             MXS_FREE(ptr);
 | |
|             hashtable_write_unlock(table);
 | |
| 
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         /* copy the value */
 | |
|         ptr->value = table->vcopyfn(value);
 | |
| 
 | |
|         /* check succesfull value copy */
 | |
|         if  (ptr->value == NULL)
 | |
|         {
 | |
|             /* remove the key ! */
 | |
|             table->kfreefn(ptr->key);
 | |
|             MXS_FREE(ptr);
 | |
| 
 | |
|             /* value not copied, return */
 | |
|             hashtable_write_unlock(table);
 | |
| 
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         ptr->next = table->entries[hashkey % table->hashsize];
 | |
|         table->entries[hashkey % table->hashsize] = ptr;
 | |
|     }
 | |
|     table->n_elements++;
 | |
|     hashtable_write_unlock(table);
 | |
| 
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Delete an item from the hash table that has a given key
 | |
|  *
 | |
|  * @param table         The hash table to delete from
 | |
|  * @param key           The key value of the item to remove
 | |
|  * @return Return the number of items deleted
 | |
|  */
 | |
| int
 | |
| hashtable_delete(HASHTABLE *table, void *key)
 | |
| {
 | |
|     unsigned int hashkey;
 | |
|     HASHENTRIES *entry, *ptr;
 | |
| 
 | |
|     if (table == NULL || key == NULL)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     hashkey = table->hashfn(key) % table->hashsize;
 | |
|     hashtable_write_lock(table);
 | |
|     entry = table->entries[hashkey % table->hashsize];
 | |
|     while (entry && entry->key && table->cmpfn(key, entry->key) != 0)
 | |
|     {
 | |
|         entry = entry->next;
 | |
|     }
 | |
|     if (entry == NULL)
 | |
|     {
 | |
|         /* Not found */
 | |
|         hashtable_write_unlock(table);
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     if (entry == table->entries[hashkey % table->hashsize])
 | |
|     {
 | |
|         /* We are removing from the first entry */
 | |
|         table->entries[hashkey % table->hashsize] = entry->next;
 | |
|         table->kfreefn(entry->key);
 | |
|         table->vfreefn(entry->value);
 | |
| 
 | |
|         if (entry->next != NULL)
 | |
|         {
 | |
|             entry->key = entry->next->key;
 | |
|             entry->value = entry->next->value;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             entry->key = NULL;
 | |
|             entry->value = NULL;
 | |
|         }
 | |
|         MXS_FREE(entry);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         ptr = table->entries[hashkey % table->hashsize];
 | |
|         while (ptr && ptr->next != entry)
 | |
|         {
 | |
|             ptr = ptr->next;
 | |
|         }
 | |
|         if (ptr == NULL)
 | |
|         {
 | |
|             hashtable_write_unlock(table);
 | |
|             return 0;       /* This should never happen */
 | |
|         }
 | |
|         ptr->next = entry->next;
 | |
|         table->kfreefn(entry->key);
 | |
|         table->vfreefn(entry->value);
 | |
|         MXS_FREE(entry);
 | |
|     }
 | |
|     table->n_elements--;
 | |
|     assert(table->n_elements >= 0);
 | |
|     hashtable_write_unlock(table);
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Fetch an item with a given key value from the hash table
 | |
|  *
 | |
|  * @param table         The hash table
 | |
|  * @param key           The key value
 | |
|  * @return The item or NULL if the item was not found
 | |
|  */
 | |
| void *
 | |
| hashtable_fetch(HASHTABLE *table, void *key)
 | |
| {
 | |
|     unsigned int hashkey;
 | |
|     HASHENTRIES *entry;
 | |
| 
 | |
|     if (table == NULL || key == NULL || 0 == table->hashsize)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     hashkey = table->hashfn(key) % table->hashsize;
 | |
|     hashtable_read_lock(table);
 | |
|     entry = table->entries[hashkey % table->hashsize];
 | |
|     while (entry && entry->key && table->cmpfn(key, entry->key) != 0)
 | |
|     {
 | |
|         entry = entry->next;
 | |
|     }
 | |
|     if (entry == NULL)
 | |
|     {
 | |
|         hashtable_read_unlock(table);
 | |
|         return NULL;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         hashtable_read_unlock(table);
 | |
|         return entry->value;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Print hash table statistics to the standard output
 | |
|  *
 | |
|  * @param table         The hash table
 | |
|  */
 | |
| void
 | |
| hashtable_stats(HASHTABLE *table)
 | |
| {
 | |
|     int total, longest, i, j;
 | |
|     HASHENTRIES *entries;
 | |
| 
 | |
|     if (table == NULL)
 | |
|     {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     printf("Hashtable: %p, size %d\n", table, table->hashsize);
 | |
|     total = 0;
 | |
|     longest = 0;
 | |
|     hashtable_read_lock(table);
 | |
|     for (i = 0; i < table->hashsize; i++)
 | |
|     {
 | |
|         j = 0;
 | |
|         entries = table->entries[i];
 | |
|         while (entries)
 | |
|         {
 | |
|             j++;
 | |
|             entries = entries->next;
 | |
|         }
 | |
|         total += j;
 | |
|         if (j > longest)
 | |
|         {
 | |
|             longest = j;
 | |
|         }
 | |
|     }
 | |
|     hashtable_read_unlock(table);
 | |
|     printf("\tNo. of entries:       %d\n", total);
 | |
|     printf("\tAverage chain length: %.1f\n", (float)total / table->hashsize);
 | |
|     printf("\tLongest chain length: %d\n", longest);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Produces stat output about hashtable
 | |
|  *
 | |
|  * Parameters:
 | |
|  * @param table - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param hashsize - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param nelems - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @param longest - <usage>
 | |
|  *          <description>
 | |
|  *
 | |
|  * @return void
 | |
|  *
 | |
|  *
 | |
|  */
 | |
| void hashtable_get_stats(void* table,
 | |
|                          int*  hashsize,
 | |
|                          int*  nelems,
 | |
|                          int*  longest)
 | |
| {
 | |
|     HASHTABLE* ht;
 | |
|     HASHENTRIES* entries;
 | |
|     int i;
 | |
|     int j;
 | |
| 
 | |
|     *nelems = 0;
 | |
|     *longest = 0;
 | |
|     *hashsize = 0;
 | |
| 
 | |
|     if (table != NULL)
 | |
|     {
 | |
|         ht = (HASHTABLE *)table;
 | |
|         CHK_HASHTABLE(ht);
 | |
|         hashtable_read_lock(ht);
 | |
| 
 | |
|         for (i = 0; i < ht->hashsize; i++)
 | |
|         {
 | |
|             j = 0;
 | |
|             entries = ht->entries[i];
 | |
|             while (entries)
 | |
|             {
 | |
|                 j++;
 | |
|                 entries = entries->next;
 | |
|             }
 | |
|             *nelems += j;
 | |
|             if (j > *longest)
 | |
|             {
 | |
|                 *longest = j;
 | |
|             }
 | |
|         }
 | |
|         *hashsize = ht->hashsize;
 | |
|         hashtable_read_unlock(ht);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Take a read lock on the hashtable.
 | |
|  *
 | |
|  * The hashtable support multiple readers and a single writer,
 | |
|  * we have a spinlock to protect the two counts, n_readers and
 | |
|  * writelock.
 | |
|  *
 | |
|  * We take the hashtable spinlock and then check that writelock
 | |
|  * is set to zero. If not we release the spinlock and do dirty
 | |
|  * reads of writelock until it goes to 0. Once it is zero we
 | |
|  * acquire the spinlock again and test that writelock is still
 | |
|  * 0.
 | |
|  *
 | |
|  * With writelock set to zero we increment n_readers with the
 | |
|  * spinlock still held.
 | |
|  *
 | |
|  * @param table         The hashtable to lock.
 | |
|  */
 | |
| static void
 | |
| hashtable_read_lock(HASHTABLE *table)
 | |
| {
 | |
|     spinlock_acquire(&table->spin);
 | |
|     while (table->writelock)
 | |
|     {
 | |
|         spinlock_release(&table->spin);
 | |
|         while (atomic_add(&table->writelock, 1) != 0)
 | |
|         {
 | |
|             atomic_add(&table->writelock, -1);
 | |
|         }
 | |
|         atomic_add(&table->writelock, -1);
 | |
|         spinlock_acquire(&table->spin);
 | |
|     }
 | |
|     atomic_add(&table->n_readers, 1);
 | |
|     spinlock_release(&table->spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Release a previously obtained readlock.
 | |
|  *
 | |
|  * Simply decrement the n_readers value for the hash table
 | |
|  *
 | |
|  * @param table         The hash table to unlock
 | |
|  */
 | |
| static void
 | |
| hashtable_read_unlock(HASHTABLE *table)
 | |
| {
 | |
|     atomic_add(&table->n_readers, -1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Obtain an exclusive write lock for the hash table.
 | |
|  *
 | |
|  * We acquire the hashtable spinlock, check for the number of
 | |
|  * readers beign zero. If it is not we hold the spinlock and
 | |
|  * loop waiting for the n_readers to reach zero. This will prevent
 | |
|  * any new readers beign granted access but will not prevent current
 | |
|  * readers releasing the read lock.
 | |
|  *
 | |
|  * Once we have no readers we increment writelock and test if we are
 | |
|  * the only writelock holder, if not we repeat the process. We hold
 | |
|  * the spinlock throughout the process since both read and write
 | |
|  * locks do not require the spinlock to be acquired.
 | |
|  *
 | |
|  * @param table The table to lock for updates
 | |
|  */
 | |
| static void
 | |
| hashtable_write_lock(HASHTABLE *table)
 | |
| {
 | |
|     int available;
 | |
| 
 | |
|     spinlock_acquire(&table->spin);
 | |
|     do
 | |
|     {
 | |
|         while (atomic_add(&table->n_readers, 1) != 0)
 | |
|         {
 | |
|             atomic_add(&table->n_readers, -1);
 | |
|         }
 | |
|         atomic_add(&table->n_readers, -1);
 | |
|         available = atomic_add(&table->writelock, 1);
 | |
|         if (available != 0)
 | |
|         {
 | |
|             atomic_add(&table->writelock, -1);
 | |
|         }
 | |
|     }
 | |
|     while (available != 0);
 | |
|     spinlock_release(&table->spin);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Release the write lock on the hash table.
 | |
|  *
 | |
|  * @param table The hash table to unlock
 | |
|  */
 | |
| static void
 | |
| hashtable_write_unlock(HASHTABLE *table)
 | |
| {
 | |
|     atomic_add(&table->writelock, -1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Create an iterator on a hash table
 | |
|  *
 | |
|  * @param table         The table to ceate an iterator on
 | |
|  * @return      An iterator to use in future calls
 | |
|  */
 | |
| HASHITERATOR *
 | |
| hashtable_iterator(HASHTABLE *table)
 | |
| {
 | |
|     HASHITERATOR *rval = (HASHITERATOR *)MXS_MALLOC(sizeof(HASHITERATOR));
 | |
| 
 | |
|     if (rval)
 | |
|     {
 | |
|         rval->table = table;
 | |
|         rval->chain = 0;
 | |
|         rval->depth = -1;
 | |
|     }
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the next key for a hashtable iterator
 | |
|  *
 | |
|  * @param iter  The hashtable iterator
 | |
|  * @return      The next key value or NULL
 | |
|  */
 | |
| void *
 | |
| hashtable_next(HASHITERATOR *iter)
 | |
| {
 | |
|     int i;
 | |
|     HASHENTRIES *entries;
 | |
| 
 | |
|     if (iter == NULL)
 | |
|     {
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     iter->depth++;
 | |
|     while (iter->chain < iter->table->hashsize)
 | |
|     {
 | |
|         hashtable_read_lock(iter->table);
 | |
|         if ((entries = iter->table->entries[iter->chain]) != NULL)
 | |
|         {
 | |
|             i = 0;
 | |
|             while (entries && i < iter->depth)
 | |
|             {
 | |
|                 entries = entries->next;
 | |
|                 i++;
 | |
|             }
 | |
|             hashtable_read_unlock(iter->table);
 | |
|             if (entries)
 | |
|             {
 | |
|                 return entries->key;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             hashtable_read_unlock(iter->table);
 | |
|         }
 | |
|         iter->depth = 0;
 | |
|         iter->chain++;
 | |
|     }
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Free a hashtable iterator
 | |
|  *
 | |
|  * @param iter  The iterator to free
 | |
|  */
 | |
| void
 | |
| hashtable_iterator_free(HASHITERATOR *iter)
 | |
| {
 | |
|     MXS_FREE(iter);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Save a hashtable to disk
 | |
|  *
 | |
|  * @param table         Hashtable to save
 | |
|  * @param filename      Filename to write hashtable into
 | |
|  * @param keywrite      Pointer to function that writes a single key
 | |
|  * @param valuewrite    Pointer to function that writes a single value
 | |
|  * @return              Number of entries written or -1 on error
 | |
|  */
 | |
| int
 | |
| hashtable_save(HASHTABLE *table, const char *filename,
 | |
|                int (*keywrite)(int, void*),
 | |
|                int (*valuewrite)(int, void*))
 | |
| {
 | |
|     int fd, rval = 0;
 | |
|     HASHITERATOR *iter;
 | |
|     void *key, *value;
 | |
| 
 | |
|     if ((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1)
 | |
|     {
 | |
|         return -1;
 | |
|     }
 | |
|     if (write(fd, "HASHTABLE", 7) != 7)     // Magic number
 | |
|     {
 | |
|         close(fd);
 | |
|         return -1;
 | |
|     }
 | |
|     if (write(fd, &rval, sizeof(rval)) == -1) // Write zero counter, will be overrwriten at end
 | |
|     {
 | |
|         MXS_ERROR("Failed to write hashtable item count: %d, %s", errno,
 | |
|                   mxs_strerror(errno));
 | |
|     }
 | |
|     if ((iter = hashtable_iterator(table)) != NULL)
 | |
|     {
 | |
|         while ((key = hashtable_next(iter)) != NULL)
 | |
|         {
 | |
|             if (!(*keywrite)(fd, key))
 | |
|             {
 | |
|                 close(fd);
 | |
|                 hashtable_iterator_free(iter);
 | |
|                 return -1;
 | |
|             }
 | |
|             if ((value = hashtable_fetch(table, key)) == NULL ||
 | |
|                 (*valuewrite)(fd, value) == 0)
 | |
|             {
 | |
|                 close(fd);
 | |
|                 hashtable_iterator_free(iter);
 | |
|                 return -1;
 | |
|             }
 | |
|             rval++;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* Now go back and write the count of entries */
 | |
|     if (lseek(fd, 7L, SEEK_SET) != -1)
 | |
|     {
 | |
|         if (write(fd, &rval, sizeof(rval)) == -1)
 | |
|         {
 | |
|             MXS_ERROR("Failed to write hashtable item count: %d, %s", errno,
 | |
|                       mxs_strerror(errno));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     close(fd);
 | |
|     hashtable_iterator_free(iter);
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Load a hashtable from disk
 | |
|  *
 | |
|  * @param table         Hashtable to load
 | |
|  * @param filename      Filename to read hashtable from
 | |
|  * @param keyread       Pointer to function that reads a single key
 | |
|  * @param valueread     Pointer to function that reads a single value
 | |
|  * @return              Number of entries read or -1 on error
 | |
|  */
 | |
| int
 | |
| hashtable_load(HASHTABLE *table, const char *filename,
 | |
|                void *(*keyread)(int),
 | |
|                void *(*valueread)(int))
 | |
| {
 | |
|     int fd, count, rval = 0;
 | |
|     void *key, *value;
 | |
|     char buf[40];
 | |
| 
 | |
|     if ((fd = open(filename, O_RDONLY)) == -1)
 | |
|     {
 | |
|         return -1;
 | |
|     }
 | |
|     if (read(fd, buf, 7) != 7)
 | |
|     {
 | |
|         close(fd);
 | |
|         return -1;
 | |
|     }
 | |
|     if (strncmp(buf, "HASHTABLE", 7) != 0)
 | |
|     {
 | |
|         close(fd);
 | |
|         return -1;
 | |
|     }
 | |
|     if (read(fd, &count, sizeof(count)) != sizeof(count))
 | |
|     {
 | |
|         close(fd);
 | |
|         return -1;
 | |
|     }
 | |
|     while (count--)
 | |
|     {
 | |
|         key = keyread(fd);
 | |
|         value = valueread(fd);
 | |
|         if (key == NULL || value == NULL)
 | |
|         {
 | |
|             break;
 | |
|         }
 | |
|         hashtable_add(table, key, value);
 | |
|         rval++;
 | |
|     }
 | |
| 
 | |
|     close(fd);
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Return the number of elements added to the hashtable
 | |
|  * @param table Hashtable to measure
 | |
|  * @return Number of inserted elements or 0 if table is NULL
 | |
|  */
 | |
| int hashtable_size(HASHTABLE *table)
 | |
| {
 | |
|     assert(table);
 | |
|     spinlock_acquire(&table->spin);
 | |
|     int rval = table->n_elements;
 | |
|     spinlock_release(&table->spin);
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Frees memory assumed to have been allocated using one of the MaxScale
 | |
|  * allocation functions. Intended to be used together with a hashtable
 | |
|  * copy function that merely makes a straight memory copy of the key/value,
 | |
|  * e.g. hashtable_item_strdup.
 | |
|  * @param data The memory to be freed.
 | |
|  */
 | |
| void hashtable_item_free(void *data)
 | |
| {
 | |
|     MXS_FREE(data);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Convenience function intended for use as the comparison function of a hashtable,
 | |
|  * when the key is a NULL terminated string. Behaves as strcasecmp.
 | |
|  * @param str1 Pointer to string.
 | |
|  * @param str2 Pointer to string.
 | |
|  * @return Same as strcasecmp.
 | |
|  */
 | |
| int hashtable_item_strcasecmp(const void *str1, const void *str2)
 | |
| {
 | |
|     return strcasecmp((const char*)str1, (const char*)str2);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Convenience function intended for use as the comparison function of a hashtable,
 | |
|  * when the key is a NULL terminated string. Behaves as strcmp.
 | |
|  * @param str1 Pointer to string.
 | |
|  * @param str2 Pointer to string.
 | |
|  * @return Same as strcmp.
 | |
|  */
 | |
| int hashtable_item_strcmp(const void *str1, const void *str2)
 | |
| {
 | |
|     return strcmp((const char*)str1, (const char*)str2);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Convenience function intended for use as the copy function of a hashtable,
 | |
|  * when the key/value is a NULL terminated string.
 | |
|  * @param data A pointer to a NULL terminated string.
 | |
|  * @return A copy of the provided string or NULL if memory
 | |
|  *         allocation fails.
 | |
|  */
 | |
| void* hashtable_item_strdup(const void* data)
 | |
| {
 | |
|     return MXS_STRDUP((const char*)data);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Convenience function intended for use as the hash function of a hashtable,
 | |
|  * when the key is a NULL terminated string.
 | |
|  * @param data A pointer to a NULL terminated string.
 | |
|  * @return A hash of the string.
 | |
|  */
 | |
| int hashtable_item_strhash(const void* data)
 | |
| {
 | |
|     int hash = 0;
 | |
| 
 | |
|     if (data)
 | |
|     {
 | |
|         const char* key = (const char*)data;
 | |
| 
 | |
|         int c;
 | |
| 
 | |
|         while ((c = *key++))
 | |
|         {
 | |
|             hash = c + (hash << 6) + (hash << 16) - hash;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return hash;
 | |
| }
 | 
