/* * Copyright (c) 1993-2001 by Sun Microsystems, Inc. * All rights reserved. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Copyright 1988, 1991 by Carnegie Mellon University * * All Rights Reserved * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted, provided * that the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of Carnegie Mellon University not be used * in advertising or publicity pertaining to distribution of the software * without specific, written prior permission. * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. * IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF * THIS SOFTWARE. */ /* * Generalized hash table ADT * * Provides multiple, dynamically-allocated, variable-sized hash tables on * various data and keys. * * This package attempts to follow some of the coding conventions suggested * by Bob Sidebotham and the AFS Clean Code Committee of the * Information Technology Center at Carnegie Mellon. * * Additions for per bucket locking, and configurable dynamic free of * unused entries. */ #include #include #include #include #include #include #include #include #include #include "dhcpd.h" #include "hash.h" /* * Hash table size calculation routine. * * Estimate the size of a hash table based on the expected number of * entries, up to a maximum of HASHTABLESIZE. */ static unsigned hashi_Hsize(unsigned hint) { unsigned f; if (hint == 0) /* Default size. */ hint = HASHTABLESIZE; else if (hint < 16) /* Minimal size. */ hint = 16; hint /= 4; for (f = 2; f * f <= hint; f++) { /* Find next largest prime. */ if (hint % f == 0) { f = 1; hint++; } } return (MIN(HASHTABLESIZE, hint)); } /* * Frees an entire linked list of bucket members (used in the * open hashing scheme). Does nothing if the passed pointer is NULL. * * Returns B_FALSE and members which could not be freed in bucketptr, when * force variable is set to B_FALSE, and free_data routine indicates * free did not occur. */ static boolean_t hashi_FreeMember(hash_member **bucketptr, boolean_t (*free_data)(), boolean_t force) { hash_member *prev, *next, *unfree = NULL; boolean_t ret = B_TRUE; if (bucketptr) { for (prev = *bucketptr; prev; prev = next) { next = prev->next; prev->next = NULL; if (free_data != NULL) { if ((*free_data)(prev->data, force) == B_FALSE) { ret = B_FALSE; prev->next = unfree; unfree = prev; } else { free(prev); } } else free(prev); } *bucketptr = unfree; } return (ret); } /* * Dynamic free initialization. */ static void hashi_Dinit(hash_tbl *hashtable, hash_member *memberptr) { (void) mutex_init(&memberptr->h_mtx, USYNC_THREAD, NULL); memberptr->h_time = time(NULL) + hashtable->dfree_time; memberptr->h_count = 1; } /* * Dynamic free reference count increment. */ static void hashi_Dhold(hash_member *memberptr) { (void) mutex_lock(&memberptr->h_mtx); memberptr->h_count++; (void) mutex_unlock(&memberptr->h_mtx); } /* * Dynamic free expired data. Return NULL if memberptr is successfully * dynamically freed, otherwise return memberptr. */ static hash_member * hashi_Dfree(hash_member *memberptr, boolean_t (*free_data)()) { hash_member *next; next = memberptr->next; memberptr->next = NULL; if (hashi_FreeMember(&memberptr, free_data, B_FALSE) == B_TRUE) memberptr = NULL; else memberptr->next = next; return (memberptr); } /* * Hash table initialization routine. * * This routine creates and intializes a hash table of size "tablesize" * entries. Successful calls return a pointer to the hash table (which must * be passed to other hash routines to identify the hash table). Failed * calls return NULL. */ hash_tbl * hash_Init(unsigned tablesize, boolean_t (*dfree_data)(), time_t dtime, boolean_t lck) { hash_tbl *hashtblptr; unsigned totalsize; unsigned i; tablesize = hashi_Hsize(tablesize); totalsize = sizeof (hash_tbl) + (sizeof (hash_bucket) * (tablesize - 1)); hashtblptr = (hash_tbl *)smalloc(totalsize); hashtblptr->size = tablesize; /* Success! */ hashtblptr->bucketnum = 0; hashtblptr->dfree_data = dfree_data; hashtblptr->dfree_lck = lck; hashtblptr->dfree_time = dtime; hashtblptr->table = &hashtblptr->data[0]; for (i = 0; i < tablesize; i++) { hashtblptr->table[i].table = hashtblptr; if (lck == B_TRUE) { (void) rwlock_init(&(hashtblptr->table[i].rwlock), USYNC_THREAD, NULL); } } return (hashtblptr); /* NULL if failure */ } /* * Generic hash function to calculate a hash code from the given string. * * For each byte of the string, this function left-shifts the value in an * accumulator and then adds the byte into the accumulator. The contents of * the accumulator is returned after the entire string has been processed. * It is assumed that this result will be used as the "hashcode" parameter in * calls to other functions in this package. These functions automatically * adjust the hashcode for the size of each hashtable. * * This algorithm probably works best when the hash table size is a prime * number. * * Hopefully, this function is better than the previous one which returned * the sum of the squares of all the bytes. I'm still open to other * suggestions for a default hash function. The programmer is more than * welcome to supply his/her own hash function as that is one of the design * features of this package. */ static unsigned hashi_HashFunction(unsigned char *string, unsigned len) { unsigned accum; /* * Special case: allow hash_Delete() to iterate over buckets. */ if (string == NULL) return (len); for (accum = 0; len != 0; len--) { accum <<= 1; accum += (unsigned)(*string++ & 0xFF); } return (accum); } /* * This routine re-initializes the hash table. It frees all the allocated * memory and resets all bucket pointers to NULL. For the macro hash * table, the table will be reused. Other tables (with bucket locks) * will be destroyed. */ void hash_Reset(hash_tbl *hashtable, boolean_t (*free_data)()) { hash_bucket *bucketptr; unsigned i; bucketptr = &((hashtable->table)[0]); for (i = 0; i < hashtable->size; i++) { if (hashtable->dfree_lck == B_TRUE) (void) rw_wrlock(&bucketptr->rwlock); /* * Unequivocally free member, using the force parameter. */ (void) hashi_FreeMember(&bucketptr->next, free_data, B_TRUE); bucketptr->next = NULL; if (hashtable->dfree_lck == B_TRUE) { (void) rw_unlock(&bucketptr->rwlock); (void) rwlock_destroy(&(bucketptr->rwlock)); } bucketptr++; } hashtable->bucketnum = 0; } /* * Returns B_TRUE if at least one entry for the given key exists; B_FALSE * otherwise. Dynamically free expired data as searched. */ static int hashi_Exists(hash_bucket *bucketptr, int (*compare)(), hash_datum *key, boolean_t (*free_data)(), hash_member **prev) { hash_member *prevptr = (hash_member *)bucketptr; hash_member *memberptr = bucketptr->next; hash_tbl *hashtable = bucketptr->table; hash_member *next; boolean_t ret = B_FALSE; time_t now = time(NULL); while (memberptr != NULL) { /* * Dynamically free expired data. */ if (free_data != NULL && hashtable->dfree_data != NULL && memberptr->h_time < now) { next = memberptr->next; if ((memberptr = hashi_Dfree(memberptr, free_data)) == NULL) { prevptr->next = memberptr = next; continue; } } /* * Entry exists, or we are randomly selecting any * element (compare function is NULL). */ if (compare == NULL || (*compare)(key, memberptr->data)) { ret = B_TRUE; break; } else prevptr = memberptr; memberptr = memberptr->next; } if (prev != NULL) *prev = prevptr; return (ret); } /* * Returns number of Dynamically freed expired entries. */ static int hashi_Expire(hash_bucket *bucketptr, boolean_t (*free_data)()) { hash_member *prevptr = (hash_member *)bucketptr; hash_member *memberptr = bucketptr->next; hash_tbl *hashtable = bucketptr->table; hash_member *next; int rcount = 0; time_t now = time(NULL); while (memberptr) { /* * Dynamically free expired data. */ if (free_data != NULL && hashtable->dfree_data != NULL && memberptr->h_time < now) { next = memberptr->next; if ((memberptr = hashi_Dfree(memberptr, free_data)) == NULL) { rcount++; prevptr->next = memberptr = next; continue; } } prevptr = memberptr; memberptr = memberptr->next; } return (rcount); } /* * Insert the data item "element" into the hash table using "hashcode" * to determine the bucket number, and "compare" and "key" to determine * its uniqueness. * * If the insertion is successful the element is returned. If a matching entry * already exists in the given bucket of the hash table, then NULL is returned, * signifying that the entry is already in the table. This happens when some * other thread has already inserted the entry. */ void * hash_Insert(hash_tbl *hashtable, void *hashdata, unsigned hashlen, int (*compare)(), hash_datum *key, hash_datum *element) { hash_member *temp = NULL; hash_bucket *bucketptr; hash_member *prev = NULL; unsigned hashcode = hashi_HashFunction(hashdata, hashlen); bucketptr = &((hashtable->table)[hashcode % hashtable->size]); if (hashtable->dfree_lck) (void) rw_wrlock(&bucketptr->rwlock); if (hashi_Exists(bucketptr, compare, key, hashtable->dfree_data, &prev)) { /* Some other thread got there first, so just return */ if (hashtable->dfree_lck) (void) rw_unlock(&bucketptr->rwlock); return (NULL); } temp = (hash_member *)smalloc(sizeof (hash_member)); prev->next = temp; temp->data = element; temp->next = NULL; /* * Dynamic free initialization. */ if (hashtable->dfree_data != NULL) hashi_Dinit(hashtable, temp); if (hashtable->dfree_lck) (void) rw_unlock(&bucketptr->rwlock); return ((void *)temp); } /* * Release the reference count on an item. Performance: if item is to be * deleted, mark for future dynamic free. */ void hash_Rele(void *hashp, boolean_t delete) { hash_member *memberptr = (hash_member *)hashp; (void) mutex_lock(&memberptr->h_mtx); memberptr->h_count--; assert(memberptr->h_count >= 0); if (delete == B_TRUE) memberptr->h_time = 0; (void) mutex_unlock(&memberptr->h_mtx); } /* * Report the reference count on an item. */ int hash_Refcount(void *hashp) { hash_member *memberptr = (hash_member *)hashp; int ret; (void) mutex_lock(&memberptr->h_mtx); ret = memberptr->h_count; (void) mutex_unlock(&memberptr->h_mtx); return (ret); } /* * Report the dynamic free time on an item. */ int hash_Htime(void *hashp) { hash_member *memberptr = (hash_member *)hashp; int ret; (void) mutex_lock(&memberptr->h_mtx); ret = memberptr->h_time; (void) mutex_unlock(&memberptr->h_mtx); return (ret); } /* * Increase the dynamic free time on an item. */ void hash_Age(void *hashp) { hash_member *memberptr = (hash_member *)hashp; (void) mutex_lock(&memberptr->h_mtx); memberptr->h_time++; (void) mutex_unlock(&memberptr->h_mtx); } /* * Set the dynamic free time on an item. */ void hash_Dtime(void *hashp, time_t tm) { hash_member *memberptr = (hash_member *)hashp; (void) mutex_lock(&memberptr->h_mtx); memberptr->h_time = tm; (void) mutex_unlock(&memberptr->h_mtx); } /* * Delete a data item from the hash table using "hashcode" * to determine the bucket number, and "compare" and "key" to determine * its uniqueness. * * If the deletion is successful 0 is returned. If a matching entry * does not exist in the given bucket of the hash table, or some other error * occurs, -1 is returned and the insertion is not done. */ boolean_t hash_Delete(hash_tbl *hashtable, void *hashdata, unsigned hashlen, int (*compare)(), hash_datum *key, boolean_t (*free_data)()) { hash_member *prev = NULL; hash_member *temp; hash_bucket *bucketptr; unsigned hashcode = hashi_HashFunction(hashdata, hashlen); bucketptr = &((hashtable->table)[hashcode % hashtable->size]); if (hashtable->dfree_lck == B_TRUE) (void) rw_wrlock(&bucketptr->rwlock); if (hashi_Exists(bucketptr, compare, key, free_data, &prev) == B_FALSE || prev == NULL) { if (hashtable->dfree_lck == B_TRUE) (void) rw_unlock(&bucketptr->rwlock); return (B_FALSE); /* Entry does not exist */ } temp = prev->next; if (temp) { prev->next = temp->next; temp->next = NULL; (void) hashi_FreeMember(&temp, free_data, B_TRUE); } else prev->next = NULL; if (hashtable->dfree_lck == B_TRUE) (void) rw_unlock(&bucketptr->rwlock); return (B_TRUE); } /* * Locate and return the data entry associated with the given key. * * If the data entry is found, a pointer to it is returned. Otherwise, * NULL is returned. */ hash_datum * hash_Lookup(hash_tbl *hashtable, void *hashdata, unsigned hashlen, int (*compare)(), hash_datum *key, boolean_t hold) { hash_datum *ret = NULL; hash_bucket *bucketptr; hash_member *prev = NULL; unsigned hashcode = hashi_HashFunction(hashdata, hashlen); bucketptr = &((hashtable->table)[hashcode % hashtable->size]); if (hashtable->dfree_lck == B_TRUE) (void) rw_wrlock(&bucketptr->rwlock); if (hashi_Exists(bucketptr, compare, key, hashtable->dfree_data, &prev) == B_TRUE) { /* * Dynamic free increment reference. */ if (hold) hashi_Dhold(prev->next); ret = prev->next->data; } if (hashtable->dfree_lck == B_TRUE) (void) rw_unlock(&bucketptr->rwlock); return (ret); } /* * Reap expired data items, or a random data item from the hash table. */ void hash_Reap(hash_tbl *hashtable, boolean_t (*free_data)()) { hash_bucket *bucketptr; int rcount; unsigned i; bucketptr = &((hashtable->table)[0]); rcount = 0; /* * Walk the buckets, reaping expired clients. */ for (i = 0; i < hashtable->size; i++) { if (hashtable->dfree_lck == B_TRUE) (void) rw_wrlock(&bucketptr->rwlock); rcount += hashi_Expire(bucketptr, hashtable->dfree_data); if (hashtable->dfree_lck == B_TRUE) (void) rw_unlock(&bucketptr->rwlock); bucketptr++; } /* * Nothing to be reaped, delete a random element. Note that * the unhash_data routine will wait for current references * before deletion. */ if (rcount == 0) { for (i = 0; i < hashtable->size; i++) { if (hash_Delete(hashtable, NULL, i, NULL, NULL, free_data) == B_TRUE) { break; } } } }