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
62 void
__nis_init_hash_table(__nis_hash_table_mt * table,void (* itemDestructor)(void *))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
99 int
__nis_lock_hash_table(__nis_hash_table_mt * table,int traverse,char * msg)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
173 int
__nis_ulock_hash_table(__nis_hash_table_mt * table,int traverse,char * msg)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
231 static __nis_hash_item_mt **
__find_item_mt(nis_name name,__nis_hash_table_mt * table,int * keyp)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 */
268 int
__nis_insert_item_mt(void * arg,__nis_hash_table_mt * table,int readwrite)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
311 void
__nis_insert_name_mt(nis_name name,__nis_hash_table_mt * table)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 */
346 void *
__nis_find_item_mt(nis_name name,__nis_hash_table_mt * table,int readwrite,int * trylock)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
406 void *
__nis_pop_item_mt(__nis_hash_table_mt * table)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
499 void *
__nis_remove_item_mt(nis_name name,__nis_hash_table_mt * table)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 */
580 int
__nis_release_item(void * arg,__nis_hash_table_mt * table,int readwrite)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 */
644 int
__nis_item_access(void * arg)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 */
672 void
__nis_scan_table_mt(__nis_hash_table_mt * table,bool_t (* func)(__nis_hash_item_mt *,void *),void * funcarg)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