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