773 lines
18 KiB
C
773 lines
18 KiB
C
/*
|
|
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
|
|
* software: you can redistribute it and/or modify it under the terms of the
|
|
* GNU General Public License as published by the Free Software Foundation,
|
|
* version 2.
|
|
*
|
|
* This program 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 General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright MariaDB Corporation Ab 2013-2014
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <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,
|
|
int (*hashfn)(),
|
|
int (*cmpfn)());
|
|
|
|
/**
|
|
* Special null function used as default memory allfunctions 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 *
|
|
nullfn(void *data)
|
|
{
|
|
return 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, int (*hashfn)(), int (*cmpfn)())
|
|
{
|
|
return hashtable_alloc_real(NULL, size, hashfn, cmpfn);
|
|
}
|
|
|
|
HASHTABLE* hashtable_alloc_flat(
|
|
HASHTABLE* target,
|
|
int size,
|
|
int (*hashfn)(),
|
|
int (*cmpfn)())
|
|
{
|
|
return hashtable_alloc_real(target, size, hashfn, cmpfn);
|
|
}
|
|
|
|
static HASHTABLE *
|
|
hashtable_alloc_real(
|
|
HASHTABLE* target,
|
|
int size,
|
|
int (*hashfn)(),
|
|
int (*cmpfn)())
|
|
{
|
|
HASHTABLE *rval;
|
|
|
|
if (target == NULL)
|
|
{
|
|
if ((rval = 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 = nullfn;
|
|
rval->vcopyfn = nullfn;
|
|
rval->kfreefn = nullfn;
|
|
rval->vfreefn = nullfn;
|
|
rval->n_readers = 0;
|
|
rval->writelock = 0;
|
|
spinlock_init(&rval->spin);
|
|
if ((rval->entries = (HASHENTRIES **)calloc(rval->hashsize, sizeof(HASHENTRIES *))) == NULL)
|
|
{
|
|
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);
|
|
free(entry);
|
|
entry = ptr;
|
|
}
|
|
}
|
|
free(table->entries);
|
|
|
|
hashtable_write_unlock(table);
|
|
if (!table->ht_isflat)
|
|
{
|
|
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,
|
|
HASHMEMORYFN kcopyfn,
|
|
HASHMEMORYFN vcopyfn,
|
|
HASHMEMORYFN kfreefn,
|
|
HASHMEMORYFN 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 *)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) {
|
|
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);
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
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);
|
|
free(entry);
|
|
}
|
|
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)
|
|
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 (table->writelock)
|
|
;
|
|
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 (table->n_readers)
|
|
;
|
|
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;
|
|
|
|
if ((rval = (HASHITERATOR *)malloc(sizeof(HASHITERATOR))) != NULL)
|
|
{
|
|
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)
|
|
{
|
|
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, 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;
|
|
}
|
|
write(fd, &rval, sizeof(rval)); // Write zero counter, will be overrwriten at end
|
|
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)
|
|
{
|
|
write(fd, &rval, sizeof(rval));
|
|
}
|
|
|
|
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, 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;
|
|
}
|