/*
* 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) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Generic hash table library. The hash table is an array of pointers
* to items. Hash collisions are handled using linked lists from the
* table entries. A handle is associated with each table, which is used
* to maintain the hash table.
*
* +------+ +-------+ +----+ +----+
* |handle|---> |index 0|--->|item|--->|item|--->
* | ... | +-------+ +----+ +----+
* | ... | |index 1|--->
* +------+ +-------+ +----+ +----+ +----+
* |index 2|--->|item|--->|item|--->|item|--->
* +-------+ +----+ +----+ +----+
* | ... |--->
* +-------+
* | ... |--->
* +-------+
* |index n|--->
* +-------+
*
*/
#include <stdlib.h>
#include <strings.h>
#include <smbns_hash.h>
static size_t ht_default_hash(HT_HANDLE *handle, const char *key);
/*
* ht_is_power2
*
* Inline function to determine if a value is a power of two. This
* function is used by the library to validate the table size when
* a new table is created.
*
* Returns 1 if value given is power of two, otherwise returns 0.
*/
static size_t
ht_is_power2(size_t value)
{
return (((value & (value - 1)) == 0)? 1 : 0);
}
/*
* ht_create_table
*
* Create a hash table. The table size must be a positive integer and
* must be a power of two. The key size must be a positive integer.
* For null terminated keys, the key size does not need to include the
* null terminating character. The type of key is indicated by the flags.
*
* The handle and the table are are malloc'd using a single call, to
* avoid two allocations. The table is located immediately after the
* handle.
*
* On success a pointer to an opaque handle is returned. Otherwise a
* null pointer is returned.
*/
HT_HANDLE *
ht_create_table(size_t table_size, size_t key_size, size_t flags)
{
HT_HANDLE *ht;
size_t msize;
size_t i;
if ((table_size == 0) || (key_size == 0))
return (NULL);
if (ht_is_power2(table_size) == 0)
return (NULL);
msize = sizeof (HT_HANDLE) + (sizeof (HT_TABLE_ENTRY) * table_size);
if ((ht = (HT_HANDLE *)malloc(msize)) == 0)
return (NULL);
/*LINTED E_BAD_PTR_CAST_ALIGN*/
ht->ht_table = (HT_TABLE_ENTRY *)((char *)ht + sizeof (HT_HANDLE));
ht->ht_table_size = table_size;
ht->ht_table_mask = table_size - 1;
ht->ht_key_size = key_size;
ht->ht_total_items = 0;
ht->ht_flags = flags;
ht->ht_hash = ht_default_hash;
ht->ht_callback = 0;
ht->ht_sequence = random();
ht->ht_cmp = ((flags & HTHF_FIXED_KEY) == 0)
? (HT_CMP)strncmp : (HT_CMP)memcmp;
for (i = 0; i < table_size; i++)
bzero(&ht->ht_table[i], sizeof (HT_TABLE_ENTRY));
return (ht);
}
/*
* ht_destroy_table
*
* Destroy a hash table. All entries in the table are removed, which
* may invoke the callback if it's installed, and the memory is freed.
*/
void
ht_destroy_table(HT_HANDLE *handle)
{
HT_ITEM *item;
HT_ITERATOR iterator;
if (handle == 0)
return;
/* To remove marked entries */
(void) ht_clean_table(handle);
while ((item = ht_findfirst(handle, &iterator)) != 0)
(void) ht_remove_item(handle, item->hi_key);
free(handle);
}
/*
* ht_get_total_items
*
* Return the total number of items in the table. Returns -1 if the
* handle is invalid.
*/
size_t
ht_get_total_items(HT_HANDLE *handle)
{
if (handle == 0)
return ((size_t)-1);
return (handle->ht_total_items);
}
/*
* ht_default_hash
*
* Default hash function to compute the table index (hash value) based
* on the specified key. This will identify the location for the
* corresponding item in the hash table. The handle and key pointers
* should be validated before this function is called.
*
* Returns the table index location for the item.
*/
static size_t
ht_default_hash(HT_HANDLE *handle, const char *key)
{
unsigned int hash_ndx = 0;
size_t rval;
if ((handle->ht_flags & HTHF_FIXED_KEY) == 0) {
while (*key) {
hash_ndx += *key;
++key;
}
} else {
int key_len = handle->ht_key_size;
while (key_len--) {
hash_ndx += *key;
++key;
}
}
rval = (hash_ndx * HASH_MESH_VALUE) & handle->ht_table_mask;
return (rval);
}
/*
* ht_set_cmpfn
*
* Replace the current compare function. As the this is function
* for comparing items' key, it should not be called while there are
* items in the table.
*/
void
ht_set_cmpfn(HT_HANDLE *handle, HT_CMP cmpfn)
{
if (handle)
handle->ht_cmp = cmpfn;
}
/*
* ht_add_item
*
* Adds an item to a hash table. The hash table is identified by the
* handle and the key is used to generate a hashed index. The data
* item can be null; it is never dereferenced. We don't check for
* duplicates. If duplicate keys are added to the table, the last
* item added will be to the front of the duplicate list.
*
* The table sequence number may be modified here.
*
* If the item is successfully inserted, a pointer to the item object
* is returned. Otherwise a null pointer is returned.
*/
HT_ITEM *
ht_add_item(HT_HANDLE *handle, const char *key, const void *data)
{
size_t h_index, key_len;
size_t msize;
HT_ITEM *item;
if (handle == 0 || key == 0)
return (NULL);
if (handle->ht_flags & HTHF_FIXED_KEY) {
key_len = handle->ht_key_size;
} else {
key_len = strlen(key);
if (key_len > handle->ht_key_size)
return (NULL);
/* Include the null terminator */
++key_len;
}
msize = key_len + sizeof (HT_ITEM);
if ((item = malloc(msize)) == 0)
return (NULL);
item->hi_key = (char *)item + sizeof (HT_ITEM);
(void) memcpy(item->hi_key, key, key_len);
item->hi_data = (void *)data;
item->hi_flags = 0;
h_index = handle->ht_hash(handle, key);
/*
* Add to the front of the list.
*/
item->hi_next = handle->ht_table[h_index].he_head;
handle->ht_table[h_index].he_head = item;
handle->ht_table[h_index].he_count++;
handle->ht_total_items++;
handle->ht_sequence++;
return (item);
}
/*
* ht_replace_item
*
* Replace an item in a hash table. The item associated with key is removed
* using ht_remove_item and a new item is added using ht_add_item. We rely
* on parameter validation in ht_remove_item and ht_add_item.
*
* The table sequence number may be modified here.
*/
HT_ITEM *
ht_replace_item(HT_HANDLE *handle, const char *key, const void *data)
{
(void) ht_remove_item(handle, key);
return (ht_add_item(handle, key, data));
}
/*
* ht_remove_item
*
* Remove an item from a hash table. If there are duplicate keys, then the
* first key found will be deleted. Note that the data pointer is never
* dereferenced. If a callback is installed, it will be invoked and the
* return value will be null. Otherwise, the data pointer supplied by the
* application will be returned. If there is an error, a null pointer will
* be returned.
*
* The table sequence number may be modified here.
*/
void *
ht_remove_item(HT_HANDLE *handle, const char *key)
{
size_t h_index;
HT_ITEM *cur, *prev;
int key_len;
void *data = 0;
if (handle == 0 || key == 0)
return (NULL);
if ((handle->ht_flags & HTHF_FIXED_KEY) == 0)
key_len = strlen(key) + 1;
else
key_len = handle->ht_key_size;
h_index = handle->ht_hash(handle, key);
cur = handle->ht_table[h_index].he_head;
prev = 0;
while (cur) {
if (!(cur->hi_flags & HTIF_MARKED_DELETED) &&
(handle->ht_cmp(cur->hi_key, key, key_len) == 0)) {
/* found key */
if (prev == 0)
handle->ht_table[h_index].he_head =
cur->hi_next;
else
prev->hi_next = cur->hi_next;
if (handle->ht_callback)
handle->ht_callback(cur);
else
data = cur->hi_data;
/*
* Since the key and the item were allocated as
* a single chunk, we only need one free here.
*/
free(cur);
handle->ht_table[h_index].he_count--;
handle->ht_total_items--;
handle->ht_sequence++;
break;
}
prev = cur;
cur = cur->hi_next;
}
return (data);
}
/*
* ht_find_item
*
* Find an item in a hash table. If there are duplicate keys then the
* first item found (which will be the last one added) will be returned.
*
* Returns a pointer to an item. Otherwise returns a null pointer to
* indicate an error or that the key didn't match anything in the table.
*/
HT_ITEM *
ht_find_item(HT_HANDLE *handle, const char *key)
{
size_t h_index;
HT_ITEM *cur;
int key_len;
if (handle == 0 || key == 0)
return (NULL);
if ((handle->ht_flags & HTHF_FIXED_KEY) == 0)
key_len = strlen(key) + 1;
else
key_len = handle->ht_key_size;
h_index = handle->ht_hash(handle, key);
cur = handle->ht_table[h_index].he_head;
while (cur) {
if (!(cur->hi_flags & HTIF_MARKED_DELETED) &&
(handle->ht_cmp(cur->hi_key, key, key_len) == 0))
return (cur);
cur = cur->hi_next;
}
return (NULL);
}
/*
* ht_register_callback
*
* Register an application callback function that can be used to process
* an item when it is removed from the table, i.e. free any memory
* allocated for that data item.
*
* The previous callback function pointer, which may be null, before
* registering the new one. This provides the caller with the option to
* restore a previous callback as required.
*/
HT_CALLBACK
ht_register_callback(HT_HANDLE *handle, HT_CALLBACK callback)
{
HT_CALLBACK old_callback;
if (handle == 0)
return (NULL);
old_callback = handle->ht_callback;
handle->ht_callback = callback;
return (old_callback);
}
/*
* ht_clean_table
*
* This function removes all the items that are marked for deletion. Note
* that this will invoke the callback, if one has been installed. If this
* call is used, the callback mechanism is the only way for an application
* to free the item data if it was dynamically allocated.
*
* The table sequence number may be modified here.
*
* Returns 0 if the handle is valid; otherwise returns -1.
*/
size_t
ht_clean_table(HT_HANDLE *handle)
{
size_t i;
HT_ITEM *cur, *prev;
if (handle == 0)
return ((size_t)-1);
for (i = 0; i < handle->ht_table_size; i++) {
cur = handle->ht_table[i].he_head;
prev = 0;
while (cur) {
if (cur->hi_flags & HTIF_MARKED_DELETED) {
/*
* We have a marked item: remove it.
*/
if (prev == 0)
handle->ht_table[i].he_head =
cur->hi_next;
else
prev->hi_next = cur->hi_next;
if (handle->ht_callback)
handle->ht_callback(cur);
/*
* Since the key and the item were allocated as
* a single chunk, we only need one free here.
*/
free(cur);
handle->ht_table[i].he_count--;
handle->ht_sequence++;
if (prev == 0)
cur = handle->ht_table[i].he_head;
else
cur = prev->hi_next;
continue;
}
prev = cur;
cur = cur->hi_next;
}
}
return (0);
}
/*
* ht_mark_delete
*
* This function marks an item for deletion, which may be useful when
* using findfirst/findnext to avoid modifying the table during the
* table scan. Marked items can be removed later using ht_clean_table.
*/
void
ht_mark_delete(HT_HANDLE *handle, HT_ITEM *item)
{
if (handle && item) {
item->hi_flags |= HTIF_MARKED_DELETED;
handle->ht_total_items--;
}
}
/*
* ht_clear_delete
*
* This function clear an item from marked for deletion list.
*/
void
ht_clear_delete(HT_HANDLE *handle, HT_ITEM *item)
{
if (handle && item) {
item->hi_flags &= ~HTIF_MARKED_DELETED;
handle->ht_total_items++;
}
}
/*
* ht_bucket_search
*
* Returns first item which is not marked as deleted
* in the specified bucket by 'head'
*/
static HT_ITEM *
ht_bucket_search(HT_ITEM *head)
{
HT_ITEM *item = head;
while ((item != 0) && (item->hi_flags & HTIF_MARKED_DELETED))
item = item->hi_next;
return (item);
}
/*
* ht_findfirst
*
* This function is used to begin an iteration through the hash table.
* The iterator is initialized and the first item in the table (as
* determined by the hash algorithm) is returned. The current sequence
* number is stored in the iterator to determine whether or not the
* the table has changed between calls. If the table is empty, a null
* pointer is returned.
*/
HT_ITEM *
ht_findfirst(HT_HANDLE *handle, HT_ITERATOR *iterator)
{
HT_ITEM *item;
size_t h_index;
if (handle == 0 || iterator == 0 || handle->ht_total_items == 0)
return (NULL);
(void) memset(iterator, 0, sizeof (HT_ITERATOR));
iterator->hti_handle = handle;
iterator->hti_sequence = handle->ht_sequence;
for (h_index = 0; h_index < handle->ht_table_size; ++h_index) {
item = ht_bucket_search(handle->ht_table[h_index].he_head);
if (item != 0) {
iterator->hti_index = h_index;
iterator->hti_item = item;
return (item);
}
}
return (NULL);
}
/*
* ht_findnext
*
* Find the next item in the table for the given iterator. Iterators must
* be initialized by ht_findfirst, which will also return the first item
* in the table. If an item is available, a pointer to it is returned.
* Otherwise a null pointer is returned. A null pointer may indicate:
*
* - an invalid iterator (i.e. ht_findfirst has not been called)
* - the table has changed since the previous findfirst/findnext
* - the entire table has been traversed
*
* The caller can use ht_get_total_items to determine whether or not all
* of the items in the table have been visited.
*/
HT_ITEM *
ht_findnext(HT_ITERATOR *iterator)
{
HT_HANDLE *handle;
HT_ITEM *item;
size_t total;
size_t index;
if (iterator == 0 || iterator->hti_handle == 0 ||
iterator->hti_sequence == 0) {
/* Invalid iterator */
return (NULL);
}
handle = iterator->hti_handle;
if (iterator->hti_item == 0 ||
iterator->hti_sequence != handle->ht_sequence) {
/*
* No more items or the table has changed
* since the last call.
*/
return (NULL);
}
/*
* Check for another item in the current bucket.
*/
item = ht_bucket_search(iterator->hti_item->hi_next);
if (item != 0) {
iterator->hti_item = item;
return (item);
}
/*
* Nothing else in the current bucket. Look for another
* bucket with something in it and return the head item.
*/
total = handle->ht_table_size;
for (index = iterator->hti_index + 1; index < total; ++index) {
item = ht_bucket_search(handle->ht_table[index].he_head);
if (item != 0) {
iterator->hti_index = index;
iterator->hti_item = item;
return (item);
}
}
iterator->hti_index = 0;
iterator->hti_item = 0;
iterator->hti_sequence = 0;
return (NULL);
}