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 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 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 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 ** 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 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 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 * 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 * 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 * 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 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 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 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