/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2002-2003, Network Appliance, Inc. All rights reserved. */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * * MODULE: dapl_hash.c * * PURPOSE: Hash Table * Description: * * Provides a generic hash table with chaining. * * $Id: dapl_hash.c,v 1.10 2003/07/11 18:42:17 hobie16 Exp $ */ #include "dapl_hash.h" /* * * Structures * */ /* * A hash table element */ typedef struct DAPL_HASH_ELEM { struct DAPL_HASH_ELEM *next_element; DAPL_HASH_KEY key; void *datum; } DAPL_HASH_ELEM; /* * The hash table */ struct dapl_hash_table { unsigned long num_entries; unsigned long tbl_size; DAPL_HASH_ELEM *table; DAT_BOOLEAN locking_required; /* internal locking reqd */ DAPL_OS_LOCK lock; unsigned long iterator_bucket; DAPL_HASH_ELEM *iterator; /* * statistics - we tally on insert operations, counting * the number of entries in the whole hash table, as * well as the length of chains we walk to insert. This * ignores empty buckets, giving us data on overall table * occupancy, as well as max/average chain length for * the buckets used. If our hash function results in * hot buckets, this will show it. */ uint64_t hash_tbl_inserts; /* total inserts ops */ uint64_t hash_tbl_max; /* max in entire table */ uint64_t hash_tbl_total; /* total in table */ uint64_t hash_chn_max; /* longest chain */ uint64_t hash_chn_total; /* total non-0 lenghts */ }; /* * * Defines * */ /* The hash function */ #define DAPL_DOHASH(key, hashsize) ((uint64_t)((key) % (hashsize))) /* datum value in empty table slots (use 0UL or ~0UL as appropriate) */ #define NO_DATUM_VALUE ((void *) 0UL) #define NO_DATUM(value) ((value) == NO_DATUM_VALUE) /* Lookup macro (which falls back to function to rehash) */ #define DAPL_HASHLOOKUP(p_table, in_key, out_datum, bucket_head) \ { \ DAPL_HASH_ELEM *element = \ &((p_table)->table)[DAPL_DOHASH(in_key, (p_table)->tbl_size)]; \ if (NO_DATUM(element->datum)) { \ (bucket_head) = (void *)0; \ } else if (element->key == (DAPL_HASH_KEY) (in_key)) { \ (out_datum) = element->datum; \ (bucket_head) = (void *)element; \ } else if (element->next_element) { \ dapli_hash_rehash(element, \ (in_key), \ (void **)&(out_datum), \ (DAPL_HASH_ELEM **)&(bucket_head)); \ } else { \ (bucket_head) = (void *)0; \ }\ } /* * * Internal Functions * */ /* * Rehash the key (used by add and lookup functions) * * Inputs: element element to rehash key * key, datum datum for key head * head head for list */ static void dapli_hash_rehash( DAPL_HASH_ELEM *element, DAPL_HASH_KEY key, void **datum, DAPL_HASH_ELEM ** head) { /* * assume we looked at the contents of element already, * and start with the next element. */ dapl_os_assert(element->next_element); dapl_os_assert(!NO_DATUM(element->datum)); *head = element; /*CONSTCOND*/ while (1) { element = element->next_element; if (!element) { break; } if (element->key == key) { *datum = element->datum; return; } } *head = 0; } /* * Add a new key to the hash table * * Inputs: * table, key and datum to be added * allow_dup - DAT_TRUE if dups are allowed * Outputs: * report_dup - should you care to know * Returns: * DAT_TRUE on success */ static DAT_BOOLEAN dapli_hash_add( DAPL_HASH_TABLEP p_table, DAPL_HASH_KEY key, void *datum, DAT_BOOLEAN allow_dup, DAT_BOOLEAN *report_dup) { void *olddatum; DAPL_HASH_KEY hashValue; DAPL_HASH_ELEM *found; DAT_BOOLEAN status = DAT_FALSE; unsigned int chain_len = 0; if (report_dup) { (*report_dup) = DAT_FALSE; } if (NO_DATUM(datum)) { /* * Reserved value used for datum */ dapl_dbg_log( DAPL_DBG_TYPE_ERR, "dapli_hash_add () called with magic NO_DATA value " "(%p) used as datum!\n", datum); return (DAT_FALSE); } DAPL_HASHLOOKUP(p_table, key, olddatum, found); if (found) { /* * key exists already */ if (report_dup) { *report_dup = DAT_TRUE; } if (!allow_dup) { dapl_dbg_log(DAPL_DBG_TYPE_ERR, "dapli_hash_add () called with duplicate key " "(" F64x ")\n", key); return (DAT_FALSE); } } hashValue = DAPL_DOHASH(key, p_table->tbl_size); if (NO_DATUM(p_table->table[hashValue].datum)) { /* * Empty head - just fill it in */ p_table->table[hashValue].key = key; p_table->table[hashValue].datum = datum; p_table->table[hashValue].next_element = 0; p_table->num_entries++; status = DAT_TRUE; } else { DAPL_HASH_ELEM *newelement = (DAPL_HASH_ELEM *) dapl_os_alloc(sizeof (DAPL_HASH_ELEM)); /* * Add an element to the end of the chain */ if (newelement) { DAPL_HASH_ELEM *lastelement; newelement->key = key; newelement->datum = datum; newelement->next_element = 0; for (lastelement = &p_table->table[hashValue]; lastelement->next_element; lastelement = lastelement->next_element) { /* Walk to the end of the chain */ chain_len++; } lastelement->next_element = newelement; p_table->num_entries++; status = DAT_TRUE; } else { /* allocation failed - should not happen */ status = DAT_FALSE; } } /* * Tally up our counters. chain_len is one less than current chain * length. */ chain_len++; p_table->hash_tbl_inserts++; p_table->hash_tbl_total += p_table->num_entries; p_table->hash_chn_total += chain_len; if (p_table->num_entries > p_table->hash_tbl_max) { p_table->hash_tbl_max = p_table->num_entries; } if (chain_len > p_table->hash_chn_max) { p_table->hash_chn_max = chain_len; } return (status); } /* * Remove element from hash bucket * * Inputs: * element, key to be deleted * Returns: * DAT_TRUE on success */ static DAT_BOOLEAN dapl_hash_delete_element(DAPL_HASH_ELEM * element, DAPL_HASH_KEY key, DAPL_HASH_DATA *p_datum) { DAPL_HASH_ELEM *curelement; DAPL_HASH_ELEM *lastelement; lastelement = NULL; for (curelement = element; curelement; lastelement = curelement, curelement = curelement->next_element) { if (curelement->key == key) { if (p_datum) { *p_datum = curelement->datum; } if (lastelement) { /* * curelement was malloc'd; free it */ lastelement->next_element = curelement->next_element; dapl_os_free((void *) curelement, sizeof (DAPL_HASH_ELEM)); } else { /* * curelement is static list head */ DAPL_HASH_ELEM *n = curelement->next_element; if (n) { /* * If there is a next element, copy its * contents into the head and free the * original next element. */ curelement->key = n->key; curelement->datum = n->datum; curelement->next_element = n->next_element; dapl_os_free((void *) n, sizeof (DAPL_HASH_ELEM)); } else { curelement->datum = NO_DATUM_VALUE; } } break; } } return (curelement != NULL ? DAT_TRUE : DAT_FALSE); } /* * * External Functions * */ /* * Create a new hash table with at least 'table_size' hash buckets. */ DAT_RETURN dapls_hash_create( IN DAT_COUNT table_size, IN DAT_BOOLEAN locking_required, OUT DAPL_HASH_TABLE **pp_table) { DAPL_HASH_TABLE *p_table; DAT_COUNT table_length = table_size * sizeof (DAPL_HASH_ELEM); DAT_RETURN dat_status; DAT_COUNT i; dapl_os_assert(pp_table); dat_status = DAT_SUCCESS; /* Allocate hash table */ p_table = dapl_os_alloc(sizeof (DAPL_HASH_TABLE)); if (NULL == p_table) { dat_status = DAT_ERROR(DAT_INSUFFICIENT_RESOURCES, DAT_RESOURCE_MEMORY); goto bail; } /* Init hash table, allocate and init and buckets */ (void) dapl_os_memzero(p_table, sizeof (DAPL_HASH_TABLE)); p_table->tbl_size = table_size; p_table->table = (DAPL_HASH_ELEM *)dapl_os_alloc(table_length); if (NULL == p_table->table) { dapl_os_free(p_table, sizeof (DAPL_HASH_TABLE)); dat_status = DAT_ERROR(DAT_INSUFFICIENT_RESOURCES, DAT_RESOURCE_MEMORY); goto bail; } /* Init the lock anyways */ dapl_os_lock_init(&p_table->lock); p_table->locking_required = locking_required; for (i = 0; i < table_size; i++) { p_table->table[i].datum = NO_DATUM_VALUE; p_table->table[i].key = 0; p_table->table[i].next_element = 0; } *pp_table = p_table; bail: return (dat_status); } /* * Destroy a hash table */ DAT_RETURN dapls_hash_free( IN DAPL_HASH_TABLE *p_table) { dapl_os_assert(p_table && p_table->table); dapl_os_lock_destroy(&p_table->lock); dapl_os_free(p_table->table, sizeof (DAPL_HASH_ELEM) * p_table->tbl_size); dapl_os_free(p_table, sizeof (DAPL_HASH_TABLE)); return (DAT_SUCCESS); } /* * Returns the number of elements stored in the table */ DAT_RETURN dapls_hash_size( IN DAPL_HASH_TABLE *p_table, OUT DAT_COUNT *p_size) { dapl_os_assert(p_table && p_size); *p_size = p_table->num_entries; return (DAT_SUCCESS); } /* * Inserts the specified data into the table with the given key. * Duplicates are not expected, and return in error, having done nothing. */ DAT_RETURN dapls_hash_insert( IN DAPL_HASH_TABLE *p_table, IN DAPL_HASH_KEY key, IN DAPL_HASH_DATA data) { DAT_RETURN dat_status; dapl_os_assert(p_table); dat_status = DAT_SUCCESS; if (p_table->locking_required) { dapl_os_lock(&p_table->lock); } if (!dapli_hash_add(p_table, key, data, DAT_FALSE, NULL)) { dat_status = DAT_ERROR(DAT_INSUFFICIENT_RESOURCES, DAT_RESOURCE_MEMORY); } if (p_table->locking_required) { dapl_os_unlock(&p_table->lock); } return (dat_status); } /* * Searches for the given key. If found, * DAT_SUCCESS is returned and the associated * data is returned in the DAPL_HASH_DATA * pointer if that pointer is not NULL. */ DAT_RETURN dapls_hash_search( IN DAPL_HASH_TABLE *p_table, IN DAPL_HASH_KEY key, OUT DAPL_HASH_DATA *p_data) { DAT_RETURN dat_status; void *olddatum; DAPL_HASH_ELEM *found; dapl_os_assert(p_table); dat_status = DAT_ERROR(DAT_INVALID_PARAMETER, 0); if (p_table->locking_required) { dapl_os_lock(&p_table->lock); DAPL_HASHLOOKUP(p_table, key, olddatum, found); dapl_os_unlock(&p_table->lock); } else { DAPL_HASHLOOKUP(p_table, key, olddatum, found); } if (found) { if (p_data) { *p_data = olddatum; } dat_status = DAT_SUCCESS; } return (dat_status); } DAT_RETURN dapls_hash_remove( IN DAPL_HASH_TABLE *p_table, IN DAPL_HASH_KEY key, OUT DAPL_HASH_DATA *p_data) { DAT_RETURN dat_status; DAPL_HASH_KEY hashValue; dapl_os_assert(p_table); dat_status = DAT_ERROR(DAT_INVALID_PARAMETER, 0); if (p_table->num_entries == 0) { dapl_dbg_log(DAPL_DBG_TYPE_ERR, "dapls_hash_remove () called on empty hash table!\n"); return (dat_status); } hashValue = DAPL_DOHASH(key, p_table->tbl_size); if (p_table->locking_required) { dapl_os_lock(&p_table->lock); } if (dapl_hash_delete_element(&p_table->table[hashValue], key, p_data)) { p_table->num_entries--; dat_status = DAT_SUCCESS; } if (p_table->locking_required) { dapl_os_unlock(&p_table->lock); } return (dat_status); } /* * Iterates through the entire hash table return one element at a time. * Note: this is not a threadsafe routine and hence consumers that * rely on the hash-tables internal locking are not allowed to use this. */ DAT_RETURN dapls_hash_iterate( IN DAPL_HASH_TABLE *p_table, IN DAPL_HASH_ITERATOR op, OUT DAPL_HASH_DATA *p_data) { DAPL_HASH_ELEM *curr; dapl_os_assert(p_table); /* * sorry iterate is supported only for consumers that do their * own locking */ if (p_table->locking_required) { return (DAT_ERROR(DAT_INVALID_PARAMETER, 0)); } if (op == DAPL_HASH_ITERATE_INIT) { /* the hash table is empty */ if (p_table->num_entries == 0) { *p_data = NULL; return (DAT_SUCCESS); } /* find the first bucket with valid data */ p_table->iterator_bucket = 0; while (p_table->iterator_bucket < p_table->tbl_size) { curr = &p_table->table[p_table->iterator_bucket]; if (NO_DATUM(curr->datum)) { /* empty bucket - move on */ p_table->iterator_bucket++; } else { break; } } /* should not be empty if num_entries is non-zero */ dapl_os_assert(!NO_DATUM(curr->datum)); if (p_table->iterator_bucket == p_table->tbl_size) { p_table->iterator = NULL; } else { p_table->iterator = curr; } } else { dapl_os_assert(op == DAPL_HASH_ITERATE_NEXT); } /* iterator points to the element to be returned */ if (p_table->iterator == NULL) { /* nothing more left in the hashtable */ *p_data = NULL; return (DAT_SUCCESS); } dapl_dbg_log(DAPL_DBG_TYPE_EP, "dapls_hash_iterate: entry found=(%p), bucket(%d)\n", p_table->iterator, p_table->iterator_bucket); *p_data = p_table->iterator->datum; curr = p_table->iterator; /* re-position iterator to point to the next valid element */ if (curr->next_element != NULL) { /* found the next element */ p_table->iterator = curr->next_element; dapl_os_assert(!NO_DATUM(p_table->iterator->datum)); } else { p_table->iterator = NULL; /* * We are here means we've hit the end of the current bucket, * so start searching for next bucket with a valid entry - * we only need to look at the head of the bucket */ p_table->iterator_bucket++; while (p_table->iterator_bucket < p_table->tbl_size) { curr = &p_table->table[p_table->iterator_bucket]; if (NO_DATUM(curr->datum)) { p_table->iterator_bucket++; } else { p_table->iterator = curr; break; } } } return (DAT_SUCCESS); }