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