5910 libnisdb won't build with modern GCC
[unleashed.git] / usr / src / lib / libnisdb / nisdb_rw.c
blob2e88f0dfd336c8af17d51bbc43780839400b4236
1 /*
2 * CDDL HEADER START
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.
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]
20 * CDDL HEADER END
23 * Copyright 2015 Gary Mills
24 * Copyright (c) 2001 by Sun Microsystems, Inc.
25 * All rights reserved.
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"
36 * Nesting-safe RW locking functions. Return 0 when successful, an
37 * error number from the E-series when not.
40 int
41 __nisdb_rwinit(__nisdb_rwlock_t *rw) {
43 int ret;
45 if (rw == 0) {
46 #ifdef NISDB_MT_DEBUG
47 abort();
48 #endif /* NISDB_MT_DEBUG */
49 return (EFAULT);
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;
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
73 * the thread), and
74 * (b) After every call to a function that might have
75 * acquired the lock.
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;
84 return (0);
88 static __nisdb_rl_t *
89 find_reader(pthread_t id, __nisdb_rwlock_t *rw) {
91 __nisdb_rl_t *rr;
93 for (rr = &rw->reader; rr != 0; rr = rr->next) {
94 if (rr->id == INV_PTHREAD_ID) {
95 rr = 0;
96 break;
98 if (rr->id == id)
99 break;
102 return (rr);
107 __nisdb_rw_readlock_ok(__nisdb_rwlock_t *rw) {
108 int ret;
110 if (rw == 0)
111 return (EFAULT);
113 if (rw->destroyed != 0)
114 return (ESHUTDOWN);
116 if ((ret = mutex_lock(&rw->mutex)) != 0)
117 return (ret);
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) {
124 rw->force_write = 0;
125 ret = 0;
126 } else {
127 ret = EBUSY;
130 (void) mutex_unlock(&rw->mutex);
132 return (ret);
137 __nisdb_rw_force_writelock(__nisdb_rwlock_t *rw) {
138 int ret;
140 if (rw == 0 || rw->destroyed != 0)
141 return (ESHUTDOWN);
143 if ((ret = mutex_lock(&rw->mutex)) != 0)
144 return (ret);
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) {
151 rw->force_write = 1;
152 ret = 0;
153 } else {
154 ret = EBUSY;
157 (void) mutex_unlock(&rw->mutex);
159 return (ret);
164 __nisdb_wlock_trylock(__nisdb_rwlock_t *rw, int trylock) {
166 int ret;
167 pthread_t myself = pthread_self();
168 int all_readers_blocked = 0;
169 __nisdb_rl_t *rr = 0;
171 if (rw == 0) {
172 #ifdef NISDB_MT_DEBUG
173 /* This shouldn't happen */
174 abort();
175 #endif /* NISDB_MT_DEBUG */
176 return (EFAULT);
179 if (rw->destroyed != 0)
180 return (ESHUTDOWN);
182 if ((ret = mutex_lock(&rw->mutex)) != 0)
183 return (ret);
185 if (rw->destroyed != 0) {
186 (void) mutex_unlock(&rw->mutex);
187 return (ESHUTDOWN);
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) {
204 if (rr->count) {
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;
214 } else {
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.
221 if (rw->reader_count == rw->reader_blocked)
222 all_readers_blocked = 1;
227 /* Wait for reader(s) or writer to finish */
228 while (1) {
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
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;
246 * Provided that all readers are blocked on the mutex
247 * we break a potential dead-lock by acquiring the
248 * write lock.
250 if (all_readers_blocked) {
251 if (rw->writer_count == 0 || rw->writer.id == myself) {
252 break;
257 * If 'trylock' is set, tell the caller that we'd have to
258 * block to obtain the lock.
260 if (trylock) {
261 (void) mutex_unlock(&rw->mutex);
262 return (EBUSY);
265 /* If we're also a reader, indicate that we're blocking */
266 if (rr != 0) {
267 rr->wait = 1;
268 rw->reader_blocked++;
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 */
280 (void) mutex_unlock(&rw->mutex);
281 return (ret);
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 */
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;
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) {
314 __nisdb_rl_t *rr;
316 for (rr = &rw->reader; rr != 0; rr = rr->next) {
317 if (rr->id == id || rr->id == INV_PTHREAD_ID)
318 break;
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;
330 * For insertion simplicity, make it the second item
331 * on the list.
333 rr->next = rw->reader.next;
334 rw->reader.next = rr;
335 rw->reader_count++;
337 rr->count++;
339 return (rr);
344 __nisdb_rlock(__nisdb_rwlock_t *rw) {
346 int ret;
347 pthread_t myself = pthread_self();
348 __nisdb_rl_t *rr;
350 if (rw == 0) {
351 #ifdef NISDB_MT_DEBUG
352 /* This shouldn't happen */
353 abort();
354 #endif /* NISDB_MT_DEBUG */
355 return (EFAULT);
358 if (rw->destroyed != 0)
359 return (ESHUTDOWN);
361 if (rw->force_write)
362 return (__nisdb_wlock(rw));
364 if ((ret = mutex_lock(&rw->mutex)) != 0)
365 return (ret);
367 if (rw->destroyed != 0) {
368 (void) mutex_unlock(&rw->mutex);
369 return (ESHUTDOWN);
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) {
376 if (rr != 0) {
377 rr->wait = 1;
378 rw->reader_blocked++;
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 */
390 (void) mutex_unlock(&rw->mutex);
391 return (ret);
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 */
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) {
413 int ret;
414 pthread_t myself = pthread_self();
416 if (rw == 0) {
417 #ifdef NISDB_MT_DEBUG
418 /* This shouldn't happen */
419 abort();
420 #endif /* NISDB_MT_DEBUG */
421 return (EFAULT);
424 if (rw->destroyed != 0)
425 return (ESHUTDOWN);
427 if ((ret = mutex_lock(&rw->mutex)) != 0)
428 return (ret);
430 if (rw->destroyed != 0) {
431 (void) mutex_unlock(&rw->mutex);
432 return (ESHUTDOWN);
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);
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);
455 return (mutex_unlock(&rw->mutex));
460 __nisdb_rulock(__nisdb_rwlock_t *rw) {
462 int ret;
463 pthread_t myself = pthread_self();
464 __nisdb_rl_t *rr, *prev;
466 if (rw == 0) {
467 #ifdef NISDB_MT_DEBUG
468 abort();
469 #endif /* NISDB_MT_DEBUG */
470 return (EFAULT);
473 if (rw->destroyed != 0)
474 return (ESHUTDOWN);
476 if (rw->force_write)
477 return (__nisdb_wulock(rw));
479 if ((ret = mutex_lock(&rw->mutex)) != 0)
480 return (ret);
482 if (rw->destroyed != 0) {
483 (void) mutex_unlock(&rw->mutex);
484 return (ESHUTDOWN);
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);
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;
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);
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 {
519 * First record: copy second to first, and free second
520 * record.
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;
533 rw->reader_count--;
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);
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) {
552 int ret;
555 if (rw == 0) {
556 #ifdef NISDB_MT_DEBUG
557 abort();
558 #endif /* NISDB_MT_DEBUG */
559 return (EFAULT);
562 if (rw->destroyed != 0)
563 return (ESHUTDOWN);
565 if ((ret = mutex_lock(&rw->mutex)) != 0)
566 return (ret);
568 if (rw->destroyed != 0) {
569 (void) mutex_unlock(&rw->mutex);
570 return (ESHUTDOWN);
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) {
591 int ret;
592 pthread_t myself = pthread_self();
593 __nisdb_rl_t *rr;
596 if (rw == 0) {
597 #ifdef NISDB_MT_DEBUG
598 abort();
599 #endif /* NISDB_MT_DEBUG */
600 return (EFAULT);
603 if (rw->destroyed != 0)
604 return (ESHUTDOWN);
606 if (rw->force_write)
607 return (__nisdb_assert_wheld(rw));
609 if ((ret = mutex_lock(&rw->mutex)) != 0)
610 return (ret);
612 if (rw->destroyed != 0) {
613 (void) mutex_unlock(&rw->mutex);
614 return (ESHUTDOWN);
617 /* Write lock also OK */
618 if (rw->writer_count > 0 && rw->writer.id == myself) {
619 (void) mutex_unlock(&rw->mutex);
620 return (0);
623 if (rw->reader_count == 0) {
624 (void) mutex_unlock(&rw->mutex);
625 return (EBUSY);
628 rr = &rw->reader;
629 do {
630 if (rr->id == myself) {
631 (void) mutex_unlock(&rw->mutex);
632 return (0);
634 rr = rr->next;
635 } while (rr != 0);
637 ret = mutex_unlock(&rw->mutex);
638 return ((ret == 0) ? EBUSY : ret);
643 __nisdb_destroy_lock(__nisdb_rwlock_t *rw) {
645 int ret;
646 pthread_t myself = pthread_self();
649 if (rw == 0) {
650 #ifdef NISDB_MT_DEBUG
651 abort();
652 #endif /* NISDB_MT_DEBUG */
653 return (EFAULT);
656 if (rw->destroyed != 0)
657 return (ESHUTDOWN);
659 if ((ret = mutex_lock(&rw->mutex)) != 0)
660 return (ret);
662 if (rw->destroyed != 0) {
663 (void) mutex_unlock(&rw->mutex);
664 return (ESHUTDOWN);
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.
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);
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.
693 rw->destroyed++;
695 return (mutex_unlock(&rw->mutex));
698 void
699 __nisdb_lock_report(__nisdb_rwlock_t *rw) {
700 char *myself = "__nisdb_lock_report";
702 if (rw == 0) {
703 printf("%s: NULL argument\n", myself);
704 return;
707 if (rw->destroyed)
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);
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);
724 if (rw->reader_count == 0)
725 printf("0x%x: No readers\n", rw);
726 else {
727 __nisdb_rl_t *r;
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)" : ""));