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 }