diff options
Diffstat (limited to 'usr/src/lib/smbsrv/libsmb/common/smb_ht.c')
-rw-r--r-- | usr/src/lib/smbsrv/libsmb/common/smb_ht.c | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/usr/src/lib/smbsrv/libsmb/common/smb_ht.c b/usr/src/lib/smbsrv/libsmb/common/smb_ht.c new file mode 100644 index 0000000000..d167931539 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmb/common/smb_ht.c @@ -0,0 +1,639 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * 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 <smbsrv/hash_table.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 (see hash_table.h). + * + * 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); +} |