Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: htable.c,v 1.5 2026/05/09 18:49:22 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	htable 3
      6 /* SUMMARY
      7 /*	hash table manager
      8 /* SYNOPSIS
      9 /*	#include <htable.h>
     10 /*
     11 /*	typedef	struct {
     12 /* .in +4
     13 /*		char	*key;
     14 /*		void	*value;
     15 /*		/* private fields... */
     16 /* .in -4
     17 /*	} HTABLE_INFO;
     18 /*
     19 /*	HTABLE	*htable_create(size)
     20 /*	int	size;
     21 /*
     22 /*	HTABLE_INFO *htable_enter(table, key, value)
     23 /*	HTABLE	*table;
     24 /*	const char *key;
     25 /*	void	*value;
     26 /*
     27 /*	char	*htable_find(table, key)
     28 /*	HTABLE	*table;
     29 /*	const char *key;
     30 /*
     31 /*	HTABLE_INFO *htable_locate(table, key)
     32 /*	HTABLE	*table;
     33 /*	const char *key;
     34 /*
     35 /*	void	htable_delete(table, key, free_fn)
     36 /*	HTABLE	*table;
     37 /*	const char *key;
     38 /*	void	(*free_fn)(void *);
     39 /*
     40 /*	void	htable_free(table, free_fn)
     41 /*	HTABLE	*table;
     42 /*	void	(*free_fn)(void *);
     43 /*
     44 /*	void	htable_walk(table, action, ptr)
     45 /*	HTABLE	*table;
     46 /*	void	(*action)(HTABLE_INFO *, void *ptr);
     47 /*	void	*ptr;
     48 /*
     49 /*	HTABLE_INFO **htable_list(table)
     50 /*	HTABLE	*table;
     51 /*
     52 /*	HTABLE_INFO *htable_sequence(table, how)
     53 /*	HTABLE	*table;
     54 /*	int	how;
     55 /* DESCRIPTION
     56 /*	This module maintains one or more hash tables. Each table entry
     57 /*	consists of a unique string-valued lookup key and a generic
     58 /*	character-pointer value.
     59 /*	The tables are automatically resized when they fill up. When the
     60 /*	values to be remembered are not character pointers, proper casts
     61 /*	should be used or the code will not be portable.
     62 /*
     63 /*	htable_create() creates a table of the specified size and returns a
     64 /*	pointer to the result. The lookup keys are saved with mystrdup().
     65 /*	htable_enter() stores a (key, value) pair into the specified table
     66 /*	and returns a pointer to the resulting entry. The code does not
     67 /*	check if an entry with that key already exists: use htable_locate()
     68 /*	for updating an existing entry.
     69 /*
     70 /*	htable_find() returns the value that was stored under the given key,
     71 /*	or a null pointer if it was not found. In order to distinguish
     72 /*	a null value from a non-existent value, use htable_locate().
     73 /*
     74 /*	htable_locate() returns a pointer to the entry that was stored
     75 /*	for the given key, or a null pointer if it was not found.
     76 /*
     77 /*	htable_delete() removes one entry that was stored under the given key.
     78 /*	If the free_fn argument is not a null pointer, the corresponding
     79 /*	function is called with as argument the non-zero value stored under
     80 /*	the key.
     81 /*
     82 /*	htable_free() destroys a hash table, including contents. If the free_fn
     83 /*	argument is not a null pointer, the corresponding function is called
     84 /*	for each table entry, with as argument the non-zero value stored
     85 /*	with the entry.
     86 /*
     87 /*	htable_walk() invokes the action function for each table entry, with
     88 /*	a pointer to the entry as its argument. The ptr argument is passed
     89 /*	on to the action function.
     90 /*
     91 /*	htable_list() returns a null-terminated list of pointers to
     92 /*	all elements in the named table. The list should be passed to
     93 /*	myfree().
     94 /*
     95 /*	htable_sequence() returns the first or next element depending
     96 /*	on the value of the "how" argument.  Specify HTABLE_SEQ_FIRST
     97 /*	to start a new sequence, HTABLE_SEQ_NEXT to continue, and
     98 /*	HTABLE_SEQ_STOP to terminate a sequence early.
     99 /* RESTRICTIONS
    100 /*	A callback function should not modify the hash table that is
    101 /*	specified to its caller.
    102 /* DIAGNOSTICS
    103 /*	The following conditions are reported and cause the program to
    104 /*	terminate immediately: memory allocation failure; an attempt
    105 /*	to delete a non-existent entry.
    106 /* SEE ALSO
    107 /*	mymalloc(3) memory management wrapper
    108 /*	hash_fnv(3) Fowler/Noll/Vo hash function
    109 /* LICENSE
    110 /* .ad
    111 /* .fi
    112 /*	The Secure Mailer license must be distributed with this software.
    113 /* AUTHOR(S)
    114 /*	Wietse Venema
    115 /*	IBM T.J. Watson Research
    116 /*	P.O. Box 704
    117 /*	Yorktown Heights, NY 10598, USA
    118 /*
    119 /*	Wietse Venema
    120 /*	Google, Inc.
    121 /*	111 8th Avenue
    122 /*	New York, NY 10011, USA
    123 /*--*/
    124 
    125 /* C library */
    126 
    127 #include <sys_defs.h>
    128 #include <string.h>
    129 
    130 /* Local stuff */
    131 
    132 #include "mymalloc.h"
    133 #include "msg.h"
    134 #include "htable.h"
    135 
    136 /* htable_hash - hash a string */
    137 
    138 #ifndef NO_HASH_FNV
    139 #include "hash_fnv.h"
    140 
    141 #define htable_hash(s, size) (hash_fnvz(s) % (size))
    142 
    143 #else
    144 
    145 static size_t htable_hash(const char *s, size_t size)
    146 {
    147     size_t  h = 0;
    148     size_t  g;
    149 
    150     /*
    151      * From the "Dragon" book by Aho, Sethi and Ullman.
    152      */
    153 
    154     while (*s) {
    155 	h = (h << 4U) + *(unsigned const char *) s++;
    156 	if ((g = (h & 0xf0000000)) != 0) {
    157 	    h ^= (g >> 24U);
    158 	    h ^= g;
    159 	}
    160     }
    161     return (h % size);
    162 }
    163 
    164 #endif
    165 
    166 /* htable_link - insert element into table */
    167 
    168 #define htable_link(table, element) { \
    169      HTABLE_INFO **_h = table->data + htable_hash(element->key, table->size);\
    170     element->prev = 0; \
    171     if ((element->next = *_h) != 0) \
    172 	(*_h)->prev = element; \
    173     *_h = element; \
    174     table->used++; \
    175 }
    176 
    177 /* htable_size - allocate and initialize hash table */
    178 
    179 static void htable_size(HTABLE *table, size_t size)
    180 {
    181     HTABLE_INFO **h;
    182 
    183     size |= 1;
    184 
    185     table->data = h = (HTABLE_INFO **) mymalloc(size * sizeof(HTABLE_INFO *));
    186     table->size = size;
    187     table->used = 0;
    188 
    189     while (size-- > 0)
    190 	*h++ = 0;
    191 }
    192 
    193 /* htable_create - create initial hash table */
    194 
    195 HTABLE *htable_create(ssize_t size)
    196 {
    197     HTABLE *table;
    198 
    199     table = (HTABLE *) mymalloc(sizeof(HTABLE));
    200     htable_size(table, size < 13 ? 13 : size);
    201     table->seq_bucket = table->seq_element = 0;
    202     return (table);
    203 }
    204 
    205 /* htable_grow - extend existing table */
    206 
    207 static void htable_grow(HTABLE *table)
    208 {
    209     HTABLE_INFO *ht;
    210     HTABLE_INFO *next;
    211     size_t  old_size = table->size;
    212     HTABLE_INFO **h = table->data;
    213     HTABLE_INFO **old_entries = h;
    214 
    215     htable_size(table, 2 * old_size);
    216 
    217     while (old_size-- > 0) {
    218 	for (ht = *h++; ht; ht = next) {
    219 	    next = ht->next;
    220 	    htable_link(table, ht);
    221 	}
    222     }
    223     myfree((void *) old_entries);
    224 }
    225 
    226 /* htable_enter - enter (key, value) pair */
    227 
    228 HTABLE_INFO *htable_enter(HTABLE *table, const char *key, void *value)
    229 {
    230     HTABLE_INFO *ht;
    231 
    232     if (table->used >= table->size)
    233 	htable_grow(table);
    234     ht = (HTABLE_INFO *) mymalloc(sizeof(HTABLE_INFO));
    235     ht->key = mystrdup(key);
    236     ht->value = value;
    237     htable_link(table, ht);
    238     return (ht);
    239 }
    240 
    241 /* htable_find - lookup value */
    242 
    243 void   *htable_find(HTABLE *table, const char *key)
    244 {
    245     HTABLE_INFO *ht;
    246 
    247 #define	STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0))
    248 
    249     if (table)
    250 	for (ht = table->data[htable_hash(key, table->size)]; ht; ht = ht->next)
    251 	    if (STREQ(key, ht->key))
    252 		return (ht->value);
    253     return (0);
    254 }
    255 
    256 /* htable_locate - lookup entry */
    257 
    258 HTABLE_INFO *htable_locate(HTABLE *table, const char *key)
    259 {
    260     HTABLE_INFO *ht;
    261 
    262 #define	STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0))
    263 
    264     if (table)
    265 	for (ht = table->data[htable_hash(key, table->size)]; ht; ht = ht->next)
    266 	    if (STREQ(key, ht->key))
    267 		return (ht);
    268     return (0);
    269 }
    270 
    271 /* htable_delete - delete one entry */
    272 
    273 void    htable_delete(HTABLE *table, const char *key, void (*free_fn) (void *))
    274 {
    275     if (table) {
    276 	HTABLE_INFO *ht, **sp;
    277 	HTABLE_INFO **h = table->data + htable_hash(key, table->size);
    278 
    279 #define	STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0))
    280 
    281 	for (ht = *h; ht; ht = ht->next) {
    282 	    if (STREQ(key, ht->key)) {
    283 		if (ht->next)
    284 		    ht->next->prev = ht->prev;
    285 		if (ht->prev)
    286 		    ht->prev->next = ht->next;
    287 		else
    288 		    *h = ht->next;
    289 		table->used--;
    290 		myfree(ht->key);
    291 		if (free_fn && ht->value)
    292 		    (*free_fn) (ht->value);
    293 		myfree((void *) ht);
    294 		/* In case the first/next iterator has not yet visited it */
    295 		for (sp = table->seq_element; sp && *sp; sp++) {
    296 		    if (*sp == ht) {
    297 			while ((*sp = sp[1]) != 0)
    298 			    sp += 1;
    299 			break;
    300 		    }
    301 		}
    302 		return;
    303 	    }
    304 	}
    305 	msg_panic("htable_delete: unknown_key: \"%s\"", key);
    306     }
    307 }
    308 
    309 /* htable_free - destroy hash table */
    310 
    311 void    htable_free(HTABLE *table, void (*free_fn) (void *))
    312 {
    313     if (table) {
    314 	ssize_t i = table->size;
    315 	HTABLE_INFO *ht;
    316 	HTABLE_INFO *next;
    317 	HTABLE_INFO **h = table->data;
    318 
    319 	while (i-- > 0) {
    320 	    for (ht = *h++; ht; ht = next) {
    321 		next = ht->next;
    322 		myfree(ht->key);
    323 		if (free_fn && ht->value)
    324 		    (*free_fn) (ht->value);
    325 		myfree((void *) ht);
    326 	    }
    327 	}
    328 	myfree((void *) table->data);
    329 	table->data = 0;
    330 	if (table->seq_bucket)
    331 	    myfree((void *) table->seq_bucket);
    332 	table->seq_bucket = 0;
    333 	myfree((void *) table);
    334     }
    335 }
    336 
    337 /* htable_walk - iterate over hash table */
    338 
    339 void    htable_walk(HTABLE *table, void (*action) (HTABLE_INFO *, void *),
    340 		            void *ptr) {
    341     if (table) {
    342 	ssize_t i = table->size;
    343 	HTABLE_INFO **h = table->data;
    344 	HTABLE_INFO *ht;
    345 
    346 	while (i-- > 0)
    347 	    for (ht = *h++; ht; ht = ht->next)
    348 		(*action) (ht, ptr);
    349     }
    350 }
    351 
    352 /* htable_list - list all table members */
    353 
    354 HTABLE_INFO **htable_list(HTABLE *table)
    355 {
    356     HTABLE_INFO **list;
    357     HTABLE_INFO *member;
    358     ssize_t count = 0;
    359     ssize_t i;
    360 
    361     if (table != 0) {
    362 	list = (HTABLE_INFO **) mymalloc(sizeof(*list) * (table->used + 1));
    363 	for (i = 0; i < table->size; i++)
    364 	    for (member = table->data[i]; member != 0; member = member->next)
    365 		list[count++] = member;
    366     } else {
    367 	list = (HTABLE_INFO **) mymalloc(sizeof(*list));
    368     }
    369     list[count] = 0;
    370     return (list);
    371 }
    372 
    373 /* htable_sequence - dict(3) compatibility iterator */
    374 
    375 HTABLE_INFO *htable_sequence(HTABLE *table, int how)
    376 {
    377     if (table == 0)
    378 	return (0);
    379 
    380     switch (how) {
    381     case HTABLE_SEQ_FIRST:			/* start new sequence */
    382 	if (table->seq_bucket)
    383 	    myfree((void *) table->seq_bucket);
    384 	table->seq_bucket = htable_list(table);
    385 	table->seq_element = table->seq_bucket;
    386 	/* FALLTHROUGH */
    387     case HTABLE_SEQ_NEXT:			/* next element */
    388 	if (table->seq_element && *table->seq_element)
    389 	    return (*(table->seq_element)++);
    390 	/* FALLTHROUGH */
    391     default:					/* terminate sequence */
    392 	if (table->seq_bucket) {
    393 	    myfree((void *) table->seq_bucket);
    394 	    table->seq_bucket = table->seq_element = 0;
    395 	}
    396 	return (0);
    397     }
    398 }
    399 
    400 #ifdef TEST
    401 #include <vstring_vstream.h>
    402 #include <myrand.h>
    403 
    404 int     main(int unused_argc, char **unused_argv)
    405 {
    406     VSTRING *buf = vstring_alloc(10);
    407     ssize_t count = 0;
    408     HTABLE *hash;
    409     HTABLE_INFO **ht_info;
    410     HTABLE_INFO **ht;
    411     HTABLE_INFO *info;
    412     ssize_t i;
    413     ssize_t r;
    414     int     op;
    415 
    416     /*
    417      * Load a large number of strings and delete them in a random order.
    418      */
    419     hash = htable_create(10);
    420     while (vstring_get(buf, VSTREAM_IN) != VSTREAM_EOF)
    421 	htable_enter(hash, vstring_str(buf), CAST_INT_TO_VOID_PTR(count++));
    422     if (count != hash->used)
    423 	msg_panic("%ld entries stored, but %lu entries exist",
    424 		  (long) count, (unsigned long) hash->used);
    425     for (i = 0, op = HTABLE_SEQ_FIRST; htable_sequence(hash, op) != 0;
    426 	 i++, op = HTABLE_SEQ_NEXT)
    427 	 /* void */ ;
    428     if (i != hash->used)
    429 	msg_panic("%ld entries found, but %lu entries exist",
    430 		  (long) i, (unsigned long) hash->used);
    431     ht_info = htable_list(hash);
    432     for (i = 0; i < hash->used; i++) {
    433 	r = myrand() % hash->used;
    434 	info = ht_info[i];
    435 	ht_info[i] = ht_info[r];
    436 	ht_info[r] = info;
    437     }
    438     for (ht = ht_info; *ht; ht++)
    439 	htable_delete(hash, ht[0]->key, (void (*) (void *)) 0);
    440     if (hash->used > 0)
    441 	msg_panic("%ld entries not deleted", (long) hash->used);
    442     myfree((void *) ht_info);
    443     htable_free(hash, (void (*) (void *)) 0);
    444     vstring_free(buf);
    445     return (0);
    446 }
    447 
    448 #endif
    449