/* * 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 #include #include 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); }