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
__nisdb_rwinit(__nisdb_rwlock_t * rw)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 *
find_reader(pthread_t id,__nisdb_rwlock_t * rw)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
__nisdb_rw_readlock_ok(__nisdb_rwlock_t * rw)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
__nisdb_rw_force_writelock(__nisdb_rwlock_t * rw)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
__nisdb_wlock_trylock(__nisdb_rwlock_t * rw,int trylock)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
__nisdb_wlock(__nisdb_rwlock_t * rw)310 __nisdb_wlock(__nisdb_rwlock_t *rw) {
311 return (__nisdb_wlock_trylock(rw, 0));
312 }
313
314
315 static __nisdb_rl_t *
increment_reader(pthread_t id,__nisdb_rwlock_t * rw)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
__nisdb_rlock(__nisdb_rwlock_t * rw)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
__nisdb_wulock(__nisdb_rwlock_t * rw)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
__nisdb_rulock(__nisdb_rwlock_t * rw)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
__nisdb_assert_wheld(__nisdb_rwlock_t * rw)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
__nisdb_assert_rheld(__nisdb_rwlock_t * rw)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
__nisdb_destroy_lock(__nisdb_rwlock_t * rw)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
__nisdb_lock_report(__nisdb_rwlock_t * rw)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 }
743