Home | History | Annotate | Line # | Download | only in lib
      1 /*	$NetBSD: ohash.c,v 1.2 2026/06/11 14:11:52 christos Exp $	*/
      2 /* $OpenBSD: ohash.c,v 1.1 2014/06/02 18:52:03 deraadt Exp $ */
      3 
      4 /* Copyright (c) 1999, 2004 Marc Espie <espie (at) openbsd.org>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  */
     18 
     19 #if HAVE_NBTOOL_CONFIG_H
     20 #include "nbtool_config.h"
     21 #endif
     22 #include <sys/cdefs.h>
     23 __RCSID("$NetBSD: ohash.c,v 1.2 2026/06/11 14:11:52 christos Exp $");
     24 
     25 #include <stddef.h>
     26 #include <stdint.h>
     27 #include <stdlib.h>
     28 #include <string.h>
     29 #include <limits.h>
     30 #include "ohash.h"
     31 
     32 struct _ohash_record {
     33 	uint32_t	hv;
     34 	const char	*p;
     35 };
     36 
     37 #define DELETED		((const char *)h)
     38 #define NONE		(h->size)
     39 
     40 /* Don't bother changing the hash table if the change is small enough.  */
     41 #define MINSIZE		(1UL << 4)
     42 #define MINDELETED	4
     43 
     44 static void ohash_resize(struct ohash *);
     45 
     46 
     47 /* This handles the common case of variable length keys, where the
     48  * key is stored at the end of the record.
     49  */
     50 void *
     51 ohash_create_entry(struct ohash_info *i, const char *start, const char **end)
     52 {
     53 	char *p;
     54 
     55 	if (!*end)
     56 		*end = start + strlen(start);
     57 	p = (i->alloc)(i->key_offset + (*end - start) + 1, i->data);
     58 	if (p) {
     59 		memcpy(p+i->key_offset, start, *end-start);
     60 		p[i->key_offset + (*end - start)] = '\0';
     61 	}
     62 	return (void *)p;
     63 }
     64 
     65 /* hash_delete only frees the hash structure. Use hash_first/hash_next
     66  * to free entries as well.  */
     67 void
     68 ohash_delete(struct ohash *h)
     69 {
     70 	(h->info.free)(h->t, h->info.data);
     71 #ifndef NDEBUG
     72 	h->t = NULL;
     73 #endif
     74 }
     75 
     76 static void
     77 ohash_resize(struct ohash *h)
     78 {
     79 	struct _ohash_record *n;
     80 	size_t ns;
     81 	unsigned int	j;
     82 	unsigned int	i, incr;
     83 
     84 	if (4 * h->deleted < h->total) {
     85 		if (h->size >= (UINT_MAX >> 1U))
     86 			ns = UINT_MAX;
     87 		else
     88 			ns = h->size << 1U;
     89 	} else if (3 * h->deleted > 2 * h->total)
     90 		ns = h->size >> 1U;
     91 	else
     92 		ns = h->size;
     93 	if (ns < MINSIZE)
     94 		ns = MINSIZE;
     95 #ifdef STATS_HASH
     96 	STAT_HASH_EXPAND++;
     97 	STAT_HASH_SIZE += ns - h->size;
     98 #endif
     99 
    100 	n = (h->info.calloc)(ns, sizeof(struct _ohash_record), h->info.data);
    101 	if (!n)
    102 		return;
    103 
    104 	for (j = 0; j < h->size; j++) {
    105 		if (h->t[j].p != NULL && h->t[j].p != DELETED) {
    106 			i = (unsigned int)(h->t[j].hv % ns);
    107 			incr = ((h->t[j].hv % (ns - 2)) & ~1) + 1;
    108 			while (n[i].p != NULL) {
    109 				i += incr;
    110 				if (i >= ns)
    111 					i -= (unsigned int)ns;
    112 			}
    113 			n[i].hv = h->t[j].hv;
    114 			n[i].p = h->t[j].p;
    115 		}
    116 	}
    117 	(h->info.free)(h->t, h->info.data);
    118 	h->t = n;
    119 	h->size = (unsigned int)ns;
    120 	h->total -= h->deleted;
    121 	h->deleted = 0;
    122 }
    123 
    124 void *
    125 ohash_remove(struct ohash *h, unsigned int i)
    126 {
    127 	void		*result = __UNCONST(h->t[i].p);
    128 
    129 	if (result == NULL || result == DELETED)
    130 		return NULL;
    131 
    132 #ifdef STATS_HASH
    133 	STAT_HASH_ENTRIES--;
    134 #endif
    135 	h->t[i].p = DELETED;
    136 	h->deleted++;
    137 	if (h->deleted >= MINDELETED && 4 * h->deleted > h->total)
    138 		ohash_resize(h);
    139 	return result;
    140 }
    141 
    142 void *
    143 ohash_find(struct ohash *h, unsigned int i)
    144 {
    145 	if (h->t[i].p == DELETED)
    146 		return NULL;
    147 	else
    148 		return __UNCONST(h->t[i].p);
    149 }
    150 
    151 void *
    152 ohash_insert(struct ohash *h, unsigned int i, void *p)
    153 {
    154 #ifdef STATS_HASH
    155 	STAT_HASH_ENTRIES++;
    156 #endif
    157 	if (h->t[i].p == DELETED) {
    158 		h->deleted--;
    159 		h->t[i].p = p;
    160 	} else {
    161 		h->t[i].p = p;
    162 		/* Arbitrary resize boundary.  Tweak if not efficient enough.  */
    163 		if (++h->total * 4 > h->size * 3)
    164 			ohash_resize(h);
    165 	}
    166 	return p;
    167 }
    168 
    169 unsigned int
    170 ohash_entries(struct ohash *h)
    171 {
    172 	return h->total - h->deleted;
    173 }
    174 
    175 void *
    176 ohash_first(struct ohash *h, unsigned int *pos)
    177 {
    178 	*pos = 0;
    179 	return ohash_next(h, pos);
    180 }
    181 
    182 void *
    183 ohash_next(struct ohash *h, unsigned int *pos)
    184 {
    185 	for (; *pos < h->size; (*pos)++)
    186 		if (h->t[*pos].p != DELETED && h->t[*pos].p != NULL)
    187 			return __UNCONST(h->t[(*pos)++].p);
    188 	return NULL;
    189 }
    190 
    191 void
    192 ohash_init(struct ohash *h, unsigned int size, struct ohash_info *info)
    193 {
    194 	h->size = (unsigned int)(1UL << size);
    195 	if (h->size < MINSIZE)
    196 		h->size = MINSIZE;
    197 #ifdef STATS_HASH
    198 	STAT_HASH_CREATION++;
    199 	STAT_HASH_SIZE += h->size;
    200 #endif
    201 	/* Copy info so that caller may free it.  */
    202 	h->info.key_offset = info->key_offset;
    203 	h->info.calloc = info->calloc;
    204 	h->info.free = info->free;
    205 	h->info.alloc = info->alloc;
    206 	h->info.data = info->data;
    207 	h->t = (h->info.calloc)(h->size, sizeof(struct _ohash_record),
    208 		    h->info.data);
    209 	h->total = h->deleted = 0;
    210 }
    211 
    212 uint32_t
    213 ohash_interval(const char *s, const char **e)
    214 {
    215 	uint32_t k;
    216 
    217 	if (!*e)
    218 		*e = s + strlen(s);
    219 	if (s == *e)
    220 		k = 0;
    221 	else
    222 		k = *s++;
    223 	while (s != *e)
    224 		k =  ((k << 2) | (k >> 30)) ^ *s++;
    225 	return k;
    226 }
    227 
    228 unsigned int
    229 ohash_lookup_interval(struct ohash *h, const char *start, const char *end,
    230     uint32_t hv)
    231 {
    232 	unsigned int	i, incr;
    233 	unsigned int	empty;
    234 
    235 #ifdef STATS_HASH
    236 	STAT_HASH_LOOKUP++;
    237 #endif
    238 	empty = NONE;
    239 	i = hv % h->size;
    240 	incr = ((hv % (h->size-2)) & ~1) + 1;
    241 	while (h->t[i].p != NULL) {
    242 #ifdef STATS_HASH
    243 		STAT_HASH_LENGTH++;
    244 #endif
    245 		if (h->t[i].p == DELETED) {
    246 			if (empty == NONE)
    247 				empty = i;
    248 		} else if (h->t[i].hv == hv &&
    249 		    strncmp(h->t[i].p+h->info.key_offset, start,
    250 			end - start) == 0 &&
    251 		    (h->t[i].p+h->info.key_offset)[end-start] == '\0') {
    252 			if (empty != NONE) {
    253 				h->t[empty].hv = hv;
    254 				h->t[empty].p = h->t[i].p;
    255 				h->t[i].p = DELETED;
    256 				return empty;
    257 			} else {
    258 #ifdef STATS_HASH
    259 				STAT_HASH_POSITIVE++;
    260 #endif
    261 				return i;
    262 			}
    263 		}
    264 		i += incr;
    265 		if (i >= h->size)
    266 			i -= h->size;
    267 	}
    268 
    269 	/* Found an empty position.  */
    270 	if (empty != NONE)
    271 		i = empty;
    272 	h->t[i].hv = hv;
    273 	return i;
    274 }
    275 
    276 unsigned int
    277 ohash_lookup_memory(struct ohash *h, const char *k, size_t size, uint32_t hv)
    278 {
    279 	unsigned int	i, incr;
    280 	unsigned int	empty;
    281 
    282 #ifdef STATS_HASH
    283 	STAT_HASH_LOOKUP++;
    284 #endif
    285 	empty = NONE;
    286 	i = hv % h->size;
    287 	incr = ((hv % (h->size-2)) & ~1) + 1;
    288 	while (h->t[i].p != NULL) {
    289 #ifdef STATS_HASH
    290 		STAT_HASH_LENGTH++;
    291 #endif
    292 		if (h->t[i].p == DELETED) {
    293 			if (empty == NONE)
    294 				empty = i;
    295 		} else if (h->t[i].hv == hv &&
    296 		    memcmp(h->t[i].p+h->info.key_offset, k, size) == 0) {
    297 			if (empty != NONE) {
    298 				h->t[empty].hv = hv;
    299 				h->t[empty].p = h->t[i].p;
    300 				h->t[i].p = DELETED;
    301 				return empty;
    302 			} else {
    303 #ifdef STATS_HASH
    304 				STAT_HASH_POSITIVE++;
    305 #endif
    306 			}	return i;
    307 		}
    308 		i += incr;
    309 		if (i >= h->size)
    310 			i -= h->size;
    311 	}
    312 
    313 	/* Found an empty position.  */
    314 	if (empty != NONE)
    315 		i = empty;
    316 	h->t[i].hv = hv;
    317 	return i;
    318 }
    319 
    320 unsigned int
    321 ohash_qlookup(struct ohash *h, const char *s)
    322 {
    323 	const char *e = NULL;
    324 	return ohash_qlookupi(h, s, &e);
    325 }
    326 
    327 unsigned int
    328 ohash_qlookupi(struct ohash *h, const char *s, const char **e)
    329 {
    330 	uint32_t hv;
    331 
    332 	hv = ohash_interval(s, e);
    333 	return ohash_lookup_interval(h, s, *e, hv);
    334 }
    335