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
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.
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]
23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 #pragma ident "%Z%%M% %I% %E% SMI"
32 #include <rpcsvc/nis.h>
34 #include "nis_hashitem.h"
36 /* We're the magician, so undefine the define-magic */
39 #undef nis_insert_item
42 #undef nis_remove_item
44 #define set_thread_status(msg, state)
47 * The hash table routines below implement nested (or recursive)
48 * one-writer-or-many-readers locking. The following restrictions
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),
63 __nis_init_hash_table(__nis_hash_table_mt
*table
,
64 void (*itemDestructor
)(void *)) {
69 errorcode
= pthread_mutex_init(&table
->lock
, 0);
71 syslog(LOG_WARNING
, "__nis_init_hash_table: "
72 "(table->lock) pthread_mutex_init returned %d (%s)",
73 errorcode
, strerror(errorcode
));
76 errorcode
= pthread_cond_init(&table
->cond
, 0);
78 syslog(LOG_WARNING
, "__nis_init_hash_table: "
79 "(table->cond) pthread_cond_init returned %d (%s)",
80 errorcode
, strerror(errorcode
));
83 errorcode
= pthread_mutex_init(&table
->traverser_id_lock
, 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
));
92 table
->locked_items
= 0;
93 (void) memset(table
->keys
, 0, sizeof (table
->keys
));
95 table
->destroyItem
= itemDestructor
;
100 __nis_lock_hash_table(__nis_hash_table_mt
*table
, int traverse
, char *msg
) {
102 pthread_t myself
= pthread_self();
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
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",
122 (void) pthread_cond_wait(&table
->cond
,
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");
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
139 set_thread_status(msg
, "non-traverse TL");
140 if (pthread_mutex_trylock(&table
->lock
) == EBUSY
) {
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. */
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 */
156 MT_LOG(1, (LOG_NOTICE
,
157 "%d: lh table 0x%x cond wait",
159 set_thread_status(msg
, "table WL");
160 (void) pthread_mutex_lock(&table
->lock
);
161 set_thread_status(msg
, "table L");
165 MT_LOG(1, (LOG_NOTICE
, "%d: lh table %s lock acquired 0x%x",
166 myself
, traverse
?"traverse":"non-traverse", table
));
174 __nis_ulock_hash_table(__nis_hash_table_mt
*table
, int traverse
, char *msg
) {
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
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;
193 * Leave traverser_id as it is, so that it
194 * possible to see which thread last held
195 * the traverser lock.
198 /* Wake up other traversers-to-be */
199 set_thread_status(msg
, "table cond_signal");
200 (void) pthread_cond_signal(&table
->cond
);
202 (void) pthread_mutex_unlock(&table
->traverser_id_lock
);
203 set_thread_status(msg
, "traverser_id U");
206 * Called from the nis_*_item() functions. If we're
207 * traversing the table, leave it locked.
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) {
215 (void) pthread_mutex_unlock(&table
->traverser_id_lock
);
216 set_thread_status(msg
, "traverser_id U");
219 (void) pthread_mutex_unlock(&table
->lock
);
220 set_thread_status(msg
, "table U");
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"));
231 static __nis_hash_item_mt
**
232 __find_item_mt(nis_name name
, __nis_hash_table_mt
*table
, int *keyp
) {
236 __nis_hash_item_mt
*it
, **pp
;
238 /* Assume table!=0, table lock held */
240 for (s
= (unsigned char *)name
; *s
!= 0; s
++) {
243 key
%= (sizeof (table
->keys
) / sizeof (table
->keys
[0]));
248 for (pp
= &table
->keys
[key
]; (it
= *pp
) != 0; pp
= &it
->next
) {
249 if (strcmp(name
, it
->name
) == 0) {
258 * The 'readwrite' argument is interpreted as follows on a successful
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
265 * Except when 'readwrite' == 0, the caller must explicitly release the
266 * item (__nis_release_item()).
269 __nis_insert_item_mt(void *arg
, __nis_hash_table_mt
*table
, int readwrite
) {
271 __nis_hash_item_mt
*item
= arg
;
273 __nis_hash_item_mt
**pp
;
275 if (item
== 0 || __nis_lock_hash_table(table
, 0, "nitmt") == 0)
278 if ((*(pp
= __find_item_mt(item
->name
, table
, &key
))) != 0) {
279 (void) __nis_ulock_hash_table(table
, 0, "nitmt");
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
;
288 item
->writer_id
= pthread_self();
289 table
->locked_items
++;
290 } else if (readwrite
> 0) {
292 item
->last_reader_id
= pthread_self();
293 table
->locked_items
++;
297 item
->keychain
= key
;
300 table
->first
->prv_item
= item
;
302 item
->nxt_item
= table
->first
;
303 item
->prv_item
= NULL
;
306 (void) __nis_ulock_hash_table(table
, 0, "nitmt");
312 __nis_insert_name_mt(nis_name name
, __nis_hash_table_mt
*table
) {
314 __nis_hash_item_mt
*item
;
316 if (name
== 0 || table
== 0)
319 if ((item
= malloc(sizeof (*item
))) == 0) {
320 syslog(LOG_WARNING
, "__nis_insert_name_mt: malloc failed\n");
324 if ((item
->name
= strdup(name
)) == 0) {
325 syslog(LOG_WARNING
, "__nis_insert_name_mt: strdup failed\n");
330 if (! __nis_insert_item_mt(item
, table
, 0)) {
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
341 * > 0 Non-exclusive (read-only) access
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.
347 __nis_find_item_mt(nis_name name
, __nis_hash_table_mt
*table
, int readwrite
,
350 __nis_hash_item_mt
*item
;
351 pthread_t me
= pthread_self();
353 if (name
== 0 || __nis_lock_hash_table(table
, 0, "nfimt") == 0)
357 * Block waiting for more favorable conditions unless:
359 * The item doesn't exist anymore
361 * 'readwrite' == 0 (verify existence only)
363 * There's a writer, but it's us
365 * There are no writers, and we're satisfied by RO access
367 * A trylock was requested
369 while ((item
= *__find_item_mt(name
, table
, 0)) != 0) {
370 if (readwrite
== 0 ||
371 (item
->writer
== 0 && item
->readers
== 0))
373 if (item
->writer
== 0 && readwrite
> 0)
375 if ((item
->writer
!= 0 && item
->writer_id
== me
))
377 if (trylock
!= 0 && *trylock
!= 0) {
379 (void) __nis_ulock_hash_table(table
, 0, "nfimt");
382 (void) pthread_cond_wait(&item
->lock
, &table
->lock
);
387 if (item
->writer
== 0) {
388 item
->writer_id
= me
;
389 table
->locked_items
++;
392 } else if (readwrite
> 0) {
393 if (item
->readers
== 0) {
394 table
->locked_items
++;
396 item
->last_reader_id
= me
;
401 (void) __nis_ulock_hash_table(table
, 0, "nfimt");
407 __nis_pop_item_mt(__nis_hash_table_mt
*table
) {
409 __nis_hash_item_mt
*item
, *cur
, *prev
;
412 if (__nis_lock_hash_table(table
, 0, "npimt") == 0)
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)
420 if (item
->readers
== 0 && item
->writer
== 0)
422 if (item
->writer
!= 0 && item
->writer_id
== mtid
)
424 (void) pthread_cond_wait(&item
->lock
, &table
->lock
);
427 /* List might be empty now */
429 __nis_ulock_hash_table(table
, 0, "npimt");
434 for (cur
= table
->keys
[item
->keychain
]; cur
;
435 prev
= cur
, cur
= cur
->next
) {
438 prev
->next
= cur
->next
;
440 table
->keys
[cur
->keychain
] = cur
->next
;
442 cur
->prv_item
->nxt_item
= cur
->nxt_item
;
444 table
->first
= cur
->nxt_item
;
446 cur
->nxt_item
->prv_item
= cur
->prv_item
;
452 * We use keychain < 0 to indicate that the item isn't linked
453 * into the table anymore.
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
);
468 * Wake up any threads that were waiting for this item. Obviously,
469 * such threads must start over scanning the list.
471 (void) pthread_cond_signal(&item
->lock
);
472 (void) pthread_cond_destroy(&item
->lock
);
475 * If the item isn't locked, and an item destructor has been
476 * established, invoke the destructor.
478 if (item
->readers
== 0 && item
->writer
== 0 &&
479 table
->destroyItem
!= 0) {
480 (*table
->destroyItem
)(item
);
488 (void) __nis_ulock_hash_table(table
, 0, "npimt");
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.
496 return ((item
!= 0) ? item
: __nis_pop_item_mt(table
));
500 __nis_remove_item_mt(nis_name name
, __nis_hash_table_mt
*table
) {
502 __nis_hash_item_mt
*nl
, **pp
;
505 if (__nis_lock_hash_table(table
, 0, "nrimt") == 0)
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)
513 if (nl
->readers
== 0 && nl
->writer
== 0)
515 if (nl
->writer
!= 0 && nl
->writer_id
== mtid
)
517 (void) pthread_cond_wait(&nl
->lock
, &table
->lock
);
521 (void) __nis_ulock_hash_table(table
, 0, "nrimt");
525 /* Remove nl from the hash chain */
529 /* Remove nl from the linked list of all names */
531 nl
->prv_item
->nxt_item
= nl
->nxt_item
;
533 table
->first
= nl
->nxt_item
;
536 nl
->nxt_item
->prv_item
= nl
->prv_item
;
540 /* keychain < 0 means not in table anymore */
544 * If this item was locked, we can now decrement the count of
545 * locked items for the table.
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
);
555 (void) pthread_cond_signal(&nl
->lock
);
556 (void) pthread_cond_destroy(&nl
->lock
);
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
564 if (nl
->readers
== 0 && nl
->writer
== 0 && table
->destroyItem
!= 0) {
565 (*table
->destroyItem
)(nl
);
569 (void) __nis_ulock_hash_table(table
, 0, "nrimt");
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.
581 __nis_release_item(void *arg
, __nis_hash_table_mt
*table
, int readwrite
) {
583 __nis_hash_item_mt
*item
= arg
;
586 if (item
== 0 || __nis_lock_hash_table(table
, 0, "nreli") == 0)
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");
599 item
->writer
+= readwrite
;
600 if (item
->writer
== 0 && item
->keychain
>= 0) {
601 if (table
->locked_items
!= 0)
602 table
->locked_items
--;
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
--;
615 if (table
->locked_items
== 0) {
616 /* Wake up traversers-to-be */
617 (void) pthread_cond_signal(&table
->cond
);
620 /* Wake up anyone else who wants this item */
621 (void) pthread_cond_signal(&item
->lock
);
625 * Delete if no references, not linked into list, and destructor
628 if (item
->keychain
< 0 &&
629 item
->readers
== 0 && item
->writer
== 0 &&
631 item
->prv_item
== 0 && item
->nxt_item
== 0 &&
632 table
->destroyItem
!= 0)
633 (*table
->destroyItem
)(item
);
635 (void) __nis_ulock_hash_table(table
, 0, "nreli");
641 * Return -1 if item checked out for both reading and writing, 1 if
642 * readonly, and 0 otherwise.
645 __nis_item_access(void *arg
) {
647 __nis_hash_item_mt
*item
= arg
;
650 if (item
->writer
> 0) {
651 if (item
->writer_id
!= pthread_self())
654 } else if (item
->readers
> 0) {
663 * __nis_scan_table_mt()
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
674 __nis_hash_table_mt
*table
,
675 bool_t (*func
)(__nis_hash_item_mt
*, void *),
684 if (__nis_lock_hash_table(table
, 1, "nstmt") == 0) {
685 syslog(LOG_DEBUG
, "__nis_scan_table_mt: mutex lock failed ");
690 slot
< sizeof (table
->keys
) / sizeof (table
->keys
[0]);
692 __nis_hash_item_mt
*it
;
694 for (it
= table
->keys
[slot
]; it
!= 0; it
= it
->next
) {
695 if (TRUE
== (*func
)(it
, funcarg
)) {
700 if (__nis_ulock_hash_table(table
, 1, "nstmt") == 0)
701 syslog(LOG_DEBUG
, "__nis_scan_table_mt: mutex unlock failed ");