/*
 * Copyright (c) 1984 through 2008, William LeFebvre
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 * 
 *     * Neither the name of William LeFebvre nor the names of other
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* hash.m4c */

/* The file hash.c is generated from hash.m4c via the preprocessor M4 */

/*
 * Hash table functions:  init, add, lookup, first, next, sizeinfo.
 * This is a conventional "bucket hash".  The key is hashed in to a number
 * less than or equal to the number of buckets and the result is used
 * to index in to the array of buckets.  Each bucket is a linked list
 * that contains all the key/value pairs which hashed to that index.
 */

#include "os.h"

#ifdef HAVE_MATH_H
#include <math.h>
#endif

#include "hash.h"

static int
next_prime(int x)

{
    double i, j;
    int f;

    i = x;
    while (i++)
    {
	f=1;
	for (j=2; j<i; j++)
	{
	    if ((i/j)==floor(i/j))
	    {
		f=0;
		break;
	    }
	}
	if (f)
	{
	    return (int)i;
	}
    }
    return 1;
}

/* string hashes */

static int
string_hash(hash_table *ht, char *key)

{
    unsigned long s = 0;
    unsigned char ch;
    int shifting = 24;

    while ((ch = (unsigned char)*key++) != '\0')
    {
	if (shifting == 0)
	{
	    s = s + ch;
	    shifting = 24;
	}
	else
	{
	    s = s + (ch << shifting);
	    shifting -= 8;
	}
    }

    return (s % ht->num_buckets);
}

static void ll_init(llist *q)

{
    q->head = NULL;
    q->count = 0;
}

static llistitem *ll_newitem(int size)

{
    llistitem *qi;

    qi = emalloc(sizeof(llistitem) + size);
    qi->datum = ((char *)qi + sizeof(llistitem));
    return qi;
}

static void ll_freeitem(llistitem *li)

{
    free(li);
}

static void ll_add(llist *q, llistitem *new)

{
    new->next = q->head;
    q->head = new;
    q->count++;
}

static void ll_extract(llist *q, llistitem *qi, llistitem *last)

{
    if (last == NULL)
    {
	q->head = qi->next;
    }
    else
    {
	last->next = qi->next;
    }
    qi->next = NULL;
    q->count--;
}

#define LL_FIRST(q) ((q)->head)
#define LL_NEXT(q, qi)  ((qi) != NULL ? (qi)->next : NULL)
#define LL_ISEMPTY(ll)  ((ll)->count == 0)

#ifdef notdef
static llistitem *
ll_first(llist *q)

{
    return q->head;
}

static llistitem *
ll_next(llist *q, llistitem *qi)

{
    return (qi != NULL ? qi->next : NULL);
}

static int
ll_isempty(llist *ll)

{
    return (ll->count == 0);
}
#endif

/*
 * hash_table *hash_create(int num)
 *
 * Creates a hash table structure with at least "num" buckets.
 */

hash_table *
hash_create(int num)

{
    hash_table *result;
    bucket_t *b;
    int bytes;
    int i;

    /* create the resultant structure */
    result = emalloc(sizeof(hash_table));

    /* adjust bucket count to be prime */
    num = next_prime(num);

    /* create the buckets */
    bytes = sizeof(bucket_t) * num;
    result->buckets = b = emalloc(bytes);
    result->num_buckets = num;

    /* create each bucket as a linked list */
    i = num;
    while (--i >= 0)
    {
	ll_init(&(b->list));
	b++;
    }

    return result;
}

/*
 * unsigned int hash_count(hash_table *ht)
 *
 * Return total number of elements contained in hash table.
 */

#ifdef notdef
static unsigned int
hash_count(hash_table *ht)

{
    register int i = 0;
    register int cnt = 0;
    register bucket_t *bucket;

    bucket = ht->buckets;
    while (i++ < ht->num_buckets)
    {
	cnt += bucket->list.count;
	bucket++;
    }

    return cnt;
}
#endif

/*
 * void hash_sizeinfo(unsigned int *sizes, int max, hash_table *ht)
 *
 * Fill in "sizes" with information about bucket lengths in hash
 * table "max".
 */

void 
hash_sizeinfo(unsigned int *sizes, int max, hash_table *ht)

{
    register int i;
    register int idx;
    register bucket_t *bucket;

    memzero(sizes, max * sizeof(unsigned int));

    bucket = ht->buckets;
    i = 0;
    while (i++ < ht->num_buckets)
    {
	idx = bucket->list.count;
	sizes[idx >= max ? max-1 : idx]++;
	bucket++;
    }
}







/*
 * void hash_add_uint(hash_table *ht, unsigned int key, void *value)
 *
 * Add an element to table "ht".  The element is described by
 * "key" and "value".  Return NULL on success.  If the key
 * already exists in the table, then no action is taken and
 * the data for the existing element is returned.
 * Key type is unsigned int
 */

void *
hash_add_uint(hash_table *ht, unsigned int key, void *value)

{
    bucket_t *bucket;
    hash_item_uint *hi;
    hash_item_uint *h;
    llist *ll;
    llistitem *li;
    llistitem *newli;
    unsigned int k1;

    /* allocate the space we will need */
    newli = ll_newitem(sizeof(hash_item_uint));
    hi = (hash_item_uint *)newli->datum;

    /* fill in the values */
    hi->key = key;
    hi->value = value;

    /* hash to the bucket */
    bucket = &(ht->buckets[(key % ht->num_buckets)]);

    /* walk the list to make sure we do not have a duplicate */
    ll = &(bucket->list);
    li = LL_FIRST(ll);
    while (li != NULL)
    {
	h = (hash_item_uint *)li->datum;
	k1 = h->key;
	if (key == k1)
	{
	    /* found one */
	    break;
	}
	li = LL_NEXT(ll, li);
    }
    li = NULL;

    /* is there already one there? */
    if (li == NULL)
    {
	/* add the unique element to the buckets list */
	ll_add(&(bucket->list), newli);
	return NULL;
    }
    else
    {
	/* free the stuff we just allocated */
	ll_freeitem(newli);
	return ((hash_item_uint *)(li->datum))->value;
    }
}

/*
 * void *hash_replace_uint(hash_table *ht, unsigned int key, void *value)
 *
 * Replace an existing value in the hash table with a new one and
 * return the old value.  If the key does not already exist in
 * the hash table, add a new element and return NULL.
 * Key type is unsigned int
 */

void *
hash_replace_uint(hash_table *ht, unsigned int key, void *value)

{
    bucket_t *bucket;
    hash_item_uint *hi;
    llist *ll;
    llistitem *li;
    void *result = NULL;
    unsigned int k1;

    /* find the bucket */
    bucket = &(ht->buckets[(key % ht->num_buckets)]);

    /* walk the list until we find the existing item */
    ll = &(bucket->list);
    li = LL_FIRST(ll);
    while (li != NULL)
    {
	hi = (hash_item_uint *)li->datum;
	k1 = hi->key;
	if (key == k1)
	{
	    /* found it: now replace the value with the new one */
	    result = hi->value;
	    hi->value = value;
	    break;
	}
	li = LL_NEXT(ll, li);
    }

    /* if the element wasnt found, add it */
    if (result == NULL)
    {
	li = ll_newitem(sizeof(hash_item_uint));
	hi = (hash_item_uint *)li->datum;
	hi->key = key;
	hi->value = value;
	ll_add(&(bucket->list), li);
    }

    /* return the old value (so it can be freed) */
    return result;
}

/*
 * void *hash_lookup_uint(hash_table *ht, unsigned int key)
 *
 * Look up "key" in "ht" and return the associated value.  If "key"
 * is not found, return NULL.  Key type is unsigned int
 */

void *
hash_lookup_uint(hash_table *ht, unsigned int key)

{
    bucket_t *bucket;
    llist *ll;
    llistitem *li;
    hash_item_uint *h;
    void *result;
    unsigned int k1;

    result = NULL;
    if ((bucket = &(ht->buckets[(key % ht->num_buckets)])) != NULL)
    {
	ll = &(bucket->list);
	li = LL_FIRST(ll);
	while (li != NULL)
	{
	    h = (hash_item_uint *)li->datum;
	    k1 = h->key;
	    if (key == k1)
	    {
		result = h->value;
		break;
	    }
	    li = LL_NEXT(ll, li);
	}
    }
    return result;
}

/*
 * void *hash_remove_uint(hash_table *ht, unsigned int key)
 *
 * Remove the element associated with "key" from the hash table
 * "ht".  Return the value or NULL if the key was not found.
 */

void *
hash_remove_uint(hash_table *ht, unsigned int key)

{
    bucket_t *bucket;
    llist *ll;
    llistitem *li;
    llistitem *lilast;
    hash_item_uint *hi;
    void *result;
    unsigned int k1;

    result = NULL;
    if ((bucket = &(ht->buckets[(key % ht->num_buckets)])) != NULL)
    {
	ll = &(bucket->list);
	li = LL_FIRST(ll);
	lilast = NULL;
	while (li != NULL)
	{
	    hi = (hash_item_uint *)li->datum;
	    k1 = hi->key;
	    if (key == k1)
	    {
		ll_extract(ll, li, lilast);
		result = hi->value;
		key = hi->key;
		;
		ll_freeitem(li);
		break;
	    }
	    lilast = li;
	    li = LL_NEXT(ll, li);
	}
    }
    return result;
}

/*
 * hash_item_uint *hash_first_uint(hash_table *ht, hash_pos *pos)
 *
 * First function to call when iterating through all items in the hash
 * table.  Returns the first item in "ht" and initializes "*pos" to track
 * the current position.
 */

hash_item_uint *
hash_first_uint(hash_table *ht, hash_pos *pos)

{
    /* initialize pos for first item in first bucket */
    pos->num_buckets = ht->num_buckets;
    pos->hash_bucket = ht->buckets;
    pos->curr = 0;
    pos->ll_last = NULL;

    /* find the first non-empty bucket */
    while(pos->hash_bucket->list.count == 0)
    {
	pos->hash_bucket++;
	if (++pos->curr >= pos->num_buckets)
	{
	    return NULL;
	}
    }

    /* set and return the first item */
    pos->ll_item = LL_FIRST(&(pos->hash_bucket->list));
    return (hash_item_uint *)pos->ll_item->datum;
}


/*
 * hash_item_uint *hash_next_uint(hash_pos *pos)
 *
 * Return the next item in the hash table, using "pos" as a description
 * of the present position in the hash table.  "pos" also identifies the
 * specific hash table.  Return NULL if there are no more elements.
 */

hash_item_uint *
hash_next_uint(hash_pos *pos)

{
    llistitem *li;

    /* move item to last and check for NULL */
    if ((pos->ll_last = pos->ll_item) == NULL)
    {
	/* are we really at the end of the hash table? */
	if (pos->curr >= pos->num_buckets)
	{
	    /* yes: return NULL */
	    return NULL;
	}
	/* no: regrab first item in current bucket list (might be NULL) */
	li = LL_FIRST(&(pos->hash_bucket->list));
    }
    else
    {
	/* get the next item in the llist */
	li = LL_NEXT(&(pos->hash_bucket->list), pos->ll_item);
    }

    /* if its NULL we have to find another bucket */
    while (li == NULL)
    {
	/* locate another bucket */
	pos->ll_last = NULL;

	/* move to the next one */
	pos->hash_bucket++;
	if (++pos->curr >= pos->num_buckets)
	{
	    /* at the end of the hash table */
	    pos->ll_item = NULL;
	    return NULL;
	}

	/* get the first element (might be NULL) */
	li = LL_FIRST(&(pos->hash_bucket->list));
    }

    /* li is the next element to dish out */
    pos->ll_item = li;
    return (hash_item_uint *)li->datum;
}

/*
 * void *hash_remove_pos_uint(hash_pos *pos)
 *
 * Remove the hash table entry pointed to by position marker "pos".
 * The data from the entry is returned upon success, otherwise NULL.
 */


void *
hash_remove_pos_uint(hash_pos *pos)

{
    llistitem *li;
    void *ans;
    hash_item_uint *hi;

    /* sanity checks */
    if (pos == NULL || pos->ll_last == pos->ll_item)
    {
	return NULL;
    }

    /* at this point pos contains the item to remove (ll_item)
       and its predecesor (ll_last) */
    /* extract the item from the llist */
    li = pos->ll_item;
    ll_extract(&(pos->hash_bucket->list), li, pos->ll_last);

    /* retain the data */
    hi = (hash_item_uint *)li->datum;
    ans = hi->value;

    ll_freeitem(li);

    /* back up to previous item */
    /* its okay for ll_item to be null: hash_next will detect it */
    pos->ll_item = pos->ll_last;

    return ans;
}



/*
 * void hash_add_pid(hash_table *ht, pid_t key, void *value)
 *
 * Add an element to table "ht".  The element is described by
 * "key" and "value".  Return NULL on success.  If the key
 * already exists in the table, then no action is taken and
 * the data for the existing element is returned.
 * Key type is pid_t
 */

void *
hash_add_pid(hash_table *ht, pid_t key, void *value)

{
    bucket_t *bucket;
    hash_item_pid *hi;
    hash_item_pid *h;
    llist *ll;
    llistitem *li;
    llistitem *newli;
    pid_t k1;

    /* allocate the space we will need */
    newli = ll_newitem(sizeof(hash_item_pid));
    hi = (hash_item_pid *)newli->datum;

    /* fill in the values */
    hi->key = key;
    hi->value = value;

    /* hash to the bucket */
    bucket = &(ht->buckets[(key % ht->num_buckets)]);

    /* walk the list to make sure we do not have a duplicate */
    ll = &(bucket->list);
    li = LL_FIRST(ll);
    while (li != NULL)
    {
	h = (hash_item_pid *)li->datum;
	k1 = h->key;
	if (key == k1)
	{
	    /* found one */
	    break;
	}
	li = LL_NEXT(ll, li);
    }
    li = NULL;

    /* is there already one there? */
    if (li == NULL)
    {
	/* add the unique element to the buckets list */
	ll_add(&(bucket->list), newli);
	return NULL;
    }
    else
    {
	/* free the stuff we just allocated */
	ll_freeitem(newli);
	return ((hash_item_pid *)(li->datum))->value;
    }
}

/*
 * void *hash_replace_pid(hash_table *ht, pid_t key, void *value)
 *
 * Replace an existing value in the hash table with a new one and
 * return the old value.  If the key does not already exist in
 * the hash table, add a new element and return NULL.
 * Key type is pid_t
 */

void *
hash_replace_pid(hash_table *ht, pid_t key, void *value)

{
    bucket_t *bucket;
    hash_item_pid *hi;
    llist *ll;
    llistitem *li;
    void *result = NULL;
    pid_t k1;

    /* find the bucket */
    bucket = &(ht->buckets[(key % ht->num_buckets)]);

    /* walk the list until we find the existing item */
    ll = &(bucket->list);
    li = LL_FIRST(ll);
    while (li != NULL)
    {
	hi = (hash_item_pid *)li->datum;
	k1 = hi->key;
	if (key == k1)
	{
	    /* found it: now replace the value with the new one */
	    result = hi->value;
	    hi->value = value;
	    break;
	}
	li = LL_NEXT(ll, li);
    }

    /* if the element wasnt found, add it */
    if (result == NULL)
    {
	li = ll_newitem(sizeof(hash_item_pid));
	hi = (hash_item_pid *)li->datum;
	hi->key = key;
	hi->value = value;
	ll_add(&(bucket->list), li);
    }

    /* return the old value (so it can be freed) */
    return result;
}

/*
 * void *hash_lookup_pid(hash_table *ht, pid_t key)
 *
 * Look up "key" in "ht" and return the associated value.  If "key"
 * is not found, return NULL.  Key type is pid_t
 */

void *
hash_lookup_pid(hash_table *ht, pid_t key)

{
    bucket_t *bucket;
    llist *ll;
    llistitem *li;
    hash_item_pid *h;
    void *result;
    pid_t k1;

    result = NULL;
    if ((bucket = &(ht->buckets[(key % ht->num_buckets)])) != NULL)
    {
	ll = &(bucket->list);
	li = LL_FIRST(ll);
	while (li != NULL)
	{
	    h = (hash_item_pid *)li->datum;
	    k1 = h->key;
	    if (key == k1)
	    {
		result = h->value;
		break;
	    }
	    li = LL_NEXT(ll, li);
	}
    }
    return result;
}

/*
 * void *hash_remove_pid(hash_table *ht, pid_t key)
 *
 * Remove the element associated with "key" from the hash table
 * "ht".  Return the value or NULL if the key was not found.
 */

void *
hash_remove_pid(hash_table *ht, pid_t key)

{
    bucket_t *bucket;
    llist *ll;
    llistitem *li;
    llistitem *lilast;
    hash_item_pid *hi;
    void *result;
    pid_t k1;

    result = NULL;
    if ((bucket = &(ht->buckets[(key % ht->num_buckets)])) != NULL)
    {
	ll = &(bucket->list);
	li = LL_FIRST(ll);
	lilast = NULL;
	while (li != NULL)
	{
	    hi = (hash_item_pid *)li->datum;
	    k1 = hi->key;
	    if (key == k1)
	    {
		ll_extract(ll, li, lilast);
		result = hi->value;
		key = hi->key;
		;
		ll_freeitem(li);
		break;
	    }
	    lilast = li;
	    li = LL_NEXT(ll, li);
	}
    }
    return result;
}

/*
 * hash_item_pid *hash_first_pid(hash_table *ht, hash_pos *pos)
 *
 * First function to call when iterating through all items in the hash
 * table.  Returns the first item in "ht" and initializes "*pos" to track
 * the current position.
 */

hash_item_pid *
hash_first_pid(hash_table *ht, hash_pos *pos)

{
    /* initialize pos for first item in first bucket */
    pos->num_buckets = ht->num_buckets;
    pos->hash_bucket = ht->buckets;
    pos->curr = 0;
    pos->ll_last = NULL;

    /* find the first non-empty bucket */
    while(pos->hash_bucket->list.count == 0)
    {
	pos->hash_bucket++;
	if (++pos->curr >= pos->num_buckets)
	{
	    return NULL;
	}
    }

    /* set and return the first item */
    pos->ll_item = LL_FIRST(&(pos->hash_bucket->list));
    return (hash_item_pid *)pos->ll_item->datum;
}


/*
 * hash_item_pid *hash_next_pid(hash_pos *pos)
 *
 * Return the next item in the hash table, using "pos" as a description
 * of the present position in the hash table.  "pos" also identifies the
 * specific hash table.  Return NULL if there are no more elements.
 */

hash_item_pid *
hash_next_pid(hash_pos *pos)

{
    llistitem *li;

    /* move item to last and check for NULL */
    if ((pos->ll_last = pos->ll_item) == NULL)
    {
	/* are we really at the end of the hash table? */
	if (pos->curr >= pos->num_buckets)
	{
	    /* yes: return NULL */
	    return NULL;
	}
	/* no: regrab first item in current bucket list (might be NULL) */
	li = LL_FIRST(&(pos->hash_bucket->list));
    }
    else
    {
	/* get the next item in the llist */
	li = LL_NEXT(&(pos->hash_bucket->list), pos->ll_item);
    }

    /* if its NULL we have to find another bucket */
    while (li == NULL)
    {
	/* locate another bucket */
	pos->ll_last = NULL;

	/* move to the next one */
	pos->hash_bucket++;
	if (++pos->curr >= pos->num_buckets)
	{
	    /* at the end of the hash table */
	    pos->ll_item = NULL;
	    return NULL;
	}

	/* get the first element (might be NULL) */
	li = LL_FIRST(&(pos->hash_bucket->list));
    }

    /* li is the next element to dish out */
    pos->ll_item = li;
    return (hash_item_pid *)li->datum;
}

/*
 * void *hash_remove_pos_pid(hash_pos *pos)
 *
 * Remove the hash table entry pointed to by position marker "pos".
 * The data from the entry is returned upon success, otherwise NULL.
 */


void *
hash_remove_pos_pid(hash_pos *pos)

{
    llistitem *li;
    void *ans;
    hash_item_pid *hi;

    /* sanity checks */
    if (pos == NULL || pos->ll_last == pos->ll_item)
    {
	return NULL;
    }

    /* at this point pos contains the item to remove (ll_item)
       and its predecesor (ll_last) */
    /* extract the item from the llist */
    li = pos->ll_item;
    ll_extract(&(pos->hash_bucket->list), li, pos->ll_last);

    /* retain the data */
    hi = (hash_item_pid *)li->datum;
    ans = hi->value;

    /* free up the space */
    ll_freeitem(li);

    /* back up to previous item */
    /* its okay for ll_item to be null: hash_next will detect it */
    pos->ll_item = pos->ll_last;

    return ans;
}



/*
 * void hash_add_string(hash_table *ht, char * key, void *value)
 *
 * Add an element to table "ht".  The element is described by
 * "key" and "value".  Return NULL on success.  If the key
 * already exists in the table, then no action is taken and
 * the data for the existing element is returned.
 * Key type is char *
 */

void *
hash_add_string(hash_table *ht, char * key, void *value)

{
    bucket_t *bucket;
    hash_item_string *hi;
    hash_item_string *h;
    llist *ll;
    llistitem *li;
    llistitem *newli;
    char * k1;

    /* allocate the space we will need */
    newli = ll_newitem(sizeof(hash_item_string));
    hi = (hash_item_string *)newli->datum;

    /* fill in the values */
    hi->key = estrdup(key);
    hi->value = value;

    /* hash to the bucket */
    bucket = &(ht->buckets[string_hash(ht, key)]);

    /* walk the list to make sure we do not have a duplicate */
    ll = &(bucket->list);
    li = LL_FIRST(ll);
    while (li != NULL)
    {
	h = (hash_item_string *)li->datum;
	k1 = h->key;
	if (strcmp(key, k1) == 0)
	{
	    /* found one */
	    break;
	}
	li = LL_NEXT(ll, li);
    }
    li = NULL;

    /* is there already one there? */
    if (li == NULL)
    {
	/* add the unique element to the buckets list */
	ll_add(&(bucket->list), newli);
	return NULL;
    }
    else
    {
	/* free the stuff we just allocated */
	ll_freeitem(newli);
	return ((hash_item_string *)(li->datum))->value;
    }
}

/*
 * void *hash_replace_string(hash_table *ht, char * key, void *value)
 *
 * Replace an existing value in the hash table with a new one and
 * return the old value.  If the key does not already exist in
 * the hash table, add a new element and return NULL.
 * Key type is char *
 */

void *
hash_replace_string(hash_table *ht, char * key, void *value)

{
    bucket_t *bucket;
    hash_item_string *hi;
    llist *ll;
    llistitem *li;
    void *result = NULL;
    char * k1;

    /* find the bucket */
    bucket = &(ht->buckets[string_hash(ht, key)]);

    /* walk the list until we find the existing item */
    ll = &(bucket->list);
    li = LL_FIRST(ll);
    while (li != NULL)
    {
	hi = (hash_item_string *)li->datum;
	k1 = hi->key;
	if (strcmp(key, k1) == 0)
	{
	    /* found it: now replace the value with the new one */
	    result = hi->value;
	    hi->value = value;
	    break;
	}
	li = LL_NEXT(ll, li);
    }

    /* if the element wasnt found, add it */
    if (result == NULL)
    {
	li = ll_newitem(sizeof(hash_item_string));
	hi = (hash_item_string *)li->datum;
	hi->key = estrdup(key);
	hi->value = value;
	ll_add(&(bucket->list), li);
    }

    /* return the old value (so it can be freed) */
    return result;
}

/*
 * void *hash_lookup_string(hash_table *ht, char * key)
 *
 * Look up "key" in "ht" and return the associated value.  If "key"
 * is not found, return NULL.  Key type is char *
 */

void *
hash_lookup_string(hash_table *ht, char * key)

{
    bucket_t *bucket;
    llist *ll;
    llistitem *li;
    hash_item_string *h;
    void *result;
    char * k1;

    result = NULL;
    if ((bucket = &(ht->buckets[string_hash(ht, key)])) != NULL)
    {
	ll = &(bucket->list);
	li = LL_FIRST(ll);
	while (li != NULL)
	{
	    h = (hash_item_string *)li->datum;
	    k1 = h->key;
	    if (strcmp(key, k1) == 0)
	    {
		result = h->value;
		break;
	    }
	    li = LL_NEXT(ll, li);
	}
    }
    return result;
}

/*
 * void *hash_remove_string(hash_table *ht, char * key)
 *
 * Remove the element associated with "key" from the hash table
 * "ht".  Return the value or NULL if the key was not found.
 */

void *
hash_remove_string(hash_table *ht, char * key)

{
    bucket_t *bucket;
    llist *ll;
    llistitem *li;
    llistitem *lilast;
    hash_item_string *hi;
    void *result;
    char * k1;

    result = NULL;
    if ((bucket = &(ht->buckets[string_hash(ht, key)])) != NULL)
    {
	ll = &(bucket->list);
	li = LL_FIRST(ll);
	lilast = NULL;
	while (li != NULL)
	{
	    hi = (hash_item_string *)li->datum;
	    k1 = hi->key;
	    if (strcmp(key, k1) == 0)
	    {
		ll_extract(ll, li, lilast);
		result = hi->value;
		key = hi->key;
		free(key);
		ll_freeitem(li);
		break;
	    }
	    lilast = li;
	    li = LL_NEXT(ll, li);
	}
    }
    return result;
}

/*
 * hash_item_string *hash_first_string(hash_table *ht, hash_pos *pos)
 *
 * First function to call when iterating through all items in the hash
 * table.  Returns the first item in "ht" and initializes "*pos" to track
 * the current position.
 */

hash_item_string *
hash_first_string(hash_table *ht, hash_pos *pos)

{
    /* initialize pos for first item in first bucket */
    pos->num_buckets = ht->num_buckets;
    pos->hash_bucket = ht->buckets;
    pos->curr = 0;
    pos->ll_last = NULL;

    /* find the first non-empty bucket */
    while(pos->hash_bucket->list.count == 0)
    {
	pos->hash_bucket++;
	if (++pos->curr >= pos->num_buckets)
	{
	    return NULL;
	}
    }

    /* set and return the first item */
    pos->ll_item = LL_FIRST(&(pos->hash_bucket->list));
    return (hash_item_string *)pos->ll_item->datum;
}


/*
 * hash_item_string *hash_next_string(hash_pos *pos)
 *
 * Return the next item in the hash table, using "pos" as a description
 * of the present position in the hash table.  "pos" also identifies the
 * specific hash table.  Return NULL if there are no more elements.
 */

hash_item_string *
hash_next_string(hash_pos *pos)

{
    llistitem *li;

    /* move item to last and check for NULL */
    if ((pos->ll_last = pos->ll_item) == NULL)
    {
	/* are we really at the end of the hash table? */
	if (pos->curr >= pos->num_buckets)
	{
	    /* yes: return NULL */
	    return NULL;
	}
	/* no: regrab first item in current bucket list (might be NULL) */
	li = LL_FIRST(&(pos->hash_bucket->list));
    }
    else
    {
	/* get the next item in the llist */
	li = LL_NEXT(&(pos->hash_bucket->list), pos->ll_item);
    }

    /* if its NULL we have to find another bucket */
    while (li == NULL)
    {
	/* locate another bucket */
	pos->ll_last = NULL;

	/* move to the next one */
	pos->hash_bucket++;
	if (++pos->curr >= pos->num_buckets)
	{
	    /* at the end of the hash table */
	    pos->ll_item = NULL;
	    return NULL;
	}

	/* get the first element (might be NULL) */
	li = LL_FIRST(&(pos->hash_bucket->list));
    }

    /* li is the next element to dish out */
    pos->ll_item = li;
    return (hash_item_string *)li->datum;
}

/*
 * void *hash_remove_pos_string(hash_pos *pos)
 *
 * Remove the hash table entry pointed to by position marker "pos".
 * The data from the entry is returned upon success, otherwise NULL.
 */


void *
hash_remove_pos_string(hash_pos *pos)

{
    llistitem *li;
    void *ans;
    hash_item_string *hi;
    char * key;

    /* sanity checks */
    if (pos == NULL || pos->ll_last == pos->ll_item)
    {
	return NULL;
    }

    /* at this point pos contains the item to remove (ll_item)
       and its predecesor (ll_last) */
    /* extract the item from the llist */
    li = pos->ll_item;
    ll_extract(&(pos->hash_bucket->list), li, pos->ll_last);

    /* retain the data */
    hi = (hash_item_string *)li->datum;
    ans = hi->value;

    /* free up the space */
    key = hi->key;
    free(key);
    ll_freeitem(li);

    /* back up to previous item */
    /* its okay for ll_item to be null: hash_next will detect it */
    pos->ll_item = pos->ll_last;

    return ans;
}



/*
 * void hash_add_pidthr(hash_table *ht, pidthr_t key, void *value)
 *
 * Add an element to table "ht".  The element is described by
 * "key" and "value".  Return NULL on success.  If the key
 * already exists in the table, then no action is taken and
 * the data for the existing element is returned.
 * Key type is pidthr_t
 */

void *
hash_add_pidthr(hash_table *ht, pidthr_t key, void *value)

{
    bucket_t *bucket;
    hash_item_pidthr *hi;
    hash_item_pidthr *h;
    llist *ll;
    llistitem *li;
    llistitem *newli;
    pidthr_t k1;

    /* allocate the space we will need */
    newli = ll_newitem(sizeof(hash_item_pidthr));
    hi = (hash_item_pidthr *)newli->datum;

    /* fill in the values */
    hi->key = key;
    hi->value = value;

    /* hash to the bucket */
    bucket = &(ht->buckets[((key.k_thr * 10000 + key.k_pid) % ht->num_buckets)]);

    /* walk the list to make sure we do not have a duplicate */
    ll = &(bucket->list);
    li = LL_FIRST(ll);
    while (li != NULL)
    {
	h = (hash_item_pidthr *)li->datum;
	k1 = h->key;
	if ((key.k_pid == k1.k_pid && key.k_thr == k1.k_thr))
	{
	    /* found one */
	    break;
	}
	li = LL_NEXT(ll, li);
    }
    li = NULL;

    /* is there already one there? */
    if (li == NULL)
    {
	/* add the unique element to the buckets list */
	ll_add(&(bucket->list), newli);
	return NULL;
    }
    else
    {
	/* free the stuff we just allocated */
	ll_freeitem(newli);
	return ((hash_item_pidthr *)(li->datum))->value;
    }
}

/*
 * void *hash_replace_pidthr(hash_table *ht, pidthr_t key, void *value)
 *
 * Replace an existing value in the hash table with a new one and
 * return the old value.  If the key does not already exist in
 * the hash table, add a new element and return NULL.
 * Key type is pidthr_t
 */

void *
hash_replace_pidthr(hash_table *ht, pidthr_t key, void *value)

{
    bucket_t *bucket;
    hash_item_pidthr *hi;
    llist *ll;
    llistitem *li;
    void *result = NULL;
    pidthr_t k1;

    /* find the bucket */
    bucket = &(ht->buckets[((key.k_thr * 10000 + key.k_pid) % ht->num_buckets)]);

    /* walk the list until we find the existing item */
    ll = &(bucket->list);
    li = LL_FIRST(ll);
    while (li != NULL)
    {
	hi = (hash_item_pidthr *)li->datum;
	k1 = hi->key;
	if ((key.k_pid == k1.k_pid && key.k_thr == k1.k_thr))
	{
	    /* found it: now replace the value with the new one */
	    result = hi->value;
	    hi->value = value;
	    break;
	}
	li = LL_NEXT(ll, li);
    }

    /* if the element wasnt found, add it */
    if (result == NULL)
    {
	li = ll_newitem(sizeof(hash_item_pidthr));
	hi = (hash_item_pidthr *)li->datum;
	hi->key = key;
	hi->value = value;
	ll_add(&(bucket->list), li);
    }

    /* return the old value (so it can be freed) */
    return result;
}

/*
 * void *hash_lookup_pidthr(hash_table *ht, pidthr_t key)
 *
 * Look up "key" in "ht" and return the associated value.  If "key"
 * is not found, return NULL.  Key type is pidthr_t
 */

void *
hash_lookup_pidthr(hash_table *ht, pidthr_t key)

{
    bucket_t *bucket;
    llist *ll;
    llistitem *li;
    hash_item_pidthr *h;
    void *result;
    pidthr_t k1;

    result = NULL;
    if ((bucket = &(ht->buckets[((key.k_thr * 10000 + key.k_pid) % ht->num_buckets)])) != NULL)
    {
	ll = &(bucket->list);
	li = LL_FIRST(ll);
	while (li != NULL)
	{
	    h = (hash_item_pidthr *)li->datum;
	    k1 = h->key;
	    if ((key.k_pid == k1.k_pid && key.k_thr == k1.k_thr))
	    {
		result = h->value;
		break;
	    }
	    li = LL_NEXT(ll, li);
	}
    }
    return result;
}

/*
 * void *hash_remove_pidthr(hash_table *ht, pidthr_t key)
 *
 * Remove the element associated with "key" from the hash table
 * "ht".  Return the value or NULL if the key was not found.
 */

void *
hash_remove_pidthr(hash_table *ht, pidthr_t key)

{
    bucket_t *bucket;
    llist *ll;
    llistitem *li;
    llistitem *lilast;
    hash_item_pidthr *hi;
    void *result;
    pidthr_t k1;

    result = NULL;
    if ((bucket = &(ht->buckets[((key.k_thr * 10000 + key.k_pid) % ht->num_buckets)])) != NULL)
    {
	ll = &(bucket->list);
	li = LL_FIRST(ll);
	lilast = NULL;
	while (li != NULL)
	{
	    hi = (hash_item_pidthr *)li->datum;
	    k1 = hi->key;
	    if ((key.k_pid == k1.k_pid && key.k_thr == k1.k_thr))
	    {
		ll_extract(ll, li, lilast);
		result = hi->value;
		key = hi->key;
		;
		ll_freeitem(li);
		break;
	    }
	    lilast = li;
	    li = LL_NEXT(ll, li);
	}
    }
    return result;
}

/*
 * hash_item_pidthr *hash_first_pidthr(hash_table *ht, hash_pos *pos)
 *
 * First function to call when iterating through all items in the hash
 * table.  Returns the first item in "ht" and initializes "*pos" to track
 * the current position.
 */

hash_item_pidthr *
hash_first_pidthr(hash_table *ht, hash_pos *pos)

{
    /* initialize pos for first item in first bucket */
    pos->num_buckets = ht->num_buckets;
    pos->hash_bucket = ht->buckets;
    pos->curr = 0;
    pos->ll_last = NULL;

    /* find the first non-empty bucket */
    while(pos->hash_bucket->list.count == 0)
    {
	pos->hash_bucket++;
	if (++pos->curr >= pos->num_buckets)
	{
	    return NULL;
	}
    }

    /* set and return the first item */
    pos->ll_item = LL_FIRST(&(pos->hash_bucket->list));
    return (hash_item_pidthr *)pos->ll_item->datum;
}


/*
 * hash_item_pidthr *hash_next_pidthr(hash_pos *pos)
 *
 * Return the next item in the hash table, using "pos" as a description
 * of the present position in the hash table.  "pos" also identifies the
 * specific hash table.  Return NULL if there are no more elements.
 */

hash_item_pidthr *
hash_next_pidthr(hash_pos *pos)

{
    llistitem *li;

    /* move item to last and check for NULL */
    if ((pos->ll_last = pos->ll_item) == NULL)
    {
	/* are we really at the end of the hash table? */
	if (pos->curr >= pos->num_buckets)
	{
	    /* yes: return NULL */
	    return NULL;
	}
	/* no: regrab first item in current bucket list (might be NULL) */
	li = LL_FIRST(&(pos->hash_bucket->list));
    }
    else
    {
	/* get the next item in the llist */
	li = LL_NEXT(&(pos->hash_bucket->list), pos->ll_item);
    }

    /* if its NULL we have to find another bucket */
    while (li == NULL)
    {
	/* locate another bucket */
	pos->ll_last = NULL;

	/* move to the next one */
	pos->hash_bucket++;
	if (++pos->curr >= pos->num_buckets)
	{
	    /* at the end of the hash table */
	    pos->ll_item = NULL;
	    return NULL;
	}

	/* get the first element (might be NULL) */
	li = LL_FIRST(&(pos->hash_bucket->list));
    }

    /* li is the next element to dish out */
    pos->ll_item = li;
    return (hash_item_pidthr *)li->datum;
}

/*
 * void *hash_remove_pos_pidthr(hash_pos *pos)
 *
 * Remove the hash table entry pointed to by position marker "pos".
 * The data from the entry is returned upon success, otherwise NULL.
 */


void *
hash_remove_pos_pidthr(hash_pos *pos)

{
    llistitem *li;
    void *ans;
    hash_item_pidthr *hi;

    /* sanity checks */
    if (pos == NULL || pos->ll_last == pos->ll_item)
    {
	return NULL;
    }

    /* at this point pos contains the item to remove (ll_item)
       and its predecesor (ll_last) */
    /* extract the item from the llist */
    li = pos->ll_item;
    ll_extract(&(pos->hash_bucket->list), li, pos->ll_last);

    /* retain the data */
    hi = (hash_item_pidthr *)li->datum;
    ans = hi->value;

    /* free up the space */
    ll_freeitem(li);

    /* back up to previous item */
    /* its okay for ll_item to be null: hash_next will detect it */
    pos->ll_item = pos->ll_last;

    return ans;
}

#if HAVE_LWPID_T


/*
 * void hash_add_lwpid(hash_table *ht, lwpid_t key, void *value)
 *
 * Add an element to table "ht".  The element is described by
 * "key" and "value".  Return NULL on success.  If the key
 * already exists in the table, then no action is taken and
 * the data for the existing element is returned.
 * Key type is lwpid_t
 */

void *
hash_add_lwpid(hash_table *ht, lwpid_t key, void *value)

{
    bucket_t *bucket;
    hash_item_lwpid *hi;
    hash_item_lwpid *h;
    llist *ll;
    llistitem *li;
    llistitem *newli;
    lwpid_t k1;

    /* allocate the space we will need */
    newli = ll_newitem(sizeof(hash_item_lwpid));
    hi = (hash_item_lwpid *)newli->datum;

    /* fill in the values */
    hi->key = key;
    hi->value = value;

    /* hash to the bucket */
    bucket = &(ht->buckets[(key % ht->num_buckets)]);

    /* walk the list to make sure we do not have a duplicate */
    ll = &(bucket->list);
    li = LL_FIRST(ll);
    while (li != NULL)
    {
	h = (hash_item_lwpid *)li->datum;
	k1 = h->key;
	if (key == k1)
	{
	    /* found one */
	    break;
	}
	li = LL_NEXT(ll, li);
    }
    li = NULL;

    /* is there already one there? */
    if (li == NULL)
    {
	/* add the unique element to the buckets list */
	ll_add(&(bucket->list), newli);
	return NULL;
    }
    else
    {
	/* free the stuff we just allocated */
	ll_freeitem(newli);
	return ((hash_item_lwpid *)(li->datum))->value;
    }
}

/*
 * void *hash_replace_lwpid(hash_table *ht, lwpid_t key, void *value)
 *
 * Replace an existing value in the hash table with a new one and
 * return the old value.  If the key does not already exist in
 * the hash table, add a new element and return NULL.
 * Key type is lwpid_t
 */

void *
hash_replace_lwpid(hash_table *ht, lwpid_t key, void *value)

{
    bucket_t *bucket;
    hash_item_lwpid *hi;
    llist *ll;
    llistitem *li;
    void *result = NULL;
    lwpid_t k1;

    /* find the bucket */
    bucket = &(ht->buckets[(key % ht->num_buckets)]);

    /* walk the list until we find the existing item */
    ll = &(bucket->list);
    li = LL_FIRST(ll);
    while (li != NULL)
    {
	hi = (hash_item_lwpid *)li->datum;
	k1 = hi->key;
	if (key == k1)
	{
	    /* found it: now replace the value with the new one */
	    result = hi->value;
	    hi->value = value;
	    break;
	}
	li = LL_NEXT(ll, li);
    }

    /* if the element wasnt found, add it */
    if (result == NULL)
    {
	li = ll_newitem(sizeof(hash_item_lwpid));
	hi = (hash_item_lwpid *)li->datum;
	hi->key = key;
	hi->value = value;
	ll_add(&(bucket->list), li);
    }

    /* return the old value (so it can be freed) */
    return result;
}

/*
 * void *hash_lookup_lwpid(hash_table *ht, lwpid_t key)
 *
 * Look up "key" in "ht" and return the associated value.  If "key"
 * is not found, return NULL.  Key type is lwpid_t
 */

void *
hash_lookup_lwpid(hash_table *ht, lwpid_t key)

{
    bucket_t *bucket;
    llist *ll;
    llistitem *li;
    hash_item_lwpid *h;
    void *result;
    lwpid_t k1;

    result = NULL;
    if ((bucket = &(ht->buckets[(key % ht->num_buckets)])) != NULL)
    {
	ll = &(bucket->list);
	li = LL_FIRST(ll);
	while (li != NULL)
	{
	    h = (hash_item_lwpid *)li->datum;
	    k1 = h->key;
	    if (key == k1)
	    {
		result = h->value;
		break;
	    }
	    li = LL_NEXT(ll, li);
	}
    }
    return result;
}

/*
 * void *hash_remove_lwpid(hash_table *ht, lwpid_t key)
 *
 * Remove the element associated with "key" from the hash table
 * "ht".  Return the value or NULL if the key was not found.
 */

void *
hash_remove_lwpid(hash_table *ht, lwpid_t key)

{
    bucket_t *bucket;
    llist *ll;
    llistitem *li;
    llistitem *lilast;
    hash_item_lwpid *hi;
    void *result;
    lwpid_t k1;

    result = NULL;
    if ((bucket = &(ht->buckets[(key % ht->num_buckets)])) != NULL)
    {
	ll = &(bucket->list);
	li = LL_FIRST(ll);
	lilast = NULL;
	while (li != NULL)
	{
	    hi = (hash_item_lwpid *)li->datum;
	    k1 = hi->key;
	    if (key == k1)
	    {
		ll_extract(ll, li, lilast);
		result = hi->value;
		key = hi->key;
		;
		ll_freeitem(li);
		break;
	    }
	    lilast = li;
	    li = LL_NEXT(ll, li);
	}
    }
    return result;
}

/*
 * hash_item_lwpid *hash_first_lwpid(hash_table *ht, hash_pos *pos)
 *
 * First function to call when iterating through all items in the hash
 * table.  Returns the first item in "ht" and initializes "*pos" to track
 * the current position.
 */

hash_item_lwpid *
hash_first_lwpid(hash_table *ht, hash_pos *pos)

{
    /* initialize pos for first item in first bucket */
    pos->num_buckets = ht->num_buckets;
    pos->hash_bucket = ht->buckets;
    pos->curr = 0;
    pos->ll_last = NULL;

    /* find the first non-empty bucket */
    while(pos->hash_bucket->list.count == 0)
    {
	pos->hash_bucket++;
	if (++pos->curr >= pos->num_buckets)
	{
	    return NULL;
	}
    }

    /* set and return the first item */
    pos->ll_item = LL_FIRST(&(pos->hash_bucket->list));
    return (hash_item_lwpid *)pos->ll_item->datum;
}


/*
 * hash_item_lwpid *hash_next_lwpid(hash_pos *pos)
 *
 * Return the next item in the hash table, using "pos" as a description
 * of the present position in the hash table.  "pos" also identifies the
 * specific hash table.  Return NULL if there are no more elements.
 */

hash_item_lwpid *
hash_next_lwpid(hash_pos *pos)

{
    llistitem *li;

    /* move item to last and check for NULL */
    if ((pos->ll_last = pos->ll_item) == NULL)
    {
	/* are we really at the end of the hash table? */
	if (pos->curr >= pos->num_buckets)
	{
	    /* yes: return NULL */
	    return NULL;
	}
	/* no: regrab first item in current bucket list (might be NULL) */
	li = LL_FIRST(&(pos->hash_bucket->list));
    }
    else
    {
	/* get the next item in the llist */
	li = LL_NEXT(&(pos->hash_bucket->list), pos->ll_item);
    }

    /* if its NULL we have to find another bucket */
    while (li == NULL)
    {
	/* locate another bucket */
	pos->ll_last = NULL;

	/* move to the next one */
	pos->hash_bucket++;
	if (++pos->curr >= pos->num_buckets)
	{
	    /* at the end of the hash table */
	    pos->ll_item = NULL;
	    return NULL;
	}

	/* get the first element (might be NULL) */
	li = LL_FIRST(&(pos->hash_bucket->list));
    }

    /* li is the next element to dish out */
    pos->ll_item = li;
    return (hash_item_lwpid *)li->datum;
}

/*
 * void *hash_remove_pos_lwpid(hash_pos *pos)
 *
 * Remove the hash table entry pointed to by position marker "pos".
 * The data from the entry is returned upon success, otherwise NULL.
 */


void *
hash_remove_pos_lwpid(hash_pos *pos)

{
    llistitem *li;
    void *ans;
    hash_item_lwpid *hi;

    /* sanity checks */
    if (pos == NULL || pos->ll_last == pos->ll_item)
    {
	return NULL;
    }

    /* at this point pos contains the item to remove (ll_item)
       and its predecesor (ll_last) */
    /* extract the item from the llist */
    li = pos->ll_item;
    ll_extract(&(pos->hash_bucket->list), li, pos->ll_last);

    /* retain the data */
    hi = (hash_item_lwpid *)li->datum;
    ans = hi->value;

    /* free up the space */
    ll_freeitem(li);

    /* back up to previous item */
    /* its okay for ll_item to be null: hash_next will detect it */
    pos->ll_item = pos->ll_last;

    return ans;
}

#endif