xref: /illumos-gate/usr/src/lib/libldap5/sources/ldap/prldap/ldappr-threads.c (revision fc910014e8a32a65612105835a10995f2c13d942)
1 /*
2  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*
7  * The contents of this file are subject to the Netscape Public
8  * License Version 1.1 (the "License"); you may not use this file
9  * except in compliance with the License. You may obtain a copy of
10  * the License at http://www.mozilla.org/NPL/
11  *
12  * Software distributed under the License is distributed on an "AS
13  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
14  * implied. See the License for the specific language governing
15  * rights and limitations under the License.
16  *
17  * The Original Code is Mozilla Communicator client code, released
18  * March 31, 1998.
19  *
20  * The Initial Developer of the Original Code is Netscape
21  * Communications Corporation. Portions created by Netscape are
22  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
23  * Rights Reserved.
24  *
25  * Contributor(s):
26  */
27 
28 /*
29  * Thread callback functions for libldap that use the NSPR (Netscape
30  * Portable Runtime) thread API.
31  *
32  */
33 
34 #ifdef _SOLARIS_SDK
35 #include <thread.h>
36 #include <synch.h>
37 #include <prinit.h>
38 #include <prthread.h>
39 #include <syslog.h>
40 #include <string.h>
41 #include <sys/types.h>
42 #include <signal.h>
43 #include <errno.h>
44 extern int	errno;
45 #endif	/* _SOLARIS_SDK */
46 
47 #include "ldappr-int.h"
48 
49 #ifndef _SOLARIS_SDK
50 /*
51  * Macros:
52  */
53 /*
54  * Grow thread private data arrays 10 elements at a time.
55  */
56 #define PRLDAP_TPD_ARRAY_INCREMENT	10
57 
58 /*
59  * Structures and types:
60  */
61 /*
62  * Structure used by libldap thread callbacks to maintain error information.
63  */
64 typedef struct prldap_errorinfo {
65     int		plei_lderrno;
66     char	*plei_matched;
67     char	*plei_errmsg;
68 } PRLDAP_ErrorInfo;
69 
70 /*
71  * Structure used to maintain thread-private data. At the present time,
72  * only error info. is thread-private.  One of these structures is allocated
73  * for each thread.
74  */
75 typedef struct prldap_tpd_header {
76     int			ptpdh_tpd_count;	/* # of data items allocated */
77     void		**ptpdh_dataitems;	/* array of data items */
78 } PRLDAP_TPDHeader;
79 
80 /*
81  * Structure used by associate a PRLDAP thread-private data index with an
82  * LDAP session handle. One of these exists for each active LDAP session
83  * handle.
84  */
85 typedef struct prldap_tpd_map {
86     LDAP			*prtm_ld;	/* non-NULL if in use */
87     PRUintn			prtm_index;	/* index into TPD array */
88     struct prldap_tpd_map	*prtm_next;
89 } PRLDAP_TPDMap;
90 
91 #ifdef _SOLARIS_SDK
92 extern  mutex_t         inited_mutex;
93 #endif  /* _SOLARIS_SDK */
94 
95 /*
96  * Static Variables:
97  */
98 /*
99  * prldap_map_list points to all of the PRLDAP_TPDMap structures
100  * we have ever allocated.  We recycle them as we open and close LDAP
101  * sessions.
102  */
103 static PRLDAP_TPDMap *prldap_map_list = NULL;
104 
105 
106 /*
107  * The prldap_map_mutex is used to protect access to the prldap_map_list.
108  */
109 static PRLock	*prldap_map_mutex = NULL;
110 
111 /*
112  * The prldap_tpd_maxindex value is used to track the largest TPD array
113  * index we have used.
114  */
115 static PRInt32	prldap_tpd_maxindex = -1;
116 
117 /*
118  * prldap_tpdindex is an NSPR thread private data index we use to
119  * maintain our own thread-private data. It is initialized inside
120  * prldap_init_tpd().
121  */
122 static PRUintn	prldap_tpdindex = 0;
123 
124 /*
125  * The prldap_callonce_init_tpd structure is used by NSPR to ensure
126  * that prldap_init_tpd() is called at most once.
127  */
128 static PRCallOnceType prldap_callonce_init_tpd = { 0, 0, 0 };
129 
130 
131 /*
132  * Private function prototypes:
133  */
134 static void prldap_set_ld_error( int err, char *matched, char *errmsg,
135 	void *errorarg );
136 static int prldap_get_ld_error( char **matchedp, char **errmsgp,
137 	void *errorarg );
138 #endif
139 static void *prldap_mutex_alloc( void );
140 static void prldap_mutex_free( void *mutex );
141 static int prldap_mutex_lock( void *mutex );
142 static int prldap_mutex_unlock( void *mutex );
143 static void *prldap_get_thread_id( void );
144 #ifndef _SOLARIS_SDK
145 static PRStatus prldap_init_tpd( void );
146 static PRLDAP_TPDMap *prldap_allocate_map( LDAP *ld );
147 static void prldap_return_map( PRLDAP_TPDMap *map );
148 static PRUintn prldap_new_tpdindex( void );
149 static int prldap_set_thread_private( PRInt32 tpdindex, void *priv );
150 static void *prldap_get_thread_private( PRInt32 tpdindex );
151 static PRLDAP_TPDHeader *prldap_tsd_realloc( PRLDAP_TPDHeader *tsdhdr,
152 	int maxindex );
153 static void prldap_tsd_destroy( void *priv );
154 #endif
155 
156 
157 /*
158  * Install NSPR thread functions into ld (if ld is NULL, they are installed
159  * as the default functions for new LDAP * handles).
160  *
161  * Returns 0 if all goes well and -1 if not.
162  */
163 int
164 prldap_install_thread_functions( LDAP *ld, int shared )
165 {
166     struct ldap_thread_fns		tfns;
167     struct ldap_extra_thread_fns	xtfns;
168 
169 #ifndef _SOLARIS_SDK
170     if ( PR_CallOnce( &prldap_callonce_init_tpd, prldap_init_tpd )
171 		!= PR_SUCCESS ) {
172 	ldap_set_lderrno( ld, LDAP_LOCAL_ERROR, NULL, NULL );
173 	return( -1 );
174     }
175 #endif	/* _SOLARIS_SDK */
176 
177     /* set thread function pointers */
178     memset( &tfns, '\0', sizeof(struct ldap_thread_fns) );
179     tfns.ltf_get_errno = prldap_get_system_errno;
180     tfns.ltf_set_errno = prldap_set_system_errno;
181     if ( shared ) {
182 	tfns.ltf_mutex_alloc = prldap_mutex_alloc;
183 	tfns.ltf_mutex_free = prldap_mutex_free;
184 	tfns.ltf_mutex_lock = prldap_mutex_lock;
185 	tfns.ltf_mutex_unlock = prldap_mutex_unlock;
186 #ifdef _SOLARIS_SDK
187 	tfns.ltf_get_lderrno = NULL;
188 	tfns.ltf_set_lderrno = NULL;
189 #else
190 	tfns.ltf_get_lderrno = prldap_get_ld_error;
191 	tfns.ltf_set_lderrno = prldap_set_ld_error;
192 	if ( ld != NULL ) {
193 	    /*
194 	     * If this is a real ld (i.e., we are not setting the global
195 	     * defaults) allocate thread private data for error information.
196 	     * If ld is NULL we do not do this here but it is done in
197 	     * prldap_thread_new_handle().
198 	     */
199 	    if (( tfns.ltf_lderrno_arg = (void *)prldap_allocate_map( ld ))
200 		    == NULL ) {
201 		return( -1 );
202 	    }
203 	}
204 #endif
205     }
206 
207     if ( ldap_set_option( ld, LDAP_OPT_THREAD_FN_PTRS,
208 	    (void *)&tfns ) != 0 ) {
209 #ifndef _SOLARIS_SDK
210 	prldap_return_map( (PRLDAP_TPDMap *)tfns.ltf_lderrno_arg );
211 #endif
212 	return( -1 );
213     }
214 
215     /* set extended thread function pointers */
216     memset( &xtfns, '\0', sizeof(struct ldap_extra_thread_fns) );
217     xtfns.ltf_threadid_fn = prldap_get_thread_id;
218     if ( ldap_set_option( ld, LDAP_OPT_EXTRA_THREAD_FN_PTRS,
219 	    (void *)&xtfns ) != 0 ) {
220 	return( -1 );
221     }
222 
223     return( 0 );
224 }
225 
226 
227 static void *
228 prldap_mutex_alloc( void )
229 {
230     return( (void *)PR_NewLock());
231 }
232 
233 
234 static void
235 prldap_mutex_free( void *mutex )
236 {
237     PR_DestroyLock( (PRLock *)mutex );
238 }
239 
240 
241 static int
242 prldap_mutex_lock( void *mutex )
243 {
244     PR_Lock( (PRLock *)mutex );
245     return( 0 );
246 }
247 
248 
249 static int
250 prldap_mutex_unlock( void *mutex )
251 {
252     if ( PR_Unlock( (PRLock *)mutex ) == PR_FAILURE ) {
253 	return( -1 );
254     }
255 
256     return( 0 );
257 }
258 
259 
260 static void *
261 prldap_get_thread_id( void )
262 {
263 #ifdef	_SOLARIS_SDK
264 	return ((void *)thr_self());
265 #else
266     return( (void *)PR_GetCurrentThread());
267 #endif
268 }
269 
270 #ifndef	_SOLARIS_SDK
271 static int
272 prldap_get_ld_error( char **matchedp, char **errmsgp, void *errorarg )
273 {
274     PRLDAP_TPDMap	*map;
275     PRLDAP_ErrorInfo	*eip;
276 
277     if (( map = (PRLDAP_TPDMap *)errorarg ) != NULL && ( eip =
278 	    (PRLDAP_ErrorInfo *)prldap_get_thread_private(
279 	    map->prtm_index )) != NULL ) {
280 	if ( matchedp != NULL ) {
281 	    *matchedp = eip->plei_matched;
282 	}
283 	if ( errmsgp != NULL ) {
284 	    *errmsgp = eip->plei_errmsg;
285 	}
286 	return( eip->plei_lderrno );
287     } else {
288 	if ( matchedp != NULL ) {
289 	    *matchedp = NULL;
290 	}
291 	if ( errmsgp != NULL ) {
292 	    *errmsgp = NULL;
293 	}
294 	return( LDAP_LOCAL_ERROR );	/* punt */
295     }
296 }
297 
298 
299 static void
300 prldap_set_ld_error( int err, char *matched, char *errmsg, void *errorarg )
301 {
302     PRLDAP_TPDMap	*map;
303     PRLDAP_ErrorInfo	*eip;
304 
305     if (( map = (PRLDAP_TPDMap *)errorarg ) != NULL ) {
306 	if (( eip = (PRLDAP_ErrorInfo *)prldap_get_thread_private(
307 		map->prtm_index )) == NULL ) {
308 	    /*
309 	     * Error info. has not yet been allocated for this thread.
310 	     * Do so now.  Note that we free this memory only for the
311 	     * thread that calls prldap_thread_dispose_handle(), which
312 	     * should be the one that called ldap_unbind() -- see
313 	     * prldap_return_map().  Not freeing the memory used by
314 	     * other threads is deemed acceptable since it will be
315 	     * recycled and used by other LDAP sessions.  All of the
316 	     * thread-private memory is freed when a thread exits
317 	     * (inside the prldap_tsd_destroy() function).
318 	     */
319 	    eip = (PRLDAP_ErrorInfo *)PR_Calloc( 1,
320 		    sizeof( PRLDAP_ErrorInfo ));
321 	    if ( eip == NULL ) {
322 		return;	/* punt */
323 	    }
324 	    (void)prldap_set_thread_private( map->prtm_index, eip );
325 	}
326 
327 	eip->plei_lderrno = err;
328 	if ( eip->plei_matched != NULL ) {
329 	    ldap_memfree( eip->plei_matched );
330 	}
331 	eip->plei_matched = matched;
332 	if ( eip->plei_errmsg != NULL ) {
333 	    ldap_memfree( eip->plei_errmsg );
334 	}
335 	eip->plei_errmsg = errmsg;
336     }
337 }
338 #endif
339 
340 
341 /*
342  * Called when a new LDAP * session handle is allocated.
343  * Allocate thread-private data for error information, but only if
344  * it has not already been allocated and the get_ld_error callback has
345  * been installed.  If ld is not NULL when prldap_install_thread_functions()
346  * is called, we will have already allocated the thread-private data there.
347  */
348 int
349 prldap_thread_new_handle( LDAP *ld, void *sessionarg )
350 {
351     struct ldap_thread_fns	tfns;
352 
353 #ifndef _SOLARIS_SDK
354     if ( ldap_get_option( ld, LDAP_OPT_THREAD_FN_PTRS, (void *)&tfns ) != 0 ) {
355 	return( LDAP_LOCAL_ERROR );
356     }
357 
358     if ( tfns.ltf_lderrno_arg == NULL && tfns.ltf_get_lderrno != NULL ) {
359 	if (( tfns.ltf_lderrno_arg = (void *)prldap_allocate_map( ld )) == NULL
360 		|| ldap_set_option( ld, LDAP_OPT_THREAD_FN_PTRS,
361 		(void *)&tfns ) != 0 ) {
362 	    return( LDAP_LOCAL_ERROR );
363 	}
364     }
365 #endif
366 
367     return( LDAP_SUCCESS );
368 }
369 
370 
371 /*
372  * Called when an LDAP * session handle is being destroyed.
373  * Clean up our thread private data map.
374  */
375 void
376 prldap_thread_dispose_handle( LDAP *ld, void *sessionarg )
377 {
378 #ifndef _SOLARIS_SDK
379     struct ldap_thread_fns	tfns;
380 
381     if ( ldap_get_option( ld, LDAP_OPT_THREAD_FN_PTRS,
382 	    (void *)&tfns ) == 0 &&
383 	    tfns.ltf_lderrno_arg != NULL ) {
384 	prldap_return_map( (PRLDAP_TPDMap *)tfns.ltf_lderrno_arg );
385     }
386 #endif
387 }
388 
389 
390 #ifndef _SOLARIS_SDK
391 static PRStatus
392 prldap_init_tpd( void )
393 {
394     if (( prldap_map_mutex = PR_NewLock()) == NULL || PR_NewThreadPrivateIndex(
395 		&prldap_tpdindex, prldap_tsd_destroy ) != PR_SUCCESS ) {
396 	return( PR_FAILURE );
397     }
398 
399     prldap_map_list = NULL;
400 
401     return( PR_SUCCESS );
402 }
403 
404 
405 /*
406  * Function: prldap_allocate_map()
407  * Description: allocate a thread-private data map to use for a new
408  *	LDAP session handle.
409  * Returns: a pointer to the TPD map or NULL if none available.
410  */
411 static PRLDAP_TPDMap *
412 prldap_allocate_map( LDAP *ld )
413 {
414     PRLDAP_TPDMap	*map, *prevmap;
415 
416     PR_Lock( prldap_map_mutex );
417 
418     /*
419      * first look for a map that is already allocated but free to be re-used
420      */
421     prevmap = NULL;
422     for ( map = prldap_map_list; map != NULL; map = map->prtm_next ) {
423 	if ( map->prtm_ld == NULL ) {
424 	    break;
425 	}
426 	prevmap = map;
427     }
428 
429     /*
430      * if none we found (map == NULL), try to allocate a new one and add it
431      * to the end of our global list.
432      */
433     if ( map == NULL ) {
434 	PRUintn	tpdindex;
435 
436 	tpdindex = prldap_new_tpdindex();
437 	map = (PRLDAP_TPDMap *)PR_Malloc( sizeof( PRLDAP_TPDMap ));
438 	if ( map != NULL ) {
439 	    map->prtm_index = tpdindex;
440 	    map->prtm_next = NULL;
441 	    if ( prevmap == NULL ) {
442 		prldap_map_list = map;
443 	    } else {
444 		prevmap->prtm_next = map;
445 	    }
446 	}
447     }
448 
449     if ( map != NULL ) {
450 	map->prtm_ld = ld;	/* now marked as "in use" */
451 				/* since we are reusing...reset */
452 				/* to initial state */
453 	(void)prldap_set_thread_private( map->prtm_index, NULL );
454     }
455 
456     PR_Unlock( prldap_map_mutex );
457 
458     return( map );
459 }
460 
461 
462 /*
463  * Function: prldap_return_map()
464  * Description: return a thread-private data map to the pool of ones
465  *	available for re-use.
466  */
467 static void
468 prldap_return_map( PRLDAP_TPDMap *map )
469 {
470     PRLDAP_ErrorInfo	*eip;
471 
472     PR_Lock( prldap_map_mutex );
473 
474     /*
475      * Dispose of thread-private LDAP error information.  Note that this
476      * only disposes of the memory consumed on THIS thread, but that is
477      * okay.  See the comment in prldap_set_ld_error() for the reason why.
478      */
479     if (( eip = (PRLDAP_ErrorInfo *)prldap_get_thread_private(
480 		map->prtm_index )) != NULL &&
481 		prldap_set_thread_private( map->prtm_index, NULL ) == 0 ) {
482 	if ( eip->plei_matched != NULL ) {
483 	    ldap_memfree( eip->plei_matched );
484 	}
485 	if ( eip->plei_errmsg != NULL ) {
486 	    ldap_memfree( eip->plei_errmsg );
487 	}
488 
489 	PR_Free( eip );
490     }
491 
492     /* mark map as available for re-use */
493     map->prtm_ld = NULL;
494 
495     PR_Unlock( prldap_map_mutex );
496 }
497 
498 
499 /*
500  * Function: prldap_new_tpdindex()
501  * Description: allocate a thread-private data index.
502  * Returns: the new index.
503  */
504 static PRUintn
505 prldap_new_tpdindex( void )
506 {
507     PRUintn	tpdindex;
508 
509     tpdindex = (PRUintn)PR_AtomicIncrement( &prldap_tpd_maxindex );
510     return( tpdindex );
511 }
512 
513 
514 /*
515  * Function: prldap_set_thread_private()
516  * Description: store a piece of thread-private data.
517  * Returns: 0 if successful and -1 if not.
518  */
519 static int
520 prldap_set_thread_private( PRInt32 tpdindex, void *priv )
521 {
522     PRLDAP_TPDHeader	*tsdhdr;
523 
524     if ( tpdindex > prldap_tpd_maxindex ) {
525 	return( -1 );	/* bad index */
526     }
527 
528     tsdhdr = (PRLDAP_TPDHeader *)PR_GetThreadPrivate( prldap_tpdindex );
529     if ( tsdhdr == NULL || tpdindex >= tsdhdr->ptpdh_tpd_count ) {
530 	tsdhdr = prldap_tsd_realloc( tsdhdr, tpdindex );
531 	if ( tsdhdr == NULL ) {
532 	    return( -1 );	/* realloc failed */
533 	}
534     }
535 
536     tsdhdr->ptpdh_dataitems[ tpdindex ] = priv;
537     return( 0 );
538 }
539 
540 
541 /*
542  * Function: prldap_get_thread_private()
543  * Description: retrieve a piece of thread-private data.  If not set,
544  *	NULL is returned.
545  * Returns: 0 if successful and -1 if not.
546  */
547 static void *
548 prldap_get_thread_private( PRInt32 tpdindex )
549 {
550     PRLDAP_TPDHeader	*tsdhdr;
551 
552     tsdhdr = (PRLDAP_TPDHeader *)PR_GetThreadPrivate( prldap_tpdindex );
553     if ( tsdhdr == NULL ) {
554 	return( NULL );	/* no thread private data */
555     }
556 
557     if ( tpdindex >= tsdhdr->ptpdh_tpd_count
558 		|| tsdhdr->ptpdh_dataitems == NULL ) {
559 	return( NULL );	/* fewer data items than requested index */
560     }
561 
562     return( tsdhdr->ptpdh_dataitems[ tpdindex ] );
563 }
564 
565 
566 /*
567  * Function: prldap_tsd_realloc()
568  * Description: enlarge the thread-private data array.
569  * Returns: the new PRLDAP_TPDHeader value (non-NULL if successful).
570  * Note: tsdhdr can be NULL (allocates a new PRLDAP_TPDHeader).
571  */
572 static PRLDAP_TPDHeader *
573 prldap_tsd_realloc( PRLDAP_TPDHeader *tsdhdr, int maxindex )
574 {
575     void	*newdataitems = NULL;
576     int		count;
577 
578     if ( tsdhdr == NULL ) {
579 	/* allocate a new thread private data header */
580 	if (( tsdhdr = PR_Calloc( 1, sizeof( PRLDAP_TPDHeader ))) == NULL ) {
581 	    return( NULL );
582 	}
583 	(void)PR_SetThreadPrivate( prldap_tpdindex, tsdhdr );
584     }
585 
586     /*
587      * Make the size of the new array the next highest multiple of
588      * the array increment value that is greater than maxindex.
589      */
590     count = PRLDAP_TPD_ARRAY_INCREMENT *
591 		( 1 + ( maxindex / PRLDAP_TPD_ARRAY_INCREMENT ));
592 
593     /* increase the size of the data item array if necessary */
594     if ( count > tsdhdr->ptpdh_tpd_count  ) {
595 	newdataitems = (PRLDAP_ErrorInfo *)PR_Calloc( count, sizeof( void * ));
596 	if ( newdataitems == NULL ) {
597 	    return( NULL );
598 	}
599 	if ( tsdhdr->ptpdh_dataitems != NULL ) {	/* preserve old data */
600 	    memcpy( newdataitems, tsdhdr->ptpdh_dataitems,
601 			tsdhdr->ptpdh_tpd_count * sizeof( void * ));
602 	    PR_Free( tsdhdr->ptpdh_dataitems );
603 	}
604 
605 	tsdhdr->ptpdh_tpd_count = count;
606 	tsdhdr->ptpdh_dataitems = newdataitems;
607     }
608 
609     return( tsdhdr );
610 }
611 
612 
613 /*
614  * Function: prldap_tsd_destroy()
615  * Description: Free a thread-private data array. Installed as an NSPR TPD
616  *	destructor function
617  * Returns: nothing.
618  * Note: this function assumes that each TPD item installed at the PRLDAP
619  *	level can be freed with a call to PR_Free().
620  */
621 static void
622 prldap_tsd_destroy( void *priv )
623 {
624     PRLDAP_TPDHeader	*tsdhdr;
625     int			i;
626 
627     tsdhdr = (PRLDAP_TPDHeader *)priv;
628     if ( tsdhdr != NULL ) {
629 	if ( tsdhdr->ptpdh_dataitems != NULL ) {
630 	    for ( i = 0; i < tsdhdr->ptpdh_tpd_count; ++i ) {
631 		if ( tsdhdr->ptpdh_dataitems[ i ] != NULL ) {
632 		    PR_Free( tsdhdr->ptpdh_dataitems[ i ] );
633 		    tsdhdr->ptpdh_dataitems[ i ] = NULL;
634 		}
635 	    }
636 	    PR_Free( tsdhdr->ptpdh_dataitems );
637 	    tsdhdr->ptpdh_dataitems = NULL;
638 	}
639 	PR_Free( tsdhdr );
640     }
641 }
642 #endif
643 
644 #ifdef	_SOLARIS_SDK
645 #pragma	init(prldap_nspr_init)
646 static mutex_t	nspr_init_lock = DEFAULTMUTEX;
647 static int	nspr_initialized = 0;
648 
649 /*
650  * Initialize NSPR once
651  *
652  */
653 void
654 prldap_nspr_init(void) {
655 	struct sigaction	action;
656 
657 	/*
658 	 * For performance reason, test it here first
659 	 */
660 	if (nspr_initialized != 0)
661 		return;
662 
663 	(void) mutex_lock(&nspr_init_lock);
664 	/* Make sure PR_Init() is executed only once */
665 	if (nspr_initialized == 0) {
666 		/*
667 		 * PR_Init changes the signal handler of SIGPIPE to SIG_IGN.
668 		 * Save the original and restore it after PR_Init.
669 		 */
670 		(void) sigaction(SIGPIPE, NULL, &action);
671 
672 		if (PR_Initialized() == PR_FALSE) {
673 			/*
674 			 * PR_Init() changes the current thread's
675 			 * priority.  Save and restore the priority.
676 			 */
677 			int priority;
678 			(void) thr_getprio(thr_self(), &priority);
679 			PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
680 			(void) thr_setprio(thr_self(), priority);
681 		}
682 		nspr_initialized = 1;
683 		/*
684 		 * Restore signal handling attributes of SIGPIPE
685 		 */
686 		(void) sigaction(SIGPIPE, &action, NULL);
687 	}
688 	(void) mutex_unlock(&nspr_init_lock);
689 }
690 #endif
691