xref: /illumos-gate/usr/src/lib/smbsrv/libsmb/common/smb_ht.c (revision 4c87aefe8930bd07275b8dd2e96ea5f24d93a52e)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Generic hash table library. The hash table is an array of pointers
30  * to items. Hash collisions are handled using linked lists from the
31  * table entries. A handle is associated with each table, which is used
32  * to maintain the hash table.
33  *
34  * +------+     +-------+    +----+    +----+
35  * |handle|---> |index 0|--->|item|--->|item|--->
36  * | ...  |     +-------+    +----+    +----+
37  * | ...  |     |index 1|--->
38  * +------+     +-------+    +----+    +----+    +----+
39  *              |index 2|--->|item|--->|item|--->|item|--->
40  *              +-------+    +----+    +----+    +----+
41  *              | ...   |--->
42  *              +-------+
43  *              | ...   |--->
44  *              +-------+
45  *              |index n|--->
46  *              +-------+
47  *
48  */
49 
50 #include <stdlib.h>
51 #include <strings.h>
52 #include <smbsrv/hash_table.h>
53 
54 static size_t ht_default_hash(HT_HANDLE *handle, const char *key);
55 
56 /*
57  * ht_is_power2
58  *
59  * Inline function to determine if a value is a power of two. This
60  * function is used by the library to validate the table size when
61  * a new table is created.
62  *
63  * Returns 1 if value given is power of two, otherwise returns 0.
64  */
65 static size_t
66 ht_is_power2(size_t value)
67 {
68 	return (((value & (value - 1)) == 0)? 1 : 0);
69 }
70 
71 
72 /*
73  * ht_create_table
74  *
75  * Create a hash table. The table size must be a positive integer and
76  * must be a power of two. The key size must be a positive integer.
77  * For null terminated keys, the key size does not need to include the
78  * null terminating character. The type of key is indicated by the
79  * flags (see hash_table.h).
80  *
81  * The handle and the table are are malloc'd using a single call, to
82  * avoid two allocations. The table is located immediately after the
83  * handle.
84  *
85  * On success a pointer to an opaque handle is returned. Otherwise a
86  * null pointer is returned.
87  */
88 HT_HANDLE *
89 ht_create_table(size_t table_size, size_t key_size, size_t flags)
90 {
91 	HT_HANDLE *ht;
92 	size_t msize;
93 	size_t i;
94 
95 	if ((table_size == 0) || (key_size == 0))
96 		return (NULL);
97 
98 	if (ht_is_power2(table_size) == 0)
99 		return (NULL);
100 
101 	msize = sizeof (HT_HANDLE) + (sizeof (HT_TABLE_ENTRY) * table_size);
102 
103 	if ((ht = (HT_HANDLE *)malloc(msize)) == 0)
104 		return (NULL);
105 
106 	/*LINTED E_BAD_PTR_CAST_ALIGN*/
107 	ht->ht_table = (HT_TABLE_ENTRY *)((char *)ht + sizeof (HT_HANDLE));
108 	ht->ht_table_size = table_size;
109 	ht->ht_table_mask = table_size - 1;
110 	ht->ht_key_size = key_size;
111 	ht->ht_total_items = 0;
112 	ht->ht_flags = flags;
113 	ht->ht_hash = ht_default_hash;
114 	ht->ht_callback = 0;
115 	ht->ht_sequence = random();
116 	ht->ht_cmp = ((flags & HTHF_FIXED_KEY) == 0)
117 	    ? (HT_CMP)strncmp : (HT_CMP)memcmp;
118 
119 	for (i = 0; i < table_size; i++)
120 		bzero(&ht->ht_table[i], sizeof (HT_TABLE_ENTRY));
121 
122 	return (ht);
123 }
124 
125 
126 /*
127  * ht_destroy_table
128  *
129  * Destroy a hash table. All entries in the table are removed, which
130  * may invoke the callback if it's installed, and the memory is freed.
131  */
132 void
133 ht_destroy_table(HT_HANDLE *handle)
134 {
135 	HT_ITEM *item;
136 	HT_ITERATOR iterator;
137 
138 	if (handle == 0)
139 		return;
140 
141 	/* To remove marked entries */
142 	(void) ht_clean_table(handle);
143 	while ((item = ht_findfirst(handle, &iterator)) != 0)
144 		(void) ht_remove_item(handle, item->hi_key);
145 
146 	free(handle);
147 }
148 
149 
150 /*
151  * ht_get_total_items
152  *
153  * Return the total number of items in the table. Returns -1 if the
154  * handle is invalid.
155  */
156 size_t
157 ht_get_total_items(HT_HANDLE *handle)
158 {
159 	if (handle == 0)
160 		return ((size_t)-1);
161 
162 	return (handle->ht_total_items);
163 }
164 
165 
166 /*
167  * ht_default_hash
168  *
169  * Default hash function to compute the table index (hash value) based
170  * on the specified key. This will identify the location for the
171  * corresponding item in the hash table. The handle and key pointers
172  * should be validated before this function is called.
173  *
174  * Returns the table index location for the item.
175  */
176 static size_t
177 ht_default_hash(HT_HANDLE *handle, const char *key)
178 {
179 	unsigned int hash_ndx = 0;
180 	size_t rval;
181 
182 	if ((handle->ht_flags & HTHF_FIXED_KEY) == 0) {
183 		while (*key) {
184 			hash_ndx += *key;
185 			++key;
186 		}
187 	} else {
188 		int key_len = handle->ht_key_size;
189 
190 		while (key_len--) {
191 			hash_ndx += *key;
192 			++key;
193 		}
194 	}
195 
196 	rval = (hash_ndx * HASH_MESH_VALUE) & handle->ht_table_mask;
197 	return (rval);
198 }
199 
200 
201 /*
202  * ht_set_cmpfn
203  *
204  * Replace the current compare function. As the this is function
205  * for comparing items' key, it should not be called while there are
206  * items in the table.
207  */
208 void
209 ht_set_cmpfn(HT_HANDLE *handle, HT_CMP cmpfn)
210 {
211 	if (handle)
212 		handle->ht_cmp = cmpfn;
213 }
214 
215 /*
216  * ht_add_item
217  *
218  * Adds an item to a hash table. The hash table is identified by the
219  * handle and the key is used to generate a hashed index. The data
220  * item can be null; it is never dereferenced. We don't check for
221  * duplicates. If duplicate keys are added to the table, the last
222  * item added will be to the front of the duplicate list.
223  *
224  * The table sequence number may be modified here.
225  *
226  * If the item is successfully inserted, a pointer to the item object
227  * is returned. Otherwise a null pointer is returned.
228  */
229 HT_ITEM *
230 ht_add_item(HT_HANDLE *handle, const char *key, const void *data)
231 {
232 	size_t h_index, key_len;
233 	size_t msize;
234 	HT_ITEM *item;
235 
236 	if (handle == 0 || key == 0)
237 		return (NULL);
238 
239 	if (handle->ht_flags & HTHF_FIXED_KEY) {
240 		key_len = handle->ht_key_size;
241 	} else {
242 		key_len = strlen(key);
243 
244 		if (key_len > handle->ht_key_size)
245 			return (NULL);
246 
247 		/* Include the null terminator */
248 		++key_len;
249 	}
250 
251 	msize = key_len + sizeof (HT_ITEM);
252 
253 	if ((item = malloc(msize)) == 0)
254 		return (NULL);
255 
256 	item->hi_key = (char *)item + sizeof (HT_ITEM);
257 	(void) memcpy(item->hi_key, key, key_len);
258 	item->hi_data = (void *)data;
259 	item->hi_flags = 0;
260 
261 	h_index = handle->ht_hash(handle, key);
262 
263 	/*
264 	 * Add to the front of the list.
265 	 */
266 	item->hi_next = handle->ht_table[h_index].he_head;
267 	handle->ht_table[h_index].he_head = item;
268 
269 	handle->ht_table[h_index].he_count++;
270 	handle->ht_total_items++;
271 	handle->ht_sequence++;
272 
273 	return (item);
274 }
275 
276 
277 /*
278  * ht_replace_item
279  *
280  * Replace an item in a hash table. The item associated with key is removed
281  * using ht_remove_item and a new item is added using ht_add_item. We rely
282  * on parameter validation in ht_remove_item and ht_add_item.
283  *
284  * The table sequence number may be modified here.
285  */
286 HT_ITEM *
287 ht_replace_item(HT_HANDLE *handle, const char *key, const void *data)
288 {
289 	(void) ht_remove_item(handle, key);
290 
291 	return (ht_add_item(handle, key, data));
292 }
293 
294 
295 /*
296  * ht_remove_item
297  *
298  * Remove an item from a hash table. If there are duplicate keys, then the
299  * first key found will be deleted. Note that the data pointer is never
300  * dereferenced.  If a callback is installed, it will be invoked and the
301  * return value will be null. Otherwise, the data pointer supplied by the
302  * application will be returned. If there is an error, a null pointer will
303  * be returned.
304  *
305  * The table sequence number may be modified here.
306  */
307 void *
308 ht_remove_item(HT_HANDLE *handle, const char *key)
309 {
310 	size_t h_index;
311 	HT_ITEM *cur, *prev;
312 	int key_len;
313 	void *data = 0;
314 
315 	if (handle == 0 || key == 0)
316 		return (NULL);
317 
318 	if ((handle->ht_flags & HTHF_FIXED_KEY) == 0)
319 		key_len = strlen(key) + 1;
320 	else
321 		key_len = handle->ht_key_size;
322 
323 	h_index = handle->ht_hash(handle, key);
324 
325 	cur = handle->ht_table[h_index].he_head;
326 	prev = 0;
327 
328 	while (cur) {
329 		if (!(cur->hi_flags & HTIF_MARKED_DELETED) &&
330 		    (handle->ht_cmp(cur->hi_key, key, key_len) == 0)) {
331 			/* found key */
332 			if (prev == 0)
333 				handle->ht_table[h_index].he_head =
334 				    cur->hi_next;
335 			else
336 				prev->hi_next = cur->hi_next;
337 
338 			if (handle->ht_callback)
339 				handle->ht_callback(cur);
340 			else
341 				data = cur->hi_data;
342 
343 			/*
344 			 * Since the key and the item were allocated as
345 			 * a single chunk, we only need one free here.
346 			 */
347 			free(cur);
348 
349 			handle->ht_table[h_index].he_count--;
350 			handle->ht_total_items--;
351 			handle->ht_sequence++;
352 			break;
353 		}
354 
355 		prev = cur;
356 		cur = cur->hi_next;
357 	}
358 
359 	return (data);
360 }
361 
362 /*
363  * ht_find_item
364  *
365  * Find an item in a hash table. If there are duplicate keys then the
366  * first item found (which will be the last one added) will be returned.
367  *
368  * Returns a pointer to an item. Otherwise returns a null pointer to
369  * indicate an error or that the key didn't match anything in the table.
370  */
371 HT_ITEM *
372 ht_find_item(HT_HANDLE *handle, const char *key)
373 {
374 	size_t h_index;
375 	HT_ITEM *cur;
376 	int key_len;
377 
378 	if (handle == 0 || key == 0)
379 		return (NULL);
380 
381 	if ((handle->ht_flags & HTHF_FIXED_KEY) == 0)
382 		key_len = strlen(key) + 1;
383 	else
384 		key_len = handle->ht_key_size;
385 
386 	h_index = handle->ht_hash(handle, key);
387 	cur = handle->ht_table[h_index].he_head;
388 
389 	while (cur) {
390 		if (!(cur->hi_flags & HTIF_MARKED_DELETED) &&
391 		    (handle->ht_cmp(cur->hi_key, key, key_len) == 0))
392 			return (cur);
393 
394 		cur = cur->hi_next;
395 	}
396 
397 	return (NULL);
398 }
399 
400 
401 /*
402  * ht_register_callback
403  *
404  * Register an application callback function that can be used to process
405  * an item when it is removed from the table, i.e. free any memory
406  * allocated for that data item.
407  *
408  * The previous callback function pointer, which may be null, before
409  * registering the new one. This provides the caller with the option to
410  * restore a previous callback as required.
411  */
412 HT_CALLBACK
413 ht_register_callback(HT_HANDLE *handle, HT_CALLBACK callback)
414 {
415 	HT_CALLBACK old_callback;
416 
417 	if (handle == 0)
418 		return (NULL);
419 
420 	old_callback = handle->ht_callback;
421 	handle->ht_callback = callback;
422 
423 	return (old_callback);
424 }
425 
426 
427 /*
428  * ht_clean_table
429  *
430  * This function removes all the items that are marked for deletion. Note
431  * that this will invoke the callback, if one has been installed. If this
432  * call is used, the callback mechanism is the only way for an application
433  * to free the item data if it was dynamically allocated.
434  *
435  * The table sequence number may be modified here.
436  *
437  * Returns 0 if the handle is valid; otherwise returns -1.
438  */
439 size_t
440 ht_clean_table(HT_HANDLE *handle)
441 {
442 	size_t i;
443 	HT_ITEM *cur, *prev;
444 
445 	if (handle == 0)
446 		return ((size_t)-1);
447 
448 	for (i = 0; i < handle->ht_table_size; i++) {
449 		cur = handle->ht_table[i].he_head;
450 		prev = 0;
451 
452 		while (cur) {
453 			if (cur->hi_flags & HTIF_MARKED_DELETED) {
454 				/*
455 				 * We have a marked item: remove it.
456 				 */
457 				if (prev == 0)
458 					handle->ht_table[i].he_head =
459 					    cur->hi_next;
460 				else
461 					prev->hi_next = cur->hi_next;
462 
463 				if (handle->ht_callback)
464 					handle->ht_callback(cur);
465 
466 				/*
467 				 * Since the key and the item were allocated as
468 				 * a single chunk, we only need one free here.
469 				 */
470 				free(cur);
471 
472 				handle->ht_table[i].he_count--;
473 				handle->ht_sequence++;
474 
475 				if (prev == 0)
476 					cur = handle->ht_table[i].he_head;
477 				else
478 					cur = prev->hi_next;
479 				continue;
480 			}
481 
482 			prev = cur;
483 			cur = cur->hi_next;
484 		}
485 	}
486 
487 	return (0);
488 }
489 
490 
491 /*
492  * ht_mark_delete
493  *
494  * This function marks an item for deletion, which may be useful when
495  * using findfirst/findnext to avoid modifying the table during the
496  * table scan. Marked items can be removed later using ht_clean_table.
497  */
498 void
499 ht_mark_delete(HT_HANDLE *handle, HT_ITEM *item)
500 {
501 	if (handle && item) {
502 		item->hi_flags |= HTIF_MARKED_DELETED;
503 		handle->ht_total_items--;
504 	}
505 }
506 
507 /*
508  * ht_clear_delete
509  *
510  * This function clear an item from marked for deletion list.
511  */
512 void
513 ht_clear_delete(HT_HANDLE *handle, HT_ITEM *item)
514 {
515 	if (handle && item) {
516 		item->hi_flags &= ~HTIF_MARKED_DELETED;
517 		handle->ht_total_items++;
518 	}
519 }
520 
521 /*
522  * ht_bucket_search
523  *
524  * Returns first item which is not marked as deleted
525  * in the specified bucket by 'head'
526  */
527 static HT_ITEM *
528 ht_bucket_search(HT_ITEM *head)
529 {
530 	HT_ITEM *item = head;
531 	while ((item != 0) && (item->hi_flags & HTIF_MARKED_DELETED))
532 		item = item->hi_next;
533 
534 	return (item);
535 }
536 
537 /*
538  * ht_findfirst
539  *
540  * This function is used to begin an iteration through the hash table.
541  * The iterator is initialized and the first item in the table (as
542  * determined by the hash algorithm) is returned. The current sequence
543  * number is stored in the iterator to determine whether or not the
544  * the table has changed between calls. If the table is empty, a null
545  * pointer is returned.
546  */
547 HT_ITEM *
548 ht_findfirst(HT_HANDLE *handle, HT_ITERATOR *iterator)
549 {
550 	HT_ITEM *item;
551 	size_t h_index;
552 
553 	if (handle == 0 || iterator == 0 || handle->ht_total_items == 0)
554 		return (NULL);
555 
556 	(void) memset(iterator, 0, sizeof (HT_ITERATOR));
557 	iterator->hti_handle = handle;
558 	iterator->hti_sequence = handle->ht_sequence;
559 
560 	for (h_index = 0; h_index < handle->ht_table_size; ++h_index) {
561 		item = ht_bucket_search(handle->ht_table[h_index].he_head);
562 		if (item != 0) {
563 			iterator->hti_index = h_index;
564 			iterator->hti_item = item;
565 			return (item);
566 		}
567 	}
568 
569 	return (NULL);
570 }
571 
572 /*
573  * ht_findnext
574  *
575  * Find the next item in the table for the given iterator. Iterators must
576  * be initialized by ht_findfirst, which will also return the first item
577  * in the table. If an item is available, a pointer to it is returned.
578  * Otherwise a null pointer is returned. A null pointer may indicate:
579  *
580  *	- an invalid iterator (i.e. ht_findfirst has not been called)
581  *	- the table has changed since the previous findfirst/findnext
582  *	- the entire table has been traversed
583  *
584  * The caller can use ht_get_total_items to determine whether or not all
585  * of the items in the table have been visited.
586  */
587 HT_ITEM *
588 ht_findnext(HT_ITERATOR *iterator)
589 {
590 	HT_HANDLE *handle;
591 	HT_ITEM *item;
592 	size_t total;
593 	size_t index;
594 
595 	if (iterator == 0 || iterator->hti_handle == 0 ||
596 	    iterator->hti_sequence == 0) {
597 		/* Invalid iterator */
598 		return (NULL);
599 	}
600 
601 	handle = iterator->hti_handle;
602 
603 	if (iterator->hti_item == 0 ||
604 	    iterator->hti_sequence != handle->ht_sequence) {
605 		/*
606 		 * No more items or the table has changed
607 		 * since the last call.
608 		 */
609 		return (NULL);
610 	}
611 
612 	/*
613 	 * Check for another item in the current bucket.
614 	 */
615 	item = ht_bucket_search(iterator->hti_item->hi_next);
616 	if (item != 0) {
617 		iterator->hti_item = item;
618 		return (item);
619 	}
620 
621 	/*
622 	 * Nothing else in the current bucket. Look for another
623 	 * bucket with something in it and return the head item.
624 	 */
625 	total = handle->ht_table_size;
626 	for (index = iterator->hti_index + 1; index < total; ++index) {
627 		item = ht_bucket_search(handle->ht_table[index].he_head);
628 		if (item != 0) {
629 			iterator->hti_index = index;
630 			iterator->hti_item = item;
631 			return (item);
632 		}
633 	}
634 
635 	iterator->hti_index = 0;
636 	iterator->hti_item = 0;
637 	iterator->hti_sequence = 0;
638 	return (NULL);
639 }
640