xref: /onnv-gate/usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/hash.c (revision 0:68f95e015346)
1 /*
2  * Copyright (c) 1993-2001 by Sun Microsystems, Inc.
3  * All rights reserved.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * Copyright 1988, 1991 by Carnegie Mellon University
10  *
11  * All Rights Reserved
12  *
13  * Permission to use, copy, modify, and distribute this software and its
14  * documentation for any purpose and without fee is hereby granted, provided
15  * that the above copyright notice appear in all copies and that both that
16  * copyright notice and this permission notice appear in supporting
17  * documentation, and that the name of Carnegie Mellon University not be used
18  * in advertising or publicity pertaining to distribution of the software
19  * without specific, written prior permission.
20  *
21  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
22  * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
23  * IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
24  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
25  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
26  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
27  * THIS SOFTWARE.
28  */
29 
30 /*
31  * Generalized hash table ADT
32  *
33  * Provides multiple, dynamically-allocated, variable-sized hash tables on
34  * various data and keys.
35  *
36  * This package attempts to follow some of the coding conventions suggested
37  * by Bob Sidebotham and the AFS Clean Code Committee of the
38  * Information Technology Center at Carnegie Mellon.
39  *
40  * Additions for per bucket locking, and configurable dynamic free of
41  * unused entries.
42  */
43 
44 #include <stdlib.h>
45 #include <string.h>
46 #include <sys/types.h>
47 #include <sys/param.h>
48 #include <sys/sysmacros.h>
49 #include <stdarg.h>
50 #include <stddef.h>
51 #include <assert.h>
52 #include <synch.h>
53 #include "dhcpd.h"
54 #include "hash.h"
55 
56 /*
57  * Hash table size calculation routine.
58  *
59  * Estimate the size of a hash table based on the expected number of
60  * entries, up to a maximum of HASHTABLESIZE.
61  */
62 static unsigned
hashi_Hsize(unsigned hint)63 hashi_Hsize(unsigned hint)
64 {
65 	unsigned f;
66 
67 	if (hint == 0)				/* Default size. */
68 		hint = HASHTABLESIZE;
69 	else if (hint < 16)			/* Minimal size. */
70 		hint = 16;
71 
72 	hint /= 4;
73 	for (f = 2; f * f <= hint; f++) {	/* Find next largest prime. */
74 		if (hint % f == 0) {
75 			f = 1;
76 			hint++;
77 		}
78 	}
79 	return (MIN(HASHTABLESIZE, hint));
80 }
81 
82 /*
83  * Frees an entire linked list of bucket members (used in the
84  * open hashing scheme).  Does nothing if the passed pointer is NULL.
85  *
86  * Returns B_FALSE and members which could not be freed in bucketptr, when
87  * force variable is set to B_FALSE, and free_data routine indicates
88  * free did not occur.
89  */
90 static boolean_t
hashi_FreeMember(hash_member ** bucketptr,boolean_t (* free_data)(),boolean_t force)91 hashi_FreeMember(hash_member **bucketptr, boolean_t (*free_data)(),
92     boolean_t force)
93 {
94 	hash_member *prev, *next, *unfree = NULL;
95 	boolean_t ret = B_TRUE;
96 
97 	if (bucketptr) {
98 		for (prev = *bucketptr; prev; prev = next) {
99 			next = prev->next;
100 			prev->next = NULL;
101 			if (free_data != NULL) {
102 				if ((*free_data)(prev->data, force) ==
103 				    B_FALSE) {
104 					ret = B_FALSE;
105 					prev->next = unfree;
106 					unfree = prev;
107 				} else {
108 					free(prev);
109 				}
110 			} else
111 				free(prev);
112 		}
113 		*bucketptr = unfree;
114 	}
115 	return (ret);
116 }
117 
118 /*
119  * Dynamic free initialization.
120  */
121 static void
hashi_Dinit(hash_tbl * hashtable,hash_member * memberptr)122 hashi_Dinit(hash_tbl *hashtable, hash_member *memberptr)
123 {
124 	(void) mutex_init(&memberptr->h_mtx, USYNC_THREAD, NULL);
125 	memberptr->h_time = time(NULL) + hashtable->dfree_time;
126 	memberptr->h_count = 1;
127 }
128 
129 /*
130  * Dynamic free reference count increment.
131  */
132 static void
hashi_Dhold(hash_member * memberptr)133 hashi_Dhold(hash_member *memberptr)
134 {
135 	(void) mutex_lock(&memberptr->h_mtx);
136 	memberptr->h_count++;
137 	(void) mutex_unlock(&memberptr->h_mtx);
138 }
139 
140 /*
141  * Dynamic free expired data. Return NULL if memberptr is successfully
142  * dynamically freed, otherwise return memberptr.
143  */
144 static hash_member *
hashi_Dfree(hash_member * memberptr,boolean_t (* free_data)())145 hashi_Dfree(hash_member *memberptr, boolean_t (*free_data)())
146 {
147 	hash_member *next;
148 
149 	next = memberptr->next;
150 	memberptr->next = NULL;
151 	if (hashi_FreeMember(&memberptr, free_data, B_FALSE) == B_TRUE)
152 		memberptr = NULL;
153 	else
154 		memberptr->next = next;
155 	return (memberptr);
156 }
157 
158 /*
159  * Hash table initialization routine.
160  *
161  * This routine creates and intializes a hash table of size "tablesize"
162  * entries.  Successful calls return a pointer to the hash table (which must
163  * be passed to other hash routines to identify the hash table).  Failed
164  * calls return NULL.
165  */
166 hash_tbl *
hash_Init(unsigned tablesize,boolean_t (* dfree_data)(),time_t dtime,boolean_t lck)167 hash_Init(unsigned tablesize, boolean_t (*dfree_data)(), time_t dtime,
168 	boolean_t lck)
169 {
170 	hash_tbl *hashtblptr;
171 	unsigned totalsize;
172 	unsigned	i;
173 
174 	tablesize = hashi_Hsize(tablesize);
175 
176 	totalsize = sizeof (hash_tbl) + (sizeof (hash_bucket) *
177 	    (tablesize - 1));
178 
179 	hashtblptr = (hash_tbl *)smalloc(totalsize);
180 
181 	hashtblptr->size = tablesize; /* Success! */
182 	hashtblptr->bucketnum = 0;
183 	hashtblptr->dfree_data = dfree_data;
184 	hashtblptr->dfree_lck = lck;
185 	hashtblptr->dfree_time = dtime;
186 	hashtblptr->table = &hashtblptr->data[0];
187 	for (i = 0; i < tablesize; i++) {
188 		hashtblptr->table[i].table = hashtblptr;
189 		if (lck == B_TRUE) {
190 			(void) rwlock_init(&(hashtblptr->table[i].rwlock),
191 			    USYNC_THREAD, NULL);
192 		}
193 	}
194 
195 	return (hashtblptr);		/* NULL if failure */
196 }
197 
198 /*
199  * Generic hash function to calculate a hash code from the given string.
200  *
201  * For each byte of the string, this function left-shifts the value in an
202  * accumulator and then adds the byte into the accumulator.  The contents of
203  * the accumulator is returned after the entire string has been processed.
204  * It is assumed that this result will be used as the "hashcode" parameter in
205  * calls to other functions in this package.  These functions automatically
206  * adjust the hashcode for the size of each hashtable.
207  *
208  * This algorithm probably works best when the hash table size is a prime
209  * number.
210  *
211  * Hopefully, this function is better than the previous one which returned
212  * the sum of the squares of all the bytes.  I'm still open to other
213  * suggestions for a default hash function.  The programmer is more than
214  * welcome to supply his/her own hash function as that is one of the design
215  * features of this package.
216  */
217 static unsigned
hashi_HashFunction(unsigned char * string,unsigned len)218 hashi_HashFunction(unsigned char *string, unsigned len)
219 {
220 	unsigned accum;
221 
222 	/*
223 	 * Special case: allow hash_Delete() to iterate over buckets.
224 	 */
225 	if (string == NULL)
226 		return (len);
227 
228 	for (accum = 0; len != 0; len--) {
229 		accum <<= 1;
230 		accum += (unsigned)(*string++ & 0xFF);
231 	}
232 	return (accum);
233 }
234 
235 /*
236  * This routine re-initializes the hash table.  It frees all the allocated
237  * memory and resets all bucket pointers to NULL. For the macro hash
238  * table, the table will be reused. Other tables (with bucket locks)
239  * will be destroyed.
240  */
241 void
hash_Reset(hash_tbl * hashtable,boolean_t (* free_data)())242 hash_Reset(hash_tbl *hashtable, boolean_t (*free_data)())
243 {
244 	hash_bucket	*bucketptr;
245 	unsigned	i;
246 
247 	bucketptr = &((hashtable->table)[0]);
248 	for (i = 0; i < hashtable->size; i++) {
249 		if (hashtable->dfree_lck == B_TRUE)
250 			(void) rw_wrlock(&bucketptr->rwlock);
251 		/*
252 		 * Unequivocally free member, using the force parameter.
253 		 */
254 		(void) hashi_FreeMember(&bucketptr->next, free_data, B_TRUE);
255 		bucketptr->next = NULL;
256 		if (hashtable->dfree_lck == B_TRUE) {
257 			(void) rw_unlock(&bucketptr->rwlock);
258 			(void) rwlock_destroy(&(bucketptr->rwlock));
259 		}
260 		bucketptr++;
261 	}
262 	hashtable->bucketnum = 0;
263 }
264 
265 /*
266  * Returns B_TRUE if at least one entry for the given key exists; B_FALSE
267  * otherwise. Dynamically free expired data as searched.
268  */
269 static int
hashi_Exists(hash_bucket * bucketptr,int (* compare)(),hash_datum * key,boolean_t (* free_data)(),hash_member ** prev)270 hashi_Exists(hash_bucket *bucketptr, int (*compare)(), hash_datum *key,
271     boolean_t (*free_data)(), hash_member **prev)
272 {
273 	hash_member *prevptr = (hash_member *)bucketptr;
274 	hash_member *memberptr = bucketptr->next;
275 	hash_tbl *hashtable = bucketptr->table;
276 	hash_member *next;
277 	boolean_t ret = B_FALSE;
278 	time_t now = time(NULL);
279 
280 	while (memberptr != NULL) {
281 		/*
282 		 * Dynamically free expired data.
283 		 */
284 		if (free_data != NULL && hashtable->dfree_data != NULL &&
285 		    memberptr->h_time < now) {
286 			next = memberptr->next;
287 			if ((memberptr = hashi_Dfree(memberptr, free_data)) ==
288 			    NULL) {
289 				prevptr->next = memberptr = next;
290 				continue;
291 			}
292 		}
293 
294 		/*
295 		 * Entry exists, or we are randomly selecting any
296 		 * element (compare function is NULL).
297 		 */
298 		if (compare == NULL || (*compare)(key, memberptr->data)) {
299 			ret = B_TRUE;
300 			break;
301 		} else
302 			prevptr = memberptr;
303 		memberptr = memberptr->next;
304 	}
305 
306 	if (prev != NULL)
307 		*prev = prevptr;
308 	return (ret);
309 }
310 
311 /*
312  * Returns number of Dynamically freed expired entries.
313  */
314 static int
hashi_Expire(hash_bucket * bucketptr,boolean_t (* free_data)())315 hashi_Expire(hash_bucket *bucketptr, boolean_t (*free_data)())
316 {
317 	hash_member *prevptr = (hash_member *)bucketptr;
318 	hash_member *memberptr = bucketptr->next;
319 	hash_tbl *hashtable = bucketptr->table;
320 	hash_member *next;
321 	int rcount = 0;
322 	time_t now = time(NULL);
323 
324 	while (memberptr) {
325 		/*
326 		 * Dynamically free expired data.
327 		 */
328 		if (free_data != NULL && hashtable->dfree_data != NULL &&
329 		    memberptr->h_time < now) {
330 			next = memberptr->next;
331 			if ((memberptr = hashi_Dfree(memberptr, free_data)) ==
332 			    NULL) {
333 				rcount++;
334 				prevptr->next = memberptr = next;
335 				continue;
336 			}
337 		}
338 		prevptr = memberptr;
339 		memberptr = memberptr->next;
340 	}
341 	return (rcount);
342 }
343 
344 /*
345  * Insert the data item "element" into the hash table using "hashcode"
346  * to determine the bucket number, and "compare" and "key" to determine
347  * its uniqueness.
348  *
349  * If the insertion is successful the element is returned.  If a matching entry
350  * already exists in the given bucket of the hash table, then NULL is returned,
351  * signifying that the entry is already in the table. This happens when some
352  * other thread has already inserted the entry.
353  */
354 void *
hash_Insert(hash_tbl * hashtable,void * hashdata,unsigned hashlen,int (* compare)(),hash_datum * key,hash_datum * element)355 hash_Insert(hash_tbl *hashtable, void *hashdata, unsigned hashlen,
356     int (*compare)(), hash_datum *key, hash_datum *element)
357 {
358 	hash_member *temp = NULL;
359 	hash_bucket *bucketptr;
360 	hash_member *prev = NULL;
361 	unsigned hashcode = hashi_HashFunction(hashdata, hashlen);
362 
363 	bucketptr = &((hashtable->table)[hashcode % hashtable->size]);
364 	if (hashtable->dfree_lck)
365 		(void) rw_wrlock(&bucketptr->rwlock);
366 
367 	if (hashi_Exists(bucketptr, compare, key, hashtable->dfree_data,
368 	    &prev)) {
369 		/* Some other thread got there first, so just return */
370 		if (hashtable->dfree_lck)
371 			(void) rw_unlock(&bucketptr->rwlock);
372 		return (NULL);
373 	}
374 
375 	temp = (hash_member *)smalloc(sizeof (hash_member));
376 
377 	prev->next = temp;
378 	temp->data = element;
379 	temp->next = NULL;
380 
381 	/*
382 	 * Dynamic free initialization.
383 	 */
384 	if (hashtable->dfree_data != NULL)
385 		hashi_Dinit(hashtable, temp);
386 
387 	if (hashtable->dfree_lck)
388 		(void) rw_unlock(&bucketptr->rwlock);
389 
390 	return ((void *)temp);
391 }
392 
393 /*
394  * Release the reference count on an item. Performance: if item is to be
395  * deleted, mark for future dynamic free.
396  */
397 void
hash_Rele(void * hashp,boolean_t delete)398 hash_Rele(void *hashp, boolean_t delete)
399 {
400 	hash_member *memberptr = (hash_member *)hashp;
401 
402 	(void) mutex_lock(&memberptr->h_mtx);
403 	memberptr->h_count--;
404 	assert(memberptr->h_count >= 0);
405 	if (delete == B_TRUE)
406 		memberptr->h_time = 0;
407 	(void) mutex_unlock(&memberptr->h_mtx);
408 }
409 
410 /*
411  * Report the reference count on an item.
412  */
413 int
hash_Refcount(void * hashp)414 hash_Refcount(void *hashp)
415 {
416 	hash_member *memberptr = (hash_member *)hashp;
417 	int ret;
418 
419 	(void) mutex_lock(&memberptr->h_mtx);
420 	ret = memberptr->h_count;
421 	(void) mutex_unlock(&memberptr->h_mtx);
422 	return (ret);
423 }
424 
425 /*
426  * Report the dynamic free time on an item.
427  */
428 int
hash_Htime(void * hashp)429 hash_Htime(void *hashp)
430 {
431 	hash_member *memberptr = (hash_member *)hashp;
432 	int ret;
433 
434 	(void) mutex_lock(&memberptr->h_mtx);
435 	ret = memberptr->h_time;
436 	(void) mutex_unlock(&memberptr->h_mtx);
437 	return (ret);
438 }
439 
440 /*
441  * Increase the dynamic free time on an item.
442  */
443 void
hash_Age(void * hashp)444 hash_Age(void *hashp)
445 {
446 	hash_member *memberptr = (hash_member *)hashp;
447 
448 	(void) mutex_lock(&memberptr->h_mtx);
449 	memberptr->h_time++;
450 	(void) mutex_unlock(&memberptr->h_mtx);
451 }
452 
453 /*
454  *  Set the dynamic free time on an item.
455  */
456 void
hash_Dtime(void * hashp,time_t tm)457 hash_Dtime(void *hashp, time_t tm)
458 {
459 	hash_member *memberptr = (hash_member *)hashp;
460 
461 	(void) mutex_lock(&memberptr->h_mtx);
462 	memberptr->h_time = tm;
463 	(void) mutex_unlock(&memberptr->h_mtx);
464 }
465 
466 /*
467  * Delete a data item from the hash table using "hashcode"
468  * to determine the bucket number, and "compare" and "key" to determine
469  * its uniqueness.
470  *
471  * If the deletion is successful 0 is returned.  If a matching entry
472  * does not exist in the given bucket of the hash table, or some other error
473  * occurs, -1 is returned and the insertion is not done.
474  */
475 boolean_t
hash_Delete(hash_tbl * hashtable,void * hashdata,unsigned hashlen,int (* compare)(),hash_datum * key,boolean_t (* free_data)())476 hash_Delete(hash_tbl *hashtable, void *hashdata, unsigned hashlen,
477     int (*compare)(), hash_datum *key, boolean_t (*free_data)())
478 {
479 	hash_member *prev = NULL;
480 	hash_member *temp;
481 	hash_bucket *bucketptr;
482 	unsigned hashcode = hashi_HashFunction(hashdata, hashlen);
483 
484 	bucketptr = &((hashtable->table)[hashcode % hashtable->size]);
485 	if (hashtable->dfree_lck == B_TRUE)
486 		(void) rw_wrlock(&bucketptr->rwlock);
487 
488 	if (hashi_Exists(bucketptr, compare, key, free_data, &prev) ==
489 	    B_FALSE || prev == NULL) {
490 		if (hashtable->dfree_lck == B_TRUE)
491 			(void) rw_unlock(&bucketptr->rwlock);
492 		return (B_FALSE); /* Entry does not exist */
493 	}
494 
495 	temp = prev->next;
496 	if (temp) {
497 		prev->next = temp->next;
498 		temp->next = NULL;
499 		(void) hashi_FreeMember(&temp, free_data, B_TRUE);
500 	} else
501 		prev->next = NULL;
502 	if (hashtable->dfree_lck == B_TRUE)
503 		(void) rw_unlock(&bucketptr->rwlock);
504 	return (B_TRUE);
505 }
506 
507 /*
508  * Locate and return the data entry associated with the given key.
509  *
510  * If the data entry is found, a pointer to it is returned.  Otherwise,
511  * NULL is returned.
512  */
513 hash_datum *
hash_Lookup(hash_tbl * hashtable,void * hashdata,unsigned hashlen,int (* compare)(),hash_datum * key,boolean_t hold)514 hash_Lookup(hash_tbl *hashtable, void *hashdata, unsigned hashlen,
515     int (*compare)(), hash_datum *key, boolean_t hold)
516 {
517 	hash_datum *ret = NULL;
518 	hash_bucket *bucketptr;
519 	hash_member *prev = NULL;
520 	unsigned hashcode = hashi_HashFunction(hashdata, hashlen);
521 
522 	bucketptr = &((hashtable->table)[hashcode % hashtable->size]);
523 	if (hashtable->dfree_lck == B_TRUE)
524 		(void) rw_wrlock(&bucketptr->rwlock);
525 
526 	if (hashi_Exists(bucketptr, compare, key, hashtable->dfree_data,
527 	    &prev) == B_TRUE) {
528 		/*
529 		 * Dynamic free increment reference.
530 		 */
531 		if (hold)
532 			hashi_Dhold(prev->next);
533 		ret = prev->next->data;
534 
535 	}
536 	if (hashtable->dfree_lck == B_TRUE)
537 		(void) rw_unlock(&bucketptr->rwlock);
538 	return (ret);
539 }
540 
541 /*
542  * Reap expired data items, or a random data item from the hash table.
543  */
544 void
hash_Reap(hash_tbl * hashtable,boolean_t (* free_data)())545 hash_Reap(hash_tbl *hashtable, boolean_t (*free_data)())
546 {
547 	hash_bucket		*bucketptr;
548 	int			rcount;
549 	unsigned		i;
550 
551 	bucketptr = &((hashtable->table)[0]);
552 	rcount = 0;
553 
554 	/*
555 	 * Walk the buckets, reaping expired clients.
556 	 */
557 	for (i = 0; i < hashtable->size; i++) {
558 		if (hashtable->dfree_lck == B_TRUE)
559 			(void) rw_wrlock(&bucketptr->rwlock);
560 		rcount += hashi_Expire(bucketptr, hashtable->dfree_data);
561 		if (hashtable->dfree_lck == B_TRUE)
562 			(void) rw_unlock(&bucketptr->rwlock);
563 		bucketptr++;
564 	}
565 
566 	/*
567 	 * Nothing to be reaped, delete a random element. Note that
568 	 * the unhash_data routine will wait for current references
569 	 * before deletion.
570 	 */
571 	if (rcount == 0) {
572 		for (i = 0; i < hashtable->size; i++) {
573 			if (hash_Delete(hashtable, NULL, i, NULL, NULL,
574 			    free_data) == B_TRUE) {
575 				break;
576 			}
577 		}
578 	}
579 }
580