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
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.
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]
23 * Copyright 2015 Gary Mills
24 * Copyright (c) 2001 by Sun Microsystems, Inc.
25 * All rights reserved.
29 #include <rpc/types.h>
31 #include "db_dictionary_c.h"
33 #include "nisdb_ldap.h"
36 * Nesting-safe RW locking functions. Return 0 when successful, an
37 * error number from the E-series when not.
41 __nisdb_rwinit(__nisdb_rwlock_t
*rw
) {
48 #endif /* NISDB_MT_DEBUG */
52 if ((ret
= mutex_init(&rw
->mutex
, USYNC_THREAD
, 0)) != 0)
54 if ((ret
= cond_init(&rw
->cv
, USYNC_THREAD
, 0)) != 0)
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.
65 * There are two conditions under which it is safe to set
66 * 'force_write' to zero for a certain lock structure:
68 * (1) The lock will never be subject to migration, or
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
74 * (b) After every call to a function that might have
77 rw
->force_write
= NISDB_FORCE_WRITE
;
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;
89 find_reader(pthread_t id
, __nisdb_rwlock_t
*rw
) {
93 for (rr
= &rw
->reader
; rr
!= 0; rr
= rr
->next
) {
94 if (rr
->id
== INV_PTHREAD_ID
) {
107 __nisdb_rw_readlock_ok(__nisdb_rwlock_t
*rw
) {
113 if (rw
->destroyed
!= 0)
116 if ((ret
= mutex_lock(&rw
->mutex
)) != 0)
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.
123 if (rw
->destroyed
== 0 && rw
->reader_count
== 0) {
130 (void) mutex_unlock(&rw
->mutex
);
137 __nisdb_rw_force_writelock(__nisdb_rwlock_t
*rw
) {
140 if (rw
== 0 || rw
->destroyed
!= 0)
143 if ((ret
= mutex_lock(&rw
->mutex
)) != 0)
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.
150 if (rw
->destroyed
== 0 && rw
->reader_count
== 0) {
157 (void) mutex_unlock(&rw
->mutex
);
164 __nisdb_wlock_trylock(__nisdb_rwlock_t
*rw
, int trylock
) {
167 pthread_t myself
= pthread_self();
168 int all_readers_blocked
= 0;
169 __nisdb_rl_t
*rr
= 0;
172 #ifdef NISDB_MT_DEBUG
173 /* This shouldn't happen */
175 #endif /* NISDB_MT_DEBUG */
179 if (rw
->destroyed
!= 0)
182 if ((ret
= mutex_lock(&rw
->mutex
)) != 0)
185 if (rw
->destroyed
!= 0) {
186 (void) mutex_unlock(&rw
->mutex
);
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
));
199 * Need to know if we're holding a read lock already, and if
200 * all other readers are blocked waiting for the mutex.
202 if (rw
->reader_count
> 0) {
203 if ((rr
= find_reader(myself
, rw
)) != 0) {
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.
211 if (rw
->reader_count
==
212 (rw
->reader_blocked
+ 1))
213 all_readers_blocked
= 1;
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
221 if (rw
->reader_count
== rw
->reader_blocked
)
222 all_readers_blocked
= 1;
227 /* Wait for reader(s) or writer to finish */
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:
234 * - One reader, and it's us
235 * - N readers, but all blocked on the mutex
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
))) {
246 * Provided that all readers are blocked on the mutex
247 * we break a potential dead-lock by acquiring the
250 if (all_readers_blocked
) {
251 if (rw
->writer_count
== 0 || rw
->writer
.id
== myself
) {
257 * If 'trylock' is set, tell the caller that we'd have to
258 * block to obtain the lock.
261 (void) mutex_unlock(&rw
->mutex
);
265 /* If we're also a reader, indicate that we're blocking */
268 rw
->reader_blocked
++;
270 if ((ret
= cond_wait(&rw
->cv
, &rw
->mutex
)) != 0) {
273 if (rw
->reader_blocked
> 0)
274 rw
->reader_blocked
--;
275 #ifdef NISDB_MT_DEBUG
278 #endif /* NISDB_MT_DEBUG */
280 (void) mutex_unlock(&rw
->mutex
);
285 if (rw
->reader_blocked
> 0)
286 rw
->reader_blocked
--;
287 #ifdef NISDB_MT_DEBUG
290 #endif /* NISDB_MT_DEBUG */
294 /* OK to grab the write lock */
295 rw
->writer
.id
= myself
;
296 /* Increment lock depth */
298 /* Set number of writers (doesn't increase with lock depth) */
299 if (rw
->writer_count
== 0)
300 rw
->writer_count
= 1;
302 return (mutex_unlock(&rw
->mutex
));
306 __nisdb_wlock(__nisdb_rwlock_t
*rw
) {
307 return (__nisdb_wlock_trylock(rw
, 0));
311 static __nisdb_rl_t
*
312 increment_reader(pthread_t id
, __nisdb_rwlock_t
*rw
) {
316 for (rr
= &rw
->reader
; rr
!= 0; rr
= rr
->next
) {
317 if (rr
->id
== id
|| rr
->id
== INV_PTHREAD_ID
)
320 if (rw
->reader_count
== 0 && rr
== &rw
->reader
) {
321 /* No previous reader */
323 rw
->reader_count
= 1;
324 } else if (rr
== 0) {
325 if ((rr
= malloc(sizeof (__nisdb_rl_t
))) == 0)
330 * For insertion simplicity, make it the second item
333 rr
->next
= rw
->reader
.next
;
334 rw
->reader
.next
= rr
;
344 __nisdb_rlock(__nisdb_rwlock_t
*rw
) {
347 pthread_t myself
= pthread_self();
351 #ifdef NISDB_MT_DEBUG
352 /* This shouldn't happen */
354 #endif /* NISDB_MT_DEBUG */
358 if (rw
->destroyed
!= 0)
362 return (__nisdb_wlock(rw
));
364 if ((ret
= mutex_lock(&rw
->mutex
)) != 0)
367 if (rw
->destroyed
!= 0) {
368 (void) mutex_unlock(&rw
->mutex
);
372 rr
= find_reader(myself
, rw
);
374 /* Wait for writer to complete; writer == myself also OK */
375 while (rw
->writer_count
> 0 && rw
->writer
.id
!= myself
) {
378 rw
->reader_blocked
++;
380 if ((ret
= cond_wait(&rw
->cv
, &rw
->mutex
)) != 0) {
383 if (rw
->reader_blocked
> 0)
384 rw
->reader_blocked
--;
385 #ifdef NISDB_MT_DEBUG
388 #endif /* NISDB_MT_DEBUG */
390 (void) mutex_unlock(&rw
->mutex
);
395 if (rw
->reader_blocked
> 0)
396 rw
->reader_blocked
--;
397 #ifdef NISDB_MT_DEBUG
400 #endif /* NISDB_MT_DEBUG */
404 rr
= increment_reader(myself
, rw
);
405 ret
= mutex_unlock(&rw
->mutex
);
406 return ((rr
== 0) ? ENOMEM
: ret
);
411 __nisdb_wulock(__nisdb_rwlock_t
*rw
) {
414 pthread_t myself
= pthread_self();
417 #ifdef NISDB_MT_DEBUG
418 /* This shouldn't happen */
420 #endif /* NISDB_MT_DEBUG */
424 if (rw
->destroyed
!= 0)
427 if ((ret
= mutex_lock(&rw
->mutex
)) != 0)
430 if (rw
->destroyed
!= 0) {
431 (void) mutex_unlock(&rw
->mutex
);
436 if (rw
->writer_count
== 0 ||
437 rw
->writer
.id
!= myself
|| rw
->writer
.count
== 0) {
438 #ifdef NISDB_MT_DEBUG
440 #endif /* NISDB_MT_DEBUG */
441 (void) mutex_unlock(&rw
->mutex
);
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
);
455 return (mutex_unlock(&rw
->mutex
));
460 __nisdb_rulock(__nisdb_rwlock_t
*rw
) {
463 pthread_t myself
= pthread_self();
464 __nisdb_rl_t
*rr
, *prev
;
467 #ifdef NISDB_MT_DEBUG
469 #endif /* NISDB_MT_DEBUG */
473 if (rw
->destroyed
!= 0)
477 return (__nisdb_wulock(rw
));
479 if ((ret
= mutex_lock(&rw
->mutex
)) != 0)
482 if (rw
->destroyed
!= 0) {
483 (void) mutex_unlock(&rw
->mutex
);
488 if (rw
->reader_count
== 0 ||
489 (rw
->writer_count
> 0 && rw
->writer
.id
!= myself
)) {
490 #ifdef NISDB_MT_DEBUG
492 #endif /* NISDB_MT_DEBUG */
493 (void) mutex_unlock(&rw
->mutex
);
497 /* Find the reader record */
498 for (rr
= &rw
->reader
, prev
= 0; rr
!= 0; prev
= rr
, rr
= rr
->next
) {
499 if (rr
->id
== myself
)
503 if (rr
== 0 || rr
->count
== 0) {
504 #ifdef NISDB_MT_DEBUG
506 #endif /* NISDB_MT_DEBUG */
507 (void) mutex_unlock(&rw
->mutex
);
512 if (rr
->count
== 0) {
513 if (rr
!= &rw
->reader
) {
514 /* Remove item from list and free it */
515 prev
->next
= rr
->next
;
519 * First record: copy second to first, and free second
524 rw
->reader
.id
= rr
->id
;
525 rw
->reader
.count
= rr
->count
;
526 rw
->reader
.next
= rr
->next
;
529 /* Decomission the first record */
530 rr
->id
= INV_PTHREAD_ID
;
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
);
544 return (mutex_unlock(&rw
->mutex
));
548 /* Return zero if write lock held by this thread, non-zero otherwise */
550 __nisdb_assert_wheld(__nisdb_rwlock_t
*rw
) {
556 #ifdef NISDB_MT_DEBUG
558 #endif /* NISDB_MT_DEBUG */
562 if (rw
->destroyed
!= 0)
565 if ((ret
= mutex_lock(&rw
->mutex
)) != 0)
568 if (rw
->destroyed
!= 0) {
569 (void) mutex_unlock(&rw
->mutex
);
573 if (rw
->writer_count
== 0 || rw
->writer
.id
!= pthread_self()) {
574 ret
= mutex_unlock(&rw
->mutex
);
575 return ((ret
== 0) ? -1 : ret
);
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().
583 return (mutex_unlock(&rw
->mutex
));
587 /* Return zero if read lock held by this thread, non-zero otherwise */
589 __nisdb_assert_rheld(__nisdb_rwlock_t
*rw
) {
592 pthread_t myself
= pthread_self();
597 #ifdef NISDB_MT_DEBUG
599 #endif /* NISDB_MT_DEBUG */
603 if (rw
->destroyed
!= 0)
607 return (__nisdb_assert_wheld(rw
));
609 if ((ret
= mutex_lock(&rw
->mutex
)) != 0)
612 if (rw
->destroyed
!= 0) {
613 (void) mutex_unlock(&rw
->mutex
);
617 /* Write lock also OK */
618 if (rw
->writer_count
> 0 && rw
->writer
.id
== myself
) {
619 (void) mutex_unlock(&rw
->mutex
);
623 if (rw
->reader_count
== 0) {
624 (void) mutex_unlock(&rw
->mutex
);
630 if (rr
->id
== myself
) {
631 (void) mutex_unlock(&rw
->mutex
);
637 ret
= mutex_unlock(&rw
->mutex
);
638 return ((ret
== 0) ? EBUSY
: ret
);
643 __nisdb_destroy_lock(__nisdb_rwlock_t
*rw
) {
646 pthread_t myself
= pthread_self();
650 #ifdef NISDB_MT_DEBUG
652 #endif /* NISDB_MT_DEBUG */
656 if (rw
->destroyed
!= 0)
659 if ((ret
= mutex_lock(&rw
->mutex
)) != 0)
662 if (rw
->destroyed
!= 0) {
663 (void) mutex_unlock(&rw
->mutex
);
668 * Only proceed if if there are neither readers nor writers
669 * other than this thread. Also, no nested locks may be in
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
680 #endif /* NISDB_MT_DEBUG */
681 (void) mutex_unlock(&rw
->mutex
);
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.
695 return (mutex_unlock(&rw
->mutex
));
699 __nisdb_lock_report(__nisdb_rwlock_t
*rw
) {
700 char *myself
= "__nisdb_lock_report";
703 printf("%s: NULL argument\n", myself
);
708 printf("0x%x: DESTROYED\n", rw
);
710 printf("0x%x: Read locking %s\n",
711 rw
, rw
->force_write
? "disallowed" : "allowed");
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
);
719 printf("0x%x:\tWriter blocked\n", rw
);
721 printf("0x%x: Invalid writer count = %d\n",
722 rw
, rw
->writer_count
);
724 if (rw
->reader_count
== 0)
725 printf("0x%x: No readers\n", rw
);
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",
734 (r
->wait
? " (blocked)" : ""));