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