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 (c) 2001 by Sun Microsystems, Inc. 24 * All rights reserved. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include <stdio.h> 30 #include <rpc/types.h> 31 #include <rpc/xdr.h> 32 #include "db_dictionary_c.h" 33 #include "nisdb_rw.h" 34 #include "nisdb_ldap.h" 35 36 /* 37 * Nesting-safe RW locking functions. Return 0 when successful, an 38 * error number from the E-series when not. 39 */ 40 41 int 42 __nisdb_rwinit(__nisdb_rwlock_t *rw) { 43 44 int ret; 45 46 if (rw == 0) { 47 #ifdef NISDB_MT_DEBUG 48 abort(); 49 #endif /* NISDB_MT_DEBUG */ 50 return (EFAULT); 51 } 52 53 if ((ret = mutex_init(&rw->mutex, USYNC_THREAD, 0)) != 0) 54 return (ret); 55 if ((ret = cond_init(&rw->cv, USYNC_THREAD, 0)) != 0) 56 return (ret); 57 rw->destroyed = 0; 58 59 /* 60 * If we allow read-to-write lock migration, there's a potential 61 * race condition if two or more threads want to upgrade at the 62 * same time. The simple and safe (but crude and possibly costly) 63 * method to fix this is to always use exclusive locks, and so 64 * that has to be the default. 65 * 66 * There are two conditions under which it is safe to set 67 * 'force_write' to zero for a certain lock structure: 68 * 69 * (1) The lock will never be subject to migration, or 70 * 71 * (2) It's OK if the data protected by the lock has changed 72 * (a) Every time the lock (read or write) has been 73 * acquired (even if the lock already was held by 74 * the thread), and 75 * (b) After every call to a function that might have 76 * acquired the lock. 77 */ 78 rw->force_write = NISDB_FORCE_WRITE; 79 80 rw->writer_count = rw->reader_count = rw->reader_blocked = 0; 81 rw->writer.id = rw->reader.id = INV_PTHREAD_ID; 82 rw->writer.count = rw->reader.count = 0; 83 rw->writer.next = rw->reader.next = 0; 84 85 return (0); 86 } 87 88 89 static __nisdb_rl_t * 90 find_reader(pthread_t id, __nisdb_rwlock_t *rw) { 91 92 __nisdb_rl_t *rr; 93 94 for (rr = &rw->reader; rr != 0; rr = rr->next) { 95 if (rr->id == INV_PTHREAD_ID) { 96 rr = 0; 97 break; 98 } 99 if (rr->id == id) 100 break; 101 } 102 103 return (rr); 104 } 105 106 107 int 108 __nisdb_rw_readlock_ok(__nisdb_rwlock_t *rw) { 109 int ret; 110 pthread_t myself = pthread_self(); 111 __nisdb_rl_t *rr; 112 113 if (rw == 0) 114 return (EFAULT); 115 116 if (rw->destroyed != 0) 117 return (ESHUTDOWN); 118 119 if ((ret = mutex_lock(&rw->mutex)) != 0) 120 return (ret); 121 122 /* 123 * Only allow changing 'force_write' when it's really safe; i.e., 124 * the lock hasn't been destroyed, and there are no readers. 125 */ 126 if (rw->destroyed == 0 && rw->reader_count == 0) { 127 rw->force_write = 0; 128 ret = 0; 129 } else { 130 ret = EBUSY; 131 } 132 133 (void) mutex_unlock(&rw->mutex); 134 135 return (ret); 136 } 137 138 139 int 140 __nisdb_rw_force_writelock(__nisdb_rwlock_t *rw) { 141 int ret; 142 pthread_t myself = pthread_self(); 143 __nisdb_rl_t *rr; 144 145 if (rw == 0 || rw->destroyed != 0) 146 return (ESHUTDOWN); 147 148 if ((ret = mutex_lock(&rw->mutex)) != 0) 149 return (ret); 150 151 /* 152 * Only allow changing 'force_write' when it's really safe; i.e., 153 * the lock hasn't been destroyed, and there are no readers. 154 */ 155 if (rw->destroyed == 0 && rw->reader_count == 0) { 156 rw->force_write = 1; 157 ret = 0; 158 } else { 159 ret = EBUSY; 160 } 161 162 (void) mutex_unlock(&rw->mutex); 163 164 return (ret); 165 } 166 167 168 int 169 __nisdb_wlock_trylock(__nisdb_rwlock_t *rw, int trylock) { 170 171 int ret; 172 pthread_t myself = pthread_self(); 173 int all_readers_blocked = 0; 174 __nisdb_rl_t *rr = 0; 175 176 if (rw == 0) { 177 #ifdef NISDB_MT_DEBUG 178 /* This shouldn't happen */ 179 abort(); 180 #endif /* NISDB_MT_DEBUG */ 181 return (EFAULT); 182 } 183 184 if (rw->destroyed != 0) 185 return (ESHUTDOWN); 186 187 if ((ret = mutex_lock(&rw->mutex)) != 0) 188 return (ret); 189 190 if (rw->destroyed != 0) { 191 (void) mutex_unlock(&rw->mutex); 192 return (ESHUTDOWN); 193 } 194 195 /* Simplest (and probably most common) case: no readers or writers */ 196 if (rw->reader_count == 0 && rw->writer_count == 0) { 197 rw->writer_count = 1; 198 rw->writer.id = myself; 199 rw->writer.count = 1; 200 return (mutex_unlock(&rw->mutex)); 201 } 202 203 /* 204 * Need to know if we're holding a read lock already, and if 205 * all other readers are blocked waiting for the mutex. 206 */ 207 if (rw->reader_count > 0) { 208 if ((rr = find_reader(myself, rw)) != 0) { 209 if (rr->count) 210 /* 211 * We're already holding a read lock, so 212 * if the number of readers equals the number 213 * of blocked readers plus one, all other 214 * readers are blocked. 215 */ 216 if (rw->reader_count == 217 (rw->reader_blocked + 1)) 218 all_readers_blocked = 1; 219 else 220 /* 221 * We're not holding a read lock, so the 222 * number of readers should equal the number 223 * of blocked readers if all readers are 224 * blocked. 225 */ 226 if (rw->reader_count == rw->reader_blocked) 227 all_readers_blocked = 1; 228 } 229 } 230 231 /* Wait for reader(s) or writer to finish */ 232 while (1) { 233 /* 234 * We can stop looping if one of the following holds: 235 * - No readers, no writers 236 * - No writers (or writer is myself), and one of: 237 * - No readers 238 * - One reader, and it's us 239 * - N readers, but all blocked on the mutex 240 */ 241 if ( 242 (rw->writer_count == 0 && rw->reader_count == 0) || 243 ((rw->writer_count == 0 || rw->writer.id == myself) && 244 (rw->reader_count == 0) || 245 (rw->reader_count == 1 && 246 rw->reader.id == myself))) { 247 break; 248 } 249 /* 250 * Provided that all readers are blocked on the mutex 251 * we break a potential dead-lock by acquiring the 252 * write lock. 253 */ 254 if (all_readers_blocked) { 255 if (rw->writer_count == 0 || rw->writer.id == myself) { 256 break; 257 } 258 } 259 260 /* 261 * If 'trylock' is set, tell the caller that we'd have to 262 * block to obtain the lock. 263 */ 264 if (trylock) { 265 (void) mutex_unlock(&rw->mutex); 266 return (EBUSY); 267 } 268 269 /* If we're also a reader, indicate that we're blocking */ 270 if (rr != 0) { 271 rr->wait = 1; 272 rw->reader_blocked++; 273 } 274 if ((ret = cond_wait(&rw->cv, &rw->mutex)) != 0) { 275 if (rr != 0) { 276 rr->wait = 0; 277 if (rw->reader_blocked > 0) 278 rw->reader_blocked--; 279 #ifdef NISDB_MT_DEBUG 280 else 281 abort(); 282 #endif /* NISDB_MT_DEBUG */ 283 } 284 (void) mutex_unlock(&rw->mutex); 285 return (ret); 286 } 287 if (rr != 0) { 288 rr->wait = 0; 289 if (rw->reader_blocked > 0) 290 rw->reader_blocked--; 291 #ifdef NISDB_MT_DEBUG 292 else 293 abort(); 294 #endif /* NISDB_MT_DEBUG */ 295 } 296 } 297 298 /* OK to grab the write lock */ 299 rw->writer.id = myself; 300 /* Increment lock depth */ 301 rw->writer.count++; 302 /* Set number of writers (doesn't increase with lock depth) */ 303 if (rw->writer_count == 0) 304 rw->writer_count = 1; 305 306 return (mutex_unlock(&rw->mutex)); 307 } 308 309 int 310 __nisdb_wlock(__nisdb_rwlock_t *rw) { 311 return (__nisdb_wlock_trylock(rw, 0)); 312 } 313 314 315 static __nisdb_rl_t * 316 increment_reader(pthread_t id, __nisdb_rwlock_t *rw) { 317 318 __nisdb_rl_t *rr; 319 320 for (rr = &rw->reader; rr != 0; rr = rr->next) { 321 if (rr->id == id || rr->id == INV_PTHREAD_ID) 322 break; 323 } 324 if (rw->reader_count == 0 && rr == &rw->reader) { 325 /* No previous reader */ 326 rr->id = id; 327 rw->reader_count = 1; 328 } else if (rr == 0) { 329 if ((rr = malloc(sizeof (__nisdb_rl_t))) == 0) 330 return (0); 331 rr->id = id; 332 rr->count = 0; 333 /* 334 * For insertion simplicity, make it the second item 335 * on the list. 336 */ 337 rr->next = rw->reader.next; 338 rw->reader.next = rr; 339 rw->reader_count++; 340 } 341 rr->count++; 342 343 return (rr); 344 } 345 346 347 int 348 __nisdb_rlock(__nisdb_rwlock_t *rw) { 349 350 int ret; 351 pthread_t myself = pthread_self(); 352 __nisdb_rl_t *rr; 353 354 if (rw == 0) { 355 #ifdef NISDB_MT_DEBUG 356 /* This shouldn't happen */ 357 abort(); 358 #endif /* NISDB_MT_DEBUG */ 359 return (EFAULT); 360 } 361 362 if (rw->destroyed != 0) 363 return (ESHUTDOWN); 364 365 if (rw->force_write) 366 return (__nisdb_wlock(rw)); 367 368 if ((ret = mutex_lock(&rw->mutex)) != 0) 369 return (ret); 370 371 if (rw->destroyed != 0) { 372 (void) mutex_unlock(&rw->mutex); 373 return (ESHUTDOWN); 374 } 375 376 rr = find_reader(myself, rw); 377 378 /* Wait for writer to complete; writer == myself also OK */ 379 while (rw->writer_count > 0 && rw->writer.id != myself) { 380 if (rr != 0) { 381 rr->wait = 1; 382 rw->reader_blocked++; 383 } 384 if ((ret = cond_wait(&rw->cv, &rw->mutex)) != 0) { 385 if (rr != 0) { 386 rr->wait = 0; 387 if (rw->reader_blocked > 0) 388 rw->reader_blocked--; 389 #ifdef NISDB_MT_DEBUG 390 else 391 abort(); 392 #endif /* NISDB_MT_DEBUG */ 393 } 394 (void) mutex_unlock(&rw->mutex); 395 return (ret); 396 } 397 if (rr != 0) { 398 rr->wait = 0; 399 if (rw->reader_blocked > 0) 400 rw->reader_blocked--; 401 #ifdef NISDB_MT_DEBUG 402 else 403 abort(); 404 #endif /* NISDB_MT_DEBUG */ 405 } 406 } 407 408 rr = increment_reader(myself, rw); 409 ret = mutex_unlock(&rw->mutex); 410 return ((rr == 0) ? ENOMEM : ret); 411 } 412 413 414 int 415 __nisdb_wulock(__nisdb_rwlock_t *rw) { 416 417 int ret; 418 pthread_t myself = pthread_self(); 419 420 if (rw == 0) { 421 #ifdef NISDB_MT_DEBUG 422 /* This shouldn't happen */ 423 abort(); 424 #endif /* NISDB_MT_DEBUG */ 425 return (EFAULT); 426 } 427 428 if (rw->destroyed != 0) 429 return (ESHUTDOWN); 430 431 if ((ret = mutex_lock(&rw->mutex)) != 0) 432 return (ret); 433 434 if (rw->destroyed != 0) { 435 (void) mutex_unlock(&rw->mutex); 436 return (ESHUTDOWN); 437 } 438 439 /* Sanity check */ 440 if (rw->writer_count == 0 || 441 rw->writer.id != myself || rw->writer.count == 0) { 442 #ifdef NISDB_MT_DEBUG 443 abort(); 444 #endif /* NISDB_MT_DEBUG */ 445 (void) mutex_unlock(&rw->mutex); 446 return (ENOLCK); 447 } 448 449 rw->writer.count--; 450 if (rw->writer.count == 0) { 451 rw->writer.id = INV_PTHREAD_ID; 452 rw->writer_count = 0; 453 if ((ret = cond_broadcast(&rw->cv)) != 0) { 454 (void) mutex_unlock(&rw->mutex); 455 return (ret); 456 } 457 } 458 459 return (mutex_unlock(&rw->mutex)); 460 } 461 462 463 int 464 __nisdb_rulock(__nisdb_rwlock_t *rw) { 465 466 int ret; 467 pthread_t myself = pthread_self(); 468 __nisdb_rl_t *rr, *prev; 469 470 if (rw == 0) { 471 #ifdef NISDB_MT_DEBUG 472 abort(); 473 #endif /* NISDB_MT_DEBUG */ 474 return (EFAULT); 475 } 476 477 if (rw->destroyed != 0) 478 return (ESHUTDOWN); 479 480 if (rw->force_write) 481 return (__nisdb_wulock(rw)); 482 483 if ((ret = mutex_lock(&rw->mutex)) != 0) 484 return (ret); 485 486 if (rw->destroyed != 0) { 487 (void) mutex_unlock(&rw->mutex); 488 return (ESHUTDOWN); 489 } 490 491 /* Sanity check */ 492 if (rw->reader_count == 0 || 493 (rw->writer_count > 0 && rw->writer.id != myself)) { 494 #ifdef NISDB_MT_DEBUG 495 abort(); 496 #endif /* NISDB_MT_DEBUG */ 497 (void) mutex_unlock(&rw->mutex); 498 return (ENOLCK); 499 } 500 501 /* Find the reader record */ 502 for (rr = &rw->reader, prev = 0; rr != 0; prev = rr, rr = rr->next) { 503 if (rr->id == myself) 504 break; 505 } 506 507 if (rr == 0 || rr->count == 0) { 508 #ifdef NISDB_MT_DEBUG 509 abort(); 510 #endif /* NISDB_MT_DEBUG */ 511 (void) mutex_unlock(&rw->mutex); 512 return (ENOLCK); 513 } 514 515 rr->count--; 516 if (rr->count == 0) { 517 if (rr != &rw->reader) { 518 /* Remove item from list and free it */ 519 prev->next = rr->next; 520 free(rr); 521 } else { 522 /* 523 * First record: copy second to first, and free second 524 * record. 525 */ 526 if (rr->next != 0) { 527 rr = rr->next; 528 rw->reader.id = rr->id; 529 rw->reader.count = rr->count; 530 rw->reader.next = rr->next; 531 free(rr); 532 } else { 533 /* Decomission the first record */ 534 rr->id = INV_PTHREAD_ID; 535 } 536 } 537 rw->reader_count--; 538 } 539 540 /* If there are no readers, wake up any waiting writer */ 541 if (rw->reader_count == 0) { 542 if ((ret = cond_broadcast(&rw->cv)) != 0) { 543 (void) mutex_unlock(&rw->mutex); 544 return (ret); 545 } 546 } 547 548 return (mutex_unlock(&rw->mutex)); 549 } 550 551 552 /* Return zero if write lock held by this thread, non-zero otherwise */ 553 int 554 __nisdb_assert_wheld(__nisdb_rwlock_t *rw) { 555 556 int ret; 557 558 559 if (rw == 0) { 560 #ifdef NISDB_MT_DEBUG 561 abort(); 562 #endif /* NISDB_MT_DEBUG */ 563 return (EFAULT); 564 } 565 566 if (rw->destroyed != 0) 567 return (ESHUTDOWN); 568 569 if ((ret = mutex_lock(&rw->mutex)) != 0) 570 return (ret); 571 572 if (rw->destroyed != 0) { 573 (void) mutex_unlock(&rw->mutex); 574 return (ESHUTDOWN); 575 } 576 577 if (rw->writer_count == 0 || rw->writer.id != pthread_self()) { 578 ret = mutex_unlock(&rw->mutex); 579 return ((ret == 0) ? -1 : ret); 580 } 581 582 /* 583 * We're holding the lock, so we should return zero. Since 584 * that's what mutex_unlock() does if it succeeds, we just 585 * return the value of mutex_unlock(). 586 */ 587 return (mutex_unlock(&rw->mutex)); 588 } 589 590 591 /* Return zero if read lock held by this thread, non-zero otherwise */ 592 int 593 __nisdb_assert_rheld(__nisdb_rwlock_t *rw) { 594 595 int ret; 596 pthread_t myself = pthread_self(); 597 __nisdb_rl_t *rr; 598 599 600 if (rw == 0) { 601 #ifdef NISDB_MT_DEBUG 602 abort(); 603 #endif /* NISDB_MT_DEBUG */ 604 return (EFAULT); 605 } 606 607 if (rw->destroyed != 0) 608 return (ESHUTDOWN); 609 610 if (rw->force_write) 611 return (__nisdb_assert_wheld(rw)); 612 613 if ((ret = mutex_lock(&rw->mutex)) != 0) 614 return (ret); 615 616 if (rw->destroyed != 0) { 617 (void) mutex_unlock(&rw->mutex); 618 return (ESHUTDOWN); 619 } 620 621 /* Write lock also OK */ 622 if (rw->writer_count > 0 && rw->writer.id == myself) { 623 (void) mutex_unlock(&rw->mutex); 624 return (0); 625 } 626 627 if (rw->reader_count == 0) { 628 (void) mutex_unlock(&rw->mutex); 629 return (EBUSY); 630 } 631 632 rr = &rw->reader; 633 do { 634 if (rr->id == myself) { 635 (void) mutex_unlock(&rw->mutex); 636 return (0); 637 } 638 rr = rr->next; 639 } while (rr != 0); 640 641 ret = mutex_unlock(&rw->mutex); 642 return ((ret == 0) ? EBUSY : ret); 643 } 644 645 646 int 647 __nisdb_destroy_lock(__nisdb_rwlock_t *rw) { 648 649 int ret; 650 pthread_t myself = pthread_self(); 651 __nisdb_rl_t *rr; 652 653 654 if (rw == 0) { 655 #ifdef NISDB_MT_DEBUG 656 abort(); 657 #endif /* NISDB_MT_DEBUG */ 658 return (EFAULT); 659 } 660 661 if (rw->destroyed != 0) 662 return (ESHUTDOWN); 663 664 if ((ret = mutex_lock(&rw->mutex)) != 0) 665 return (ret); 666 667 if (rw->destroyed != 0) { 668 (void) mutex_unlock(&rw->mutex); 669 return (ESHUTDOWN); 670 } 671 672 /* 673 * Only proceed if if there are neither readers nor writers 674 * other than this thread. Also, no nested locks may be in 675 * effect. 676 */ 677 if ((rw->writer_count > 0 && 678 (rw->writer.id != myself || rw->writer.count != 1) || 679 (rw->reader_count > 0 && 680 !(rw->reader_count == 1 && rw->reader.id == myself && 681 rw->reader.count == 1))) || 682 (rw->writer_count > 0 && rw->reader_count > 0)) { 683 #ifdef NISDB_MT_DEBUG 684 abort(); 685 #endif /* NISDB_MT_DEBUG */ 686 (void) mutex_unlock(&rw->mutex); 687 return (ENOLCK); 688 } 689 690 /* 691 * Mark lock destroyed, so that any thread waiting on the mutex 692 * will know what's what. Of course, this is a bit iffy, since 693 * we're probably being called from a destructor, and the structure 694 * where we live will soon cease to exist (i.e., be freed and 695 * perhaps re-used). Still, we can only do our best, and give 696 * those other threads the best chance possible. 697 */ 698 rw->destroyed++; 699 700 return (mutex_unlock(&rw->mutex)); 701 } 702 703 void 704 __nisdb_lock_report(__nisdb_rwlock_t *rw) { 705 char *myself = "__nisdb_lock_report"; 706 707 if (rw == 0) { 708 printf("%s: NULL argument\n", myself); 709 return; 710 } 711 712 if (rw->destroyed) 713 printf("0x%x: DESTROYED\n", rw); 714 715 printf("0x%x: Read locking %s\n", 716 rw, rw->force_write ? "disallowed" : "allowed"); 717 718 if (rw->writer_count == 0) 719 printf("0x%x: No writer\n", rw); 720 else if (rw->writer_count == 1) { 721 printf("0x%x: Write locked by %d, depth = %d\n", 722 rw, rw->writer.id, rw->writer.count); 723 if (rw->writer.wait) 724 printf("0x%x:\tWriter blocked\n", rw); 725 } else 726 printf("0x%x: Invalid writer count = %d\n", 727 rw, rw->writer_count); 728 729 if (rw->reader_count == 0) 730 printf("0x%x: No readers\n", rw); 731 else { 732 __nisdb_rl_t *r; 733 734 printf("0x%x: %d readers, %d blocked\n", 735 rw, rw->reader_count, rw->reader_blocked); 736 for (r = &rw->reader; r != 0; r = r->next) { 737 printf("0x%x:\tthread %d, depth = %d%s\n", 738 rw, r->id, r->count, 739 (r->wait ? " (blocked)" : "")); 740 } 741 } 742 }