xref: /titanic_44/usr/src/lib/libnisdb/nis_hashitem.c (revision 8eea8e29cc4374d1ee24c25a07f45af132db3499)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <string.h>
30 #include <pthread.h>
31 #include <syslog.h>
32 #include <rpcsvc/nis.h>
33 
34 #include "nis_hashitem.h"
35 
36 /* We're the magician, so undefine the define-magic */
37 #undef	NIS_HASH_ITEM
38 #undef	NIS_HASH_TABLE
39 #undef	nis_insert_item
40 #undef	nis_find_item
41 #undef	nis_pop_item
42 #undef	nis_remove_item
43 
44 #define	set_thread_status(msg, state)
45 
46 /*
47  * The hash table routines below implement nested (or recursive)
48  * one-writer-or-many-readers locking. The following restrictions
49  * exist:
50  *
51  *	Unless an item destructor has been established, an item
52  *	MUST NOT be removed from a list (__nis_pop_item_mt() or
53  *	(__nis_remove_item_mt()) when the thread performing
54  *	the deletion is holding a read-only lock on the item.
55  *	Doing so will result in the thread blocking in
56  *	pthread_cond_wait() waiting for itself to signal on
57  *	the condition variable. Deletion when the invoking
58  *	thread is holding a write lock (any level of nesting),
59  *	or no lock, is OK.
60  */
61 
62 void
63 __nis_init_hash_table(__nis_hash_table_mt *table,
64 			void (*itemDestructor)(void *)) {
65 
66 	if (table != 0) {
67 		(void) pthread_mutex_init(&table->lock, 0);
68 		(void) pthread_cond_init(&table->cond, 0);
69 		(void) pthread_mutex_init(&table->traverser_id_lock, 0);
70 		table->traversed = 0;
71 		table->locked_items = 0;
72 		(void) memset(table->keys, 0, sizeof (table->keys));
73 		table->first = 0;
74 		table->destroyItem = itemDestructor;
75 	}
76 }
77 
78 int
79 __nis_lock_hash_table(__nis_hash_table_mt *table, int traverse, char *msg) {
80 
81 	pthread_t	myself = pthread_self();
82 
83 	if (table != 0) {
84 		if (traverse) {
85 			/*
86 			 * We want exclusive access to everything in the
87 			 * table (list). Wait until there are no threads
88 			 * either traversing the list, or with exclusive
89 			 * access to an item.
90 			 */
91 			set_thread_status(msg, "table WL");
92 			(void) pthread_mutex_lock(&table->lock);
93 			set_thread_status(msg, "table L");
94 			while ((table->traversed != 0 &&
95 					table->traverser_id != myself) ||
96 				table->locked_items != 0) {
97 				set_thread_status(msg, "traverse cond_wait");
98 				MT_LOG(1, (LOG_NOTICE,
99 					"%d: lh table 0x%x trav cond wait",
100 					myself, table));
101 				(void) pthread_cond_wait(&table->cond,
102 							&table->lock);
103 			}
104 			set_thread_status(msg, "traverser_id WL");
105 			(void) pthread_mutex_lock(&table->traverser_id_lock);
106 			set_thread_status(msg, "traverser_id L");
107 			table->traversed = 1;
108 			table->traverser_id = myself;
109 			(void) pthread_mutex_unlock(&table->traverser_id_lock);
110 			set_thread_status(msg, "traverser_id U");
111 		} else {
112 			/*
113 			 * Called from the nis_*_item() functions. If no one's
114 			 * locked the table, lock it. If the table already is
115 			 * being traversed by us, do nothing. Otherwise, wait
116 			 * for the lock.
117 			 */
118 			set_thread_status(msg, "non-traverse TL");
119 			if (pthread_mutex_trylock(&table->lock) == EBUSY) {
120 				int	dolock = 1;
121 				/* Already locked; find out if it's us */
122 				set_thread_status(msg, "traverser_id L");
123 				(void) pthread_mutex_lock(
124 						&table->traverser_id_lock);
125 				if (table->traversed != 0 &&
126 					table->traverser_id == myself) {
127 					/* It's us. No action. */
128 					dolock = 0;
129 				}
130 				(void) pthread_mutex_unlock(
131 						&table->traverser_id_lock);
132 				set_thread_status(msg, "traverser_id U");
133 				/* Not us. Wait for the lock */
134 				if (dolock) {
135 					MT_LOG(1, (LOG_NOTICE,
136 					"%d: lh table 0x%x cond wait",
137 						myself, table));
138 					set_thread_status(msg, "table WL");
139 					(void) pthread_mutex_lock(&table->lock);
140 					set_thread_status(msg, "table L");
141 				}
142 			}
143 		}
144 		MT_LOG(1, (LOG_NOTICE, "%d: lh table %s lock acquired 0x%x",
145 		myself, traverse?"traverse":"non-traverse", table));
146 		return (1);
147 	} else {
148 		return (0);
149 	}
150 }
151 
152 int
153 __nis_ulock_hash_table(__nis_hash_table_mt *table, int traverse, char *msg) {
154 
155 	int	dounlock = 0;
156 
157 	if (table != 0) {
158 		if (traverse) {
159 			/*
160 			 * Since we're keeping track of who's traversing the
161 			 * table in order to avoid recursive locking in the
162 			 * nis_*item() functions, we might as well sanity check
163 			 * here.
164 			 */
165 			set_thread_status(msg, "traverser_id WL");
166 			(void) pthread_mutex_lock(&table->traverser_id_lock);
167 			set_thread_status(msg, "traverser_id L");
168 			if (table->traversed != 0 &&
169 				table->traverser_id == pthread_self()) {
170 				table->traversed = 0;
171 				/*
172 				 * Leave traverser_id as it is, so that it
173 				 * possible to see which thread last held
174 				 * the traverser lock.
175 				 */
176 				dounlock = 1;
177 				/* Wake up other traversers-to-be */
178 				set_thread_status(msg, "table cond_signal");
179 				(void) pthread_cond_signal(&table->cond);
180 			}
181 			(void) pthread_mutex_unlock(&table->traverser_id_lock);
182 			set_thread_status(msg, "traverser_id U");
183 		} else {
184 			/*
185 			 * Called from the nis_*_item() functions. If we're
186 			 * traversing the table, leave it locked.
187 			 */
188 			set_thread_status(msg, "traverser_id WL");
189 			(void) pthread_mutex_lock(&table->traverser_id_lock);
190 			set_thread_status(msg, "traverser_id L");
191 			if (table->traversed == 0) {
192 				dounlock = 1;
193 			}
194 			(void) pthread_mutex_unlock(&table->traverser_id_lock);
195 			set_thread_status(msg, "traverser_id U");
196 		}
197 		if (dounlock) {
198 			(void) pthread_mutex_unlock(&table->lock);
199 			set_thread_status(msg, "table U");
200 		}
201 		MT_LOG(1, (LOG_NOTICE, "%d: lh table %s release 0x%x (%s)",
202 		pthread_self(), traverse?"traverse":"non-traverse", table,
203 			dounlock?"unlocked":"still held"));
204 		return (1);
205 	} else {
206 		return (0);
207 	}
208 }
209 
210 static __nis_hash_item_mt **
211 __find_item_mt(nis_name name, __nis_hash_table_mt *table, int *keyp) {
212 
213 	int			key = 0;
214 	unsigned char		*s;
215 	__nis_hash_item_mt	*it, **pp;
216 
217 	/* Assume table!=0, table lock held */
218 
219 	for (s = (unsigned char *)name;  *s != 0;  s++) {
220 		key += *s;
221 	}
222 	key %= (sizeof (table->keys) / sizeof (table->keys[0]));
223 
224 	if (keyp != 0) {
225 		*keyp = key;
226 	}
227 	for (pp = &table->keys[key];  (it = *pp) != 0;  pp = &it->next) {
228 		if (strcmp(name, it->name) == 0) {
229 			break;
230 		}
231 	}
232 
233 	return (pp);
234 }
235 
236 /*
237  * The 'readwrite' argument is interpreted as follows on a successful
238  * return:
239  *
240  *	< 0	Exclusive access to item
241  *	0	Item must not be used or referenced in any way
242  *	> 0	Non-exclusive access (read-only) to item
243  *
244  * Except when 'readwrite' ==  0, the caller must explicitly release the
245  * item (__nis_release_item()).
246  */
247 int
248 __nis_insert_item_mt(void *arg, __nis_hash_table_mt *table, int readwrite) {
249 
250 	__nis_hash_item_mt	*item = arg;
251 	int			key;
252 	__nis_hash_item_mt	**pp;
253 
254 	if (item == 0 || __nis_lock_hash_table(table, 0, "nitmt") == 0)
255 		return (0);
256 
257 	if ((*(pp = __find_item_mt(item->name, table, &key))) != 0) {
258 		(void) __nis_ulock_hash_table(table, 0, "nitmt");
259 		return (0);
260 	}
261 
262 	(void) pthread_cond_init(&item->lock, 0);
263 	item->readers = item->writer = 0;
264 	item->last_reader_id = item->writer_id = INV_PTHREAD_ID;
265 	if (readwrite < 0) {
266 		item->writer = 1;
267 		item->writer_id = pthread_self();
268 		table->locked_items++;
269 	} else if (readwrite > 0) {
270 		item->readers = 1;
271 		item->last_reader_id = pthread_self();
272 		table->locked_items++;
273 	}
274 	item->next	= *pp;
275 	*pp		= item;
276 	item->keychain	= key;
277 
278 	if (table->first)
279 		table->first->prv_item = item;
280 
281 	item->nxt_item	= table->first;
282 	item->prv_item	= NULL;
283 	table->first	= item;
284 
285 	(void) __nis_ulock_hash_table(table, 0, "nitmt");
286 
287 	return (1);
288 }
289 
290 void
291 __nis_insert_name_mt(nis_name name, __nis_hash_table_mt *table) {
292 
293 	__nis_hash_item_mt	*item;
294 
295 	if (name == 0 || table == 0)
296 		return;
297 
298 	if ((item = malloc(sizeof (*item))) == 0) {
299 		syslog(LOG_WARNING, "__nis_insert_name_mt: malloc failed\n");
300 		return;
301 	}
302 
303 	if ((item->name = strdup(name)) == 0) {
304 		syslog(LOG_WARNING, "__nis_insert_name_mt: strdup failed\n");
305 		free(item);
306 		return;
307 	}
308 
309 	if (! __nis_insert_item_mt(item, table, 0)) {
310 		free(item->name);
311 		free(item);
312 	}
313 }
314 
315 /*
316  * readwrite:	< 0	Exclusive access
317  *		0	No access; must not use returned item in any way,
318  *			other than to confirm existence indicated by a non-NULL
319  *			return value.
320  *		> 0	Non-exclusive (read-only) access
321  *
322  * If trylock != 0 and *trylock != 0 and the item exists but the requested
323  * lock type cannot be acquired, set *trylock = -1 and return 0.
324  */
325 void *
326 __nis_find_item_mt(nis_name name, __nis_hash_table_mt *table, int readwrite,
327 			int *trylock) {
328 
329 	__nis_hash_item_mt	*item;
330 	pthread_t		me = pthread_self();
331 
332 	if (name == 0 || __nis_lock_hash_table(table, 0, "nfimt") == 0)
333 		return (0);
334 
335 	/*
336 	 * Block waiting for more favorable conditions unless:
337 	 *
338 	 *	The item doesn't exist anymore
339 	 *
340 	 *	'readwrite' == 0 (verify existence only)
341 	 *
342 	 *	There's a writer, but it's us
343 	 *
344 	 *	There are no writers, and we're satisfied by RO access
345 	 *
346 	 *	A trylock was requested
347 	 */
348 	while ((item = *__find_item_mt(name, table, 0)) != 0) {
349 		if (readwrite == 0 ||
350 				(item->writer == 0 && item->readers == 0))
351 			break;
352 		if (item->writer == 0 && readwrite > 0)
353 			break;
354 		if ((item->writer != 0 && item->writer_id == me))
355 			break;
356 		if (trylock != 0 && *trylock != 0) {
357 			*trylock = -1;
358 			(void) __nis_ulock_hash_table(table, 0, "nfimt");
359 			return (0);
360 		}
361 		(void) pthread_cond_wait(&item->lock, &table->lock);
362 	}
363 
364 	if (item != 0) {
365 		if (readwrite < 0) {
366 			if (item->writer == 0) {
367 				item->writer_id = me;
368 				table->locked_items++;
369 			}
370 			item->writer++;
371 		} else if (readwrite > 0) {
372 			if (item->readers == 0) {
373 				table->locked_items++;
374 			}
375 			item->last_reader_id = me;
376 			item->readers++;
377 		}
378 	}
379 
380 	(void) __nis_ulock_hash_table(table, 0, "nfimt");
381 
382 	return (item);
383 }
384 
385 void *
386 __nis_pop_item_mt(__nis_hash_table_mt *table) {
387 
388 	__nis_hash_item_mt	*item, *cur, *prev;
389 	pthread_t		mtid;
390 
391 	if (__nis_lock_hash_table(table, 0, "npimt") == 0)
392 		return (0);
393 
394 	/* Wait until the first item isn't in use by another thread */
395 	mtid = pthread_self();
396 	while ((item = table->first) != 0) {
397 		if (table->destroyItem != 0)
398 			break;
399 		if (item->readers == 0 && item->writer == 0)
400 			break;
401 		if (item->writer != 0 && item->writer_id == mtid)
402 			break;
403 		(void) pthread_cond_wait(&item->lock, &table->lock);
404 	}
405 
406 	/* List might be empty now */
407 	if (item == 0) {
408 		__nis_ulock_hash_table(table, 0, "npimt");
409 		return (0);
410 	}
411 
412 	prev = 0;
413 	for (cur = table->keys[item->keychain]; cur;
414 					prev = cur, cur = cur->next) {
415 		if (cur == item) {
416 			if (prev)
417 				prev->next = cur->next;
418 			else
419 				table->keys[cur->keychain] = cur->next;
420 			if (cur->prv_item)
421 				cur->prv_item->nxt_item = cur->nxt_item;
422 			else
423 				table->first = cur->nxt_item;
424 			if (cur->nxt_item)
425 				cur->nxt_item->prv_item = cur->prv_item;
426 			break;
427 		}
428 	}
429 
430 	/*
431 	 * We use keychain < 0 to indicate that the item isn't linked
432 	 * into the table anymore.
433 	 */
434 	item->keychain = -1;
435 
436 	/* Adjust the count of locked items in the table */
437 	if (table->locked_items != 0 &&
438 			(item->writer > 0 || item->readers > 0)) {
439 		table->locked_items--;
440 		if (table->locked_items == 0) {
441 			/* Wake up traversers-to-be */
442 			(void) pthread_cond_signal(&table->cond);
443 		}
444 	}
445 
446 	/*
447 	 * Wake up any threads that were waiting for this item. Obviously,
448 	 * such threads must start over scanning the list.
449 	 */
450 	(void) pthread_cond_signal(&item->lock);
451 	(void) pthread_cond_destroy(&item->lock);
452 
453 	/*
454 	 * If the item isn't locked, and an item destructor has been
455 	 * established, invoke the destructor.
456 	 */
457 	if (item->readers == 0 && item->writer == 0 &&
458 			table->destroyItem != 0) {
459 		(*table->destroyItem)(item);
460 		item = 0;
461 	} else {
462 		item->next = 0;
463 		item->prv_item = 0;
464 		item->nxt_item = 0;
465 	}
466 
467 	(void) __nis_ulock_hash_table(table, 0, "npimt");
468 
469 	/*
470 	 * If we get here, and the 'item' is NULL, we've popped the
471 	 * item, but also destroyed it. Returning NULL would make
472 	 * our caller believe the list is empty, so instead, we invoke
473 	 * ourselves to pop the next item.
474 	 */
475 	return ((item != 0) ? item : __nis_pop_item_mt(table));
476 }
477 
478 void *
479 __nis_remove_item_mt(nis_name name, __nis_hash_table_mt *table) {
480 
481 	__nis_hash_item_mt	*nl, **pp;
482 	pthread_t		mtid;
483 
484 	if (__nis_lock_hash_table(table, 0, "nrimt") == 0)
485 		return (0);
486 
487 	/* Find the item, and make sure it's not in use */
488 	mtid = pthread_self();
489 	while ((nl = *(pp = __find_item_mt(name, table, (int *)0))) != 0) {
490 		if (table->destroyItem != 0)
491 			break;
492 		if (nl->readers == 0 && nl->writer == 0)
493 			break;
494 		if (nl->writer != 0 && nl->writer_id == mtid)
495 			break;
496 		(void) pthread_cond_wait(&nl->lock, &table->lock);
497 	}
498 
499 	if (nl == 0) {
500 		(void) __nis_ulock_hash_table(table, 0, "nrimt");
501 		return (0);
502 	}
503 
504 	/* Remove nl from the hash chain */
505 	*pp = nl->next;
506 	nl->next = 0;
507 
508 	/* Remove nl from the linked list of all names */
509 	if (nl->prv_item)
510 		nl->prv_item->nxt_item = nl->nxt_item;
511 	else
512 		table->first = nl->nxt_item;
513 
514 	if (nl->nxt_item)
515 		nl->nxt_item->prv_item = nl->prv_item;
516 	nl->prv_item = 0;
517 	nl->nxt_item = 0;
518 
519 	/* keychain < 0 means not in table anymore */
520 	nl->keychain = -1;
521 
522 	/*
523 	 * If this item was locked, we can now decrement the count of
524 	 * locked items for the table.
525 	 */
526 	if (table->locked_items != 0 &&
527 		(nl->writer > 0 || nl->readers > 0)) {
528 		table->locked_items--;
529 		if (table->locked_items == 0) {
530 			/* Wake up traversers-to-be */
531 			(void) pthread_cond_signal(&table->cond);
532 		}
533 	}
534 	(void) pthread_cond_signal(&nl->lock);
535 	(void) pthread_cond_destroy(&nl->lock);
536 
537 	/*
538 	 * If the item isn't locked, and an item destructor has been
539 	 * established, invoke the destructor. In that case, we return
540 	 * NULL, so that our caller doesn't try to reference the
541 	 * deleted item.
542 	 */
543 	if (nl->readers == 0 && nl->writer == 0 && table->destroyItem != 0) {
544 		(*table->destroyItem)(nl);
545 		nl = 0;
546 	}
547 
548 	(void) __nis_ulock_hash_table(table, 0, "nrimt");
549 
550 	return (nl);
551 }
552 
553 /*
554  * Release an item that had been acquired exclusively or non-exclusively.
555  * Note that 'readwrite' can assume any integer value, and thus can be
556  * used to release any level of recursive locking. It's the responsibility
557  * of the caller to make sure that proper nesting is maintained.
558  */
559 int
560 __nis_release_item(void *arg, __nis_hash_table_mt *table, int readwrite) {
561 
562 	__nis_hash_item_mt	*item = arg;
563 	int			wakeup = 0;
564 
565 	if (item == 0 || __nis_lock_hash_table(table, 0, "nreli") == 0)
566 		return (0);
567 
568 	if ((readwrite < 0 && abs(readwrite) > item->writer) ||
569 		(readwrite < 0 && item->writer > 0 &&
570 			item->writer_id != pthread_self()) ||
571 		(readwrite > 0 && readwrite > item->readers)) {
572 		/* Caller confused; ignore */
573 		(void) __nis_ulock_hash_table(table, 0, "nreli");
574 		return (0);
575 	}
576 
577 	if (readwrite < 0) {
578 		item->writer += readwrite;
579 		if (item->writer == 0 && item->keychain >= 0) {
580 			if (table->locked_items != 0)
581 				table->locked_items--;
582 			wakeup = 1;
583 		}
584 	} else if (readwrite > 0) {
585 		item->readers -= readwrite;
586 		item->last_reader_id = INV_PTHREAD_ID;
587 		if (item->readers == 0 && item->keychain >= 0) {
588 			if (table->locked_items != 0)
589 				table->locked_items--;
590 			wakeup = 1;
591 		}
592 	}
593 
594 	if (table->locked_items == 0) {
595 		/* Wake up traversers-to-be */
596 		(void) pthread_cond_signal(&table->cond);
597 	}
598 	if (wakeup) {
599 		/* Wake up anyone else who wants this item */
600 		(void) pthread_cond_signal(&item->lock);
601 	}
602 
603 	/*
604 	 * Delete if no references, not linked into list, and destructor
605 	 * established.
606 	 */
607 	if (item->keychain < 0 &&
608 			item->readers == 0 && item->writer == 0 &&
609 			item->next == 0 &&
610 			item->prv_item == 0 && item->nxt_item == 0 &&
611 			table->destroyItem != 0)
612 		(*table->destroyItem)(item);
613 
614 	(void) __nis_ulock_hash_table(table, 0, "nreli");
615 
616 	return (1);
617 }
618 
619 /*
620  * Return -1 if item checked out for both reading and writing, 1 if
621  * readonly, and 0 otherwise.
622  */
623 int
624 __nis_item_access(void *arg) {
625 
626 	__nis_hash_item_mt	*item = arg;
627 
628 	if (item != 0) {
629 		if (item->writer > 0) {
630 			if (item->writer_id != pthread_self())
631 				abort();
632 			return (-1);
633 		} else if (item->readers > 0) {
634 			return (1);
635 		}
636 	}
637 
638 	return (0);
639 }
640 
641 /*
642  * __nis_scan_table_mt()
643  *
644  * Iterate over all items in a __nis_hash_table_mt. We ignore
645  * first/prv_item/nxt_item and scan in hash-chain order.  The iterator
646  * function should *not* insert or delete items. If the iterator
647  * function returns TRUE the scan terminates. For compatibility with
648  * the existing non-MT nis_scan_table() this function has no return
649  * value.
650  */
651 void
652 __nis_scan_table_mt(
653 	__nis_hash_table_mt	*table,
654 	bool_t		(*func)(__nis_hash_item_mt *, void *),
655 	void		*funcarg)
656 {
657 	int slot;
658 
659 	if (table == 0) {
660 		return;
661 	}
662 
663 	if (__nis_lock_hash_table(table, 1, "nstmt") == 0) {
664 		syslog(LOG_DEBUG, "__nis_scan_table_mt: mutex lock failed ");
665 		return;
666 	}
667 
668 	for (slot = 0;
669 	    slot < sizeof (table->keys) / sizeof (table->keys[0]);
670 	    slot++) {
671 		__nis_hash_item_mt *it;
672 
673 		for (it = table->keys[slot]; it != 0; it = it->next) {
674 			if (TRUE == (*func)(it, funcarg)) {
675 				break;
676 			}
677 		}
678 	}
679 	    if (__nis_ulock_hash_table(table, 1, "nstmt") == 0)
680 		syslog(LOG_DEBUG, "__nis_scan_table_mt: mutex unlock failed ");
681 }
682