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