a2223c7302
doesn't depend on the 'comp' set. ok espie@ deraadt@
482 lines
13 KiB
C
482 lines
13 KiB
C
/*
|
|
* Mixed hash table / binary tree code.
|
|
* (c) Thomas Pornin 2002
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 4. The name of the authors may not be used to endorse or promote
|
|
* products derived from this software without specific prior written
|
|
* permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
|
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include "nhash.h"
|
|
#include "mem.h"
|
|
|
|
/*
|
|
* Hash a string into an `unsigned' value. This function is derived
|
|
* from the hash function used in the ELF binary object file format
|
|
* hash tables. The result size is a 32-bit number if the `unsigned'
|
|
* type is big enough to hold 32-bit arbitrary numbers, a 16-bit number
|
|
* otherwise.
|
|
*/
|
|
static unsigned hash_string(char *name)
|
|
{
|
|
unsigned h = 0;
|
|
|
|
for (h = 0; *name; name ++) {
|
|
unsigned g;
|
|
|
|
h = (h << 4) + *(unsigned char *)name;
|
|
#if UINT_MAX >= 0xffffffffU
|
|
g = h & 0xF0000000U;
|
|
h ^= (g >> 24);
|
|
#else
|
|
g = h & 0xF000U;
|
|
h ^= (g >> 12);
|
|
#endif
|
|
h &= ~g;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
/*
|
|
* Each item in the table is a structure beginning with a `hash_item_header'
|
|
* structure. Those headers define binary trees such that all left-descendants
|
|
* (respectively right-descendants) of a given tree node have an associated
|
|
* hash value strictly smaller (respectively greater) than the hash value
|
|
* associated with this node.
|
|
*
|
|
* The `ident' field points to an array of char. The `sizeof(unsigned)'
|
|
* first `char' contain a copy of an `unsigned' value which is the hashed
|
|
* string, except the least significant bit. When this bit is set to 0,
|
|
* the node contains the unique item using that hash value. If the bit
|
|
* is set to 1, then there are several items with that hash value.
|
|
*
|
|
* When several items share the same hash value, they are linked together
|
|
* in a linked list by their `left' field. The node contains no data;
|
|
* it is a "fake item".
|
|
*
|
|
* The `char' following the hash value encode the item name for true items.
|
|
* For fake items, they contain the pointer to the first true item of the
|
|
* corresponding link list (suitably aligned).
|
|
*
|
|
* There are HTT_NUM_TREES trees; the items are sorted among trees by the
|
|
* lest significant bits of their hash value.
|
|
*/
|
|
|
|
static void internal_init(HTT *htt, void (*deldata)(void *), int reduced)
|
|
{
|
|
htt->deldata = deldata;
|
|
if (reduced) {
|
|
HTT2 *htt2 = (HTT2 *)htt;
|
|
|
|
htt2->tree[0] = htt2->tree[1] = NULL;
|
|
} else {
|
|
unsigned u;
|
|
|
|
for (u = 0; u < HTT_NUM_TREES; u ++) htt->tree[u] = NULL;
|
|
}
|
|
}
|
|
|
|
/* see nhash.h */
|
|
void HTT_init(HTT *htt, void (*deldata)(void *))
|
|
{
|
|
internal_init(htt, deldata, 0);
|
|
}
|
|
|
|
/* see nhash.h */
|
|
void HTT2_init(HTT2 *htt, void (*deldata)(void *))
|
|
{
|
|
internal_init((HTT *)htt, deldata, 1);
|
|
}
|
|
|
|
#define PTR_SHIFT (sizeof(hash_item_header *) * \
|
|
((sizeof(unsigned) + sizeof(hash_item_header *) - 1) / \
|
|
sizeof(hash_item_header *)))
|
|
|
|
#define TREE(u) (*(reduced ? ((HTT2 *)htt)->tree + ((u) & 1) \
|
|
: htt->tree + ((u) & (HTT_NUM_TREES - 1))))
|
|
|
|
/*
|
|
* Find a node for the given hash value. If `father' is not NULL, fill
|
|
* `*father' with a pointer to the node's father.
|
|
* If the return value is NULL, then no existing node was found; if `*father'
|
|
* is also NULL, the tree is empty. If the return value is not NULL but
|
|
* `*father' is NULL, then the found node is the tree root.
|
|
*
|
|
* If `father' is not NULL, then `*leftson' is filled with 1 if the node
|
|
* was looked for as the father left son, 0 otherwise.
|
|
*/
|
|
static hash_item_header *find_node(HTT *htt, unsigned u,
|
|
hash_item_header **father, int *leftson, int reduced)
|
|
{
|
|
hash_item_header *node = TREE(u);
|
|
hash_item_header *nodef = NULL;
|
|
int ls;
|
|
|
|
u &= ~1U;
|
|
while (node != NULL) {
|
|
unsigned v = *(unsigned *)(node->ident);
|
|
unsigned w = v & ~1U;
|
|
|
|
if (u == w) break;
|
|
nodef = node;
|
|
if (u < w) {
|
|
node = node->left;
|
|
ls = 1;
|
|
} else {
|
|
node = node->right;
|
|
ls = 0;
|
|
}
|
|
}
|
|
if (father != NULL) {
|
|
*father = nodef;
|
|
*leftson = ls;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
static void *internal_get(HTT *htt, char *name, int reduced)
|
|
{
|
|
unsigned u = hash_string(name), v;
|
|
hash_item_header *node = find_node(htt, u, NULL, NULL, reduced);
|
|
|
|
if (node == NULL) return NULL;
|
|
v = *(unsigned *)(node->ident);
|
|
if ((v & 1U) == 0) {
|
|
return (strcmp(HASH_ITEM_NAME(node), name) == 0) ? node : NULL;
|
|
}
|
|
node = *(hash_item_header **)(node->ident + PTR_SHIFT);
|
|
while (node != NULL) {
|
|
if (strcmp(HASH_ITEM_NAME(node), name) == 0) return node;
|
|
node = node->left;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* see nhash.h */
|
|
void *HTT_get(HTT *htt, char *name)
|
|
{
|
|
return internal_get(htt, name, 0);
|
|
}
|
|
|
|
/* see nhash.h */
|
|
void *HTT2_get(HTT2 *htt, char *name)
|
|
{
|
|
return internal_get((HTT *)htt, name, 1);
|
|
}
|
|
|
|
/*
|
|
* Make an item identifier from its name and its hash value.
|
|
*/
|
|
static char *make_ident(char *name, unsigned u)
|
|
{
|
|
size_t n = strlen(name) + 1;
|
|
char *ident = getmem(n + sizeof(unsigned));
|
|
|
|
*(unsigned *)ident = u & ~1U;
|
|
memcpy(ident + sizeof(unsigned), name, n);
|
|
return ident;
|
|
}
|
|
|
|
/*
|
|
* Make an identifier for a fake item, pointing to a true item.
|
|
*/
|
|
static char *make_fake_ident(unsigned u, hash_item_header *next)
|
|
{
|
|
char *ident = getmem(PTR_SHIFT + sizeof(hash_item_header *));
|
|
|
|
*(unsigned *)ident = u | 1U;
|
|
*(hash_item_header **)(ident + PTR_SHIFT) = next;
|
|
return ident;
|
|
}
|
|
|
|
/*
|
|
* Adding an item is straightforward:
|
|
* 1. look for its emplacement
|
|
* 2. if no node is found, use the item as a new node and link it to the tree
|
|
* 3. if a node is found:
|
|
* 3.1. if the node is real, check for name inequality, then create a
|
|
* fake node and assemble the two-element linked list
|
|
* 3.2. if the node is fake, look for the name in the list; if not found,
|
|
* add the node at the list end
|
|
*/
|
|
static void *internal_put(HTT *htt, void *item, char *name, int reduced)
|
|
{
|
|
unsigned u = hash_string(name), v;
|
|
int ls;
|
|
hash_item_header *father;
|
|
hash_item_header *node = find_node(htt, u, &father, &ls, reduced);
|
|
hash_item_header *itemg = item, *pnode;
|
|
|
|
if (node == NULL) {
|
|
itemg->left = itemg->right = NULL;
|
|
itemg->ident = make_ident(name, u);
|
|
if (father == NULL) {
|
|
TREE(u) = itemg;
|
|
} else if (ls) {
|
|
father->left = itemg;
|
|
} else {
|
|
father->right = itemg;
|
|
}
|
|
return NULL;
|
|
}
|
|
v = *(unsigned *)(node->ident);
|
|
if ((v & 1U) == 0) {
|
|
if (strcmp(HASH_ITEM_NAME(node), name) == 0)
|
|
return node;
|
|
pnode = getmem(sizeof *pnode);
|
|
pnode->left = node->left;
|
|
pnode->right = node->right;
|
|
pnode->ident = make_fake_ident(u, node);
|
|
node->left = itemg;
|
|
node->right = NULL;
|
|
itemg->left = itemg->right = NULL;
|
|
itemg->ident = make_ident(name, u);
|
|
if (father == NULL) {
|
|
TREE(u) = pnode;
|
|
} else if (ls) {
|
|
father->left = pnode;
|
|
} else {
|
|
father->right = pnode;
|
|
}
|
|
return NULL;
|
|
}
|
|
node = *(hash_item_header **)(node->ident + PTR_SHIFT);
|
|
while (node != NULL) {
|
|
if (strcmp(HASH_ITEM_NAME(node), name) == 0) return node;
|
|
pnode = node;
|
|
node = node->left;
|
|
}
|
|
itemg->left = itemg->right = NULL;
|
|
itemg->ident = make_ident(name, u);
|
|
pnode->left = itemg;
|
|
return NULL;
|
|
}
|
|
|
|
/* see nhash.h */
|
|
void *HTT_put(HTT *htt, void *item, char *name)
|
|
{
|
|
return internal_put(htt, item, name, 0);
|
|
}
|
|
|
|
/* see nhash.h */
|
|
void *HTT2_put(HTT2 *htt, void *item, char *name)
|
|
{
|
|
return internal_put((HTT *)htt, item, name, 1);
|
|
}
|
|
|
|
/*
|
|
* A fake node subnode list has shrunk to one item only; make the
|
|
* node real again.
|
|
* fnode the fake node
|
|
* node the last remaining node
|
|
* father the fake node father (NULL if the fake node is root)
|
|
* leftson 1 if the fake node is a left son, 0 otehrwise
|
|
* u the hash value for this node
|
|
*/
|
|
static void shrink_node(HTT *htt, hash_item_header *fnode,
|
|
hash_item_header *node, hash_item_header *father, int leftson,
|
|
unsigned u, int reduced)
|
|
{
|
|
node->left = fnode->left;
|
|
node->right = fnode->right;
|
|
if (father == NULL) {
|
|
TREE(u) = node;
|
|
} else if (leftson) {
|
|
father->left = node;
|
|
} else {
|
|
father->right = node;
|
|
}
|
|
freemem(fnode->ident);
|
|
freemem(fnode);
|
|
}
|
|
|
|
/*
|
|
* Deletion algorithm:
|
|
* 1. look for the node; if not found, exit
|
|
* 2. if the node is real:
|
|
* 2.1. check for equality; exit otherwise
|
|
* 2.2. delete the node
|
|
* 2.3. promote the leftest of right descendants or rightest of left
|
|
* descendants
|
|
* 3. if the node is fake:
|
|
* 3.1. check the list items for equality; exit otherwise
|
|
* 3.2. delete the correct item
|
|
* 3.3. if there remains only one item, supress the fake node
|
|
*/
|
|
static int internal_del(HTT *htt, char *name, int reduced)
|
|
{
|
|
unsigned u = hash_string(name), v;
|
|
int ls;
|
|
hash_item_header *father;
|
|
hash_item_header *node = find_node(htt, u, &father, &ls, reduced);
|
|
hash_item_header *pnode, *fnode, *znode;
|
|
char *tmp;
|
|
|
|
if (node == NULL) return 0;
|
|
v = *(unsigned *)(node->ident);
|
|
if ((v & 1U) != 0) {
|
|
fnode = node;
|
|
node = znode = *(hash_item_header **)(node->ident + PTR_SHIFT);
|
|
pnode = NULL;
|
|
while (node != NULL) {
|
|
if (strcmp(HASH_ITEM_NAME(node), name) == 0) break;
|
|
pnode = node;
|
|
node = node->left;
|
|
}
|
|
if (node == NULL) return 0;
|
|
if (pnode == NULL) {
|
|
/*
|
|
* We supress the first item in the list.
|
|
*/
|
|
*(hash_item_header **)(fnode->ident + PTR_SHIFT) =
|
|
node->left;
|
|
if (node->left->left == NULL) {
|
|
shrink_node(htt, fnode, node->left,
|
|
father, ls, u, reduced);
|
|
}
|
|
} else {
|
|
pnode->left = node->left;
|
|
if (pnode->left == NULL && znode == pnode) {
|
|
shrink_node(htt, fnode, pnode,
|
|
father, ls, u, reduced);
|
|
}
|
|
}
|
|
} else {
|
|
if (strcmp(HASH_ITEM_NAME(node), name) != 0) return 0;
|
|
if (node->left != NULL) {
|
|
for (znode = node, pnode = node->left; pnode->right;
|
|
znode = pnode, pnode = pnode->right);
|
|
if (znode != node) {
|
|
znode->right = pnode->left;
|
|
pnode->left = node->left;
|
|
}
|
|
pnode->right = node->right;
|
|
} else if (node->right != NULL) {
|
|
for (znode = node, pnode = node->right; pnode->left;
|
|
znode = pnode, pnode = pnode->left);
|
|
if (znode != node) {
|
|
znode->left = pnode->right;
|
|
pnode->right = node->right;
|
|
}
|
|
pnode->left = node->left;
|
|
} else pnode = NULL;
|
|
if (father == NULL) {
|
|
TREE(u) = pnode;
|
|
} else if (ls) {
|
|
father->left = pnode;
|
|
} else {
|
|
father->right = pnode;
|
|
}
|
|
}
|
|
tmp = node->ident;
|
|
htt->deldata(node);
|
|
freemem(tmp);
|
|
return 1;
|
|
}
|
|
|
|
/* see nhash.h */
|
|
int HTT_del(HTT *htt, char *name)
|
|
{
|
|
return internal_del(htt, name, 0);
|
|
}
|
|
|
|
/* see nhash.h */
|
|
int HTT2_del(HTT2 *htt, char *name)
|
|
{
|
|
return internal_del((HTT *)htt, name, 1);
|
|
}
|
|
|
|
/*
|
|
* Apply `action()' on all nodes of the tree whose root is given as
|
|
* parameter `node'. If `wipe' is non-zero, the nodes are removed
|
|
* from memory.
|
|
*/
|
|
static void scan_node(hash_item_header *node, void (*action)(void *), int wipe)
|
|
{
|
|
unsigned v;
|
|
|
|
if (node == NULL) return;
|
|
scan_node(node->left, action, wipe);
|
|
scan_node(node->right, action, wipe);
|
|
v = *(unsigned *)(node->ident);
|
|
if ((v & 1U) != 0) {
|
|
hash_item_header *pnode, *nnode;
|
|
|
|
for (pnode = *(hash_item_header **)(node->ident + PTR_SHIFT);
|
|
pnode != NULL; pnode = nnode) {
|
|
char *tmp = pnode->ident;
|
|
|
|
nnode = pnode->left;
|
|
action(pnode);
|
|
if (wipe) freemem(tmp);
|
|
}
|
|
if (wipe) {
|
|
freemem(node->ident);
|
|
freemem(node);
|
|
}
|
|
} else {
|
|
char *tmp = node->ident;
|
|
|
|
action(node);
|
|
if (wipe) freemem(tmp);
|
|
}
|
|
}
|
|
|
|
/* see nhash.h */
|
|
void HTT_scan(HTT *htt, void (*action)(void *))
|
|
{
|
|
unsigned u;
|
|
|
|
for (u = 0; u < HTT_NUM_TREES; u ++) {
|
|
scan_node(htt->tree[u], action, 0);
|
|
}
|
|
}
|
|
|
|
/* see nhash.h */
|
|
void HTT2_scan(HTT2 *htt, void (*action)(void *))
|
|
{
|
|
scan_node(htt->tree[0], action, 0);
|
|
scan_node(htt->tree[1], action, 0);
|
|
}
|
|
|
|
/* see nhash.h */
|
|
void HTT_kill(HTT *htt)
|
|
{
|
|
unsigned u;
|
|
|
|
for (u = 0; u < HTT_NUM_TREES; u ++) {
|
|
scan_node(htt->tree[u], htt->deldata, 1);
|
|
}
|
|
}
|
|
|
|
/* see nhash.h */
|
|
void HTT2_kill(HTT2 *htt)
|
|
{
|
|
scan_node(htt->tree[0], htt->deldata, 1);
|
|
scan_node(htt->tree[1], htt->deldata, 1);
|
|
}
|