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 }