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
41int
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
89static __nisdb_rl_t *
90find_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
107int
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
139int
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
168int
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
309int
310__nisdb_wlock(__nisdb_rwlock_t *rw) {
311	return (__nisdb_wlock_trylock(rw, 0));
312}
313
314
315static __nisdb_rl_t *
316increment_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
347int
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
414int
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
463int
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 */
553int
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 */
592int
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
646int
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
703void
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