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