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