ldappr-threads.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is Mozilla Communicator client code, released
* March 31, 1998.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998-1999 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*/
/*
* Thread callback functions for libldap that use the NSPR (Netscape
* Portable Runtime) thread API.
*
*/
#ifdef _SOLARIS_SDK
#include <thread.h>
#include <synch.h>
#include <prinit.h>
#include <prthread.h>
#include <syslog.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
extern int errno;
#endif /* _SOLARIS_SDK */
#include "ldappr-int.h"
#ifndef _SOLARIS_SDK
/*
* Macros:
*/
/*
* Grow thread private data arrays 10 elements at a time.
*/
#define PRLDAP_TPD_ARRAY_INCREMENT 10
/*
* Structures and types:
*/
/*
* Structure used by libldap thread callbacks to maintain error information.
*/
typedef struct prldap_errorinfo {
int plei_lderrno;
char *plei_matched;
char *plei_errmsg;
} PRLDAP_ErrorInfo;
/*
* Structure used to maintain thread-private data. At the present time,
* only error info. is thread-private. One of these structures is allocated
* for each thread.
*/
typedef struct prldap_tpd_header {
int ptpdh_tpd_count; /* # of data items allocated */
void **ptpdh_dataitems; /* array of data items */
} PRLDAP_TPDHeader;
/*
* Structure used by associate a PRLDAP thread-private data index with an
* LDAP session handle. One of these exists for each active LDAP session
* handle.
*/
typedef struct prldap_tpd_map {
LDAP *prtm_ld; /* non-NULL if in use */
PRUintn prtm_index; /* index into TPD array */
struct prldap_tpd_map *prtm_next;
} PRLDAP_TPDMap;
#ifdef _SOLARIS_SDK
extern mutex_t inited_mutex;
#endif /* _SOLARIS_SDK */
/*
* Static Variables:
*/
/*
* prldap_map_list points to all of the PRLDAP_TPDMap structures
* we have ever allocated. We recycle them as we open and close LDAP
* sessions.
*/
static PRLDAP_TPDMap *prldap_map_list = NULL;
/*
* The prldap_map_mutex is used to protect access to the prldap_map_list.
*/
static PRLock *prldap_map_mutex = NULL;
/*
* The prldap_tpd_maxindex value is used to track the largest TPD array
* index we have used.
*/
static PRInt32 prldap_tpd_maxindex = -1;
/*
* prldap_tpdindex is an NSPR thread private data index we use to
* maintain our own thread-private data. It is initialized inside
* prldap_init_tpd().
*/
static PRUintn prldap_tpdindex = 0;
/*
* The prldap_callonce_init_tpd structure is used by NSPR to ensure
* that prldap_init_tpd() is called at most once.
*/
static PRCallOnceType prldap_callonce_init_tpd = { 0, 0, 0 };
/*
* Private function prototypes:
*/
static void prldap_set_ld_error( int err, char *matched, char *errmsg,
void *errorarg );
static int prldap_get_ld_error( char **matchedp, char **errmsgp,
void *errorarg );
#endif
static void *prldap_mutex_alloc( void );
static void prldap_mutex_free( void *mutex );
static int prldap_mutex_lock( void *mutex );
static int prldap_mutex_unlock( void *mutex );
static void *prldap_get_thread_id( void );
#ifndef _SOLARIS_SDK
static PRStatus prldap_init_tpd( void );
static PRLDAP_TPDMap *prldap_allocate_map( LDAP *ld );
static void prldap_return_map( PRLDAP_TPDMap *map );
static PRUintn prldap_new_tpdindex( void );
static int prldap_set_thread_private( PRInt32 tpdindex, void *priv );
static void *prldap_get_thread_private( PRInt32 tpdindex );
static PRLDAP_TPDHeader *prldap_tsd_realloc( PRLDAP_TPDHeader *tsdhdr,
int maxindex );
static void prldap_tsd_destroy( void *priv );
#endif
/*
* Install NSPR thread functions into ld (if ld is NULL, they are installed
* as the default functions for new LDAP * handles).
*
* Returns 0 if all goes well and -1 if not.
*/
int
prldap_install_thread_functions( LDAP *ld, int shared )
{
struct ldap_thread_fns tfns;
struct ldap_extra_thread_fns xtfns;
#ifndef _SOLARIS_SDK
if ( PR_CallOnce( &prldap_callonce_init_tpd, prldap_init_tpd )
!= PR_SUCCESS ) {
ldap_set_lderrno( ld, LDAP_LOCAL_ERROR, NULL, NULL );
return( -1 );
}
#endif /* _SOLARIS_SDK */
/* set thread function pointers */
memset( &tfns, '\0', sizeof(struct ldap_thread_fns) );
tfns.ltf_get_errno = prldap_get_system_errno;
tfns.ltf_set_errno = prldap_set_system_errno;
if ( shared ) {
tfns.ltf_mutex_alloc = prldap_mutex_alloc;
tfns.ltf_mutex_free = prldap_mutex_free;
tfns.ltf_mutex_lock = prldap_mutex_lock;
tfns.ltf_mutex_unlock = prldap_mutex_unlock;
#ifdef _SOLARIS_SDK
tfns.ltf_get_lderrno = NULL;
tfns.ltf_set_lderrno = NULL;
#else
tfns.ltf_get_lderrno = prldap_get_ld_error;
tfns.ltf_set_lderrno = prldap_set_ld_error;
if ( ld != NULL ) {
/*
* If this is a real ld (i.e., we are not setting the global
* defaults) allocate thread private data for error information.
* If ld is NULL we do not do this here but it is done in
* prldap_thread_new_handle().
*/
if (( tfns.ltf_lderrno_arg = (void *)prldap_allocate_map( ld ))
== NULL ) {
return( -1 );
}
}
#endif
}
if ( ldap_set_option( ld, LDAP_OPT_THREAD_FN_PTRS,
(void *)&tfns ) != 0 ) {
#ifndef _SOLARIS_SDK
prldap_return_map( (PRLDAP_TPDMap *)tfns.ltf_lderrno_arg );
#endif
return( -1 );
}
/* set extended thread function pointers */
memset( &xtfns, '\0', sizeof(struct ldap_extra_thread_fns) );
xtfns.ltf_threadid_fn = prldap_get_thread_id;
if ( ldap_set_option( ld, LDAP_OPT_EXTRA_THREAD_FN_PTRS,
(void *)&xtfns ) != 0 ) {
return( -1 );
}
return( 0 );
}
static void *
prldap_mutex_alloc( void )
{
return( (void *)PR_NewLock());
}
static void
prldap_mutex_free( void *mutex )
{
PR_DestroyLock( (PRLock *)mutex );
}
static int
prldap_mutex_lock( void *mutex )
{
PR_Lock( (PRLock *)mutex );
return( 0 );
}
static int
prldap_mutex_unlock( void *mutex )
{
if ( PR_Unlock( (PRLock *)mutex ) == PR_FAILURE ) {
return( -1 );
}
return( 0 );
}
static void *
prldap_get_thread_id( void )
{
#ifdef _SOLARIS_SDK
return ((void *)thr_self());
#else
return( (void *)PR_GetCurrentThread());
#endif
}
#ifndef _SOLARIS_SDK
static int
prldap_get_ld_error( char **matchedp, char **errmsgp, void *errorarg )
{
PRLDAP_TPDMap *map;
PRLDAP_ErrorInfo *eip;
if (( map = (PRLDAP_TPDMap *)errorarg ) != NULL && ( eip =
(PRLDAP_ErrorInfo *)prldap_get_thread_private(
map->prtm_index )) != NULL ) {
if ( matchedp != NULL ) {
*matchedp = eip->plei_matched;
}
if ( errmsgp != NULL ) {
*errmsgp = eip->plei_errmsg;
}
return( eip->plei_lderrno );
} else {
if ( matchedp != NULL ) {
*matchedp = NULL;
}
if ( errmsgp != NULL ) {
*errmsgp = NULL;
}
return( LDAP_LOCAL_ERROR ); /* punt */
}
}
static void
prldap_set_ld_error( int err, char *matched, char *errmsg, void *errorarg )
{
PRLDAP_TPDMap *map;
PRLDAP_ErrorInfo *eip;
if (( map = (PRLDAP_TPDMap *)errorarg ) != NULL ) {
if (( eip = (PRLDAP_ErrorInfo *)prldap_get_thread_private(
map->prtm_index )) == NULL ) {
/*
* Error info. has not yet been allocated for this thread.
* Do so now. Note that we free this memory only for the
* thread that calls prldap_thread_dispose_handle(), which
* should be the one that called ldap_unbind() -- see
* prldap_return_map(). Not freeing the memory used by
* other threads is deemed acceptable since it will be
* recycled and used by other LDAP sessions. All of the
* thread-private memory is freed when a thread exits
* (inside the prldap_tsd_destroy() function).
*/
eip = (PRLDAP_ErrorInfo *)PR_Calloc( 1,
sizeof( PRLDAP_ErrorInfo ));
if ( eip == NULL ) {
return; /* punt */
}
(void)prldap_set_thread_private( map->prtm_index, eip );
}
eip->plei_lderrno = err;
if ( eip->plei_matched != NULL ) {
ldap_memfree( eip->plei_matched );
}
eip->plei_matched = matched;
if ( eip->plei_errmsg != NULL ) {
ldap_memfree( eip->plei_errmsg );
}
eip->plei_errmsg = errmsg;
}
}
#endif
/*
* Called when a new LDAP * session handle is allocated.
* Allocate thread-private data for error information, but only if
* it has not already been allocated and the get_ld_error callback has
* been installed. If ld is not NULL when prldap_install_thread_functions()
* is called, we will have already allocated the thread-private data there.
*/
int
prldap_thread_new_handle( LDAP *ld, void *sessionarg )
{
struct ldap_thread_fns tfns;
#ifndef _SOLARIS_SDK
if ( ldap_get_option( ld, LDAP_OPT_THREAD_FN_PTRS, (void *)&tfns ) != 0 ) {
return( LDAP_LOCAL_ERROR );
}
if ( tfns.ltf_lderrno_arg == NULL && tfns.ltf_get_lderrno != NULL ) {
if (( tfns.ltf_lderrno_arg = (void *)prldap_allocate_map( ld )) == NULL
|| ldap_set_option( ld, LDAP_OPT_THREAD_FN_PTRS,
(void *)&tfns ) != 0 ) {
return( LDAP_LOCAL_ERROR );
}
}
#endif
return( LDAP_SUCCESS );
}
/*
* Called when an LDAP * session handle is being destroyed.
* Clean up our thread private data map.
*/
void
prldap_thread_dispose_handle( LDAP *ld, void *sessionarg )
{
#ifndef _SOLARIS_SDK
struct ldap_thread_fns tfns;
if ( ldap_get_option( ld, LDAP_OPT_THREAD_FN_PTRS,
(void *)&tfns ) == 0 &&
tfns.ltf_lderrno_arg != NULL ) {
prldap_return_map( (PRLDAP_TPDMap *)tfns.ltf_lderrno_arg );
}
#endif
}
#ifndef _SOLARIS_SDK
static PRStatus
prldap_init_tpd( void )
{
if (( prldap_map_mutex = PR_NewLock()) == NULL || PR_NewThreadPrivateIndex(
&prldap_tpdindex, prldap_tsd_destroy ) != PR_SUCCESS ) {
return( PR_FAILURE );
}
prldap_map_list = NULL;
return( PR_SUCCESS );
}
/*
* Function: prldap_allocate_map()
* Description: allocate a thread-private data map to use for a new
* LDAP session handle.
* Returns: a pointer to the TPD map or NULL if none available.
*/
static PRLDAP_TPDMap *
prldap_allocate_map( LDAP *ld )
{
PRLDAP_TPDMap *map, *prevmap;
PR_Lock( prldap_map_mutex );
/*
* first look for a map that is already allocated but free to be re-used
*/
prevmap = NULL;
for ( map = prldap_map_list; map != NULL; map = map->prtm_next ) {
if ( map->prtm_ld == NULL ) {
break;
}
prevmap = map;
}
/*
* if none we found (map == NULL), try to allocate a new one and add it
* to the end of our global list.
*/
if ( map == NULL ) {
PRUintn tpdindex;
tpdindex = prldap_new_tpdindex();
map = (PRLDAP_TPDMap *)PR_Malloc( sizeof( PRLDAP_TPDMap ));
if ( map != NULL ) {
map->prtm_index = tpdindex;
map->prtm_next = NULL;
if ( prevmap == NULL ) {
prldap_map_list = map;
} else {
prevmap->prtm_next = map;
}
}
}
if ( map != NULL ) {
map->prtm_ld = ld; /* now marked as "in use" */
/* since we are reusing...reset */
/* to initial state */
(void)prldap_set_thread_private( map->prtm_index, NULL );
}
PR_Unlock( prldap_map_mutex );
return( map );
}
/*
* Function: prldap_return_map()
* Description: return a thread-private data map to the pool of ones
* available for re-use.
*/
static void
prldap_return_map( PRLDAP_TPDMap *map )
{
PRLDAP_ErrorInfo *eip;
PR_Lock( prldap_map_mutex );
/*
* Dispose of thread-private LDAP error information. Note that this
* only disposes of the memory consumed on THIS thread, but that is
* okay. See the comment in prldap_set_ld_error() for the reason why.
*/
if (( eip = (PRLDAP_ErrorInfo *)prldap_get_thread_private(
map->prtm_index )) != NULL &&
prldap_set_thread_private( map->prtm_index, NULL ) == 0 ) {
if ( eip->plei_matched != NULL ) {
ldap_memfree( eip->plei_matched );
}
if ( eip->plei_errmsg != NULL ) {
ldap_memfree( eip->plei_errmsg );
}
PR_Free( eip );
}
/* mark map as available for re-use */
map->prtm_ld = NULL;
PR_Unlock( prldap_map_mutex );
}
/*
* Function: prldap_new_tpdindex()
* Description: allocate a thread-private data index.
* Returns: the new index.
*/
static PRUintn
prldap_new_tpdindex( void )
{
PRUintn tpdindex;
tpdindex = (PRUintn)PR_AtomicIncrement( &prldap_tpd_maxindex );
return( tpdindex );
}
/*
* Function: prldap_set_thread_private()
* Description: store a piece of thread-private data.
* Returns: 0 if successful and -1 if not.
*/
static int
prldap_set_thread_private( PRInt32 tpdindex, void *priv )
{
PRLDAP_TPDHeader *tsdhdr;
if ( tpdindex > prldap_tpd_maxindex ) {
return( -1 ); /* bad index */
}
tsdhdr = (PRLDAP_TPDHeader *)PR_GetThreadPrivate( prldap_tpdindex );
if ( tsdhdr == NULL || tpdindex >= tsdhdr->ptpdh_tpd_count ) {
tsdhdr = prldap_tsd_realloc( tsdhdr, tpdindex );
if ( tsdhdr == NULL ) {
return( -1 ); /* realloc failed */
}
}
tsdhdr->ptpdh_dataitems[ tpdindex ] = priv;
return( 0 );
}
/*
* Function: prldap_get_thread_private()
* Description: retrieve a piece of thread-private data. If not set,
* NULL is returned.
* Returns: 0 if successful and -1 if not.
*/
static void *
prldap_get_thread_private( PRInt32 tpdindex )
{
PRLDAP_TPDHeader *tsdhdr;
tsdhdr = (PRLDAP_TPDHeader *)PR_GetThreadPrivate( prldap_tpdindex );
if ( tsdhdr == NULL ) {
return( NULL ); /* no thread private data */
}
if ( tpdindex >= tsdhdr->ptpdh_tpd_count
|| tsdhdr->ptpdh_dataitems == NULL ) {
return( NULL ); /* fewer data items than requested index */
}
return( tsdhdr->ptpdh_dataitems[ tpdindex ] );
}
/*
* Function: prldap_tsd_realloc()
* Description: enlarge the thread-private data array.
* Returns: the new PRLDAP_TPDHeader value (non-NULL if successful).
* Note: tsdhdr can be NULL (allocates a new PRLDAP_TPDHeader).
*/
static PRLDAP_TPDHeader *
prldap_tsd_realloc( PRLDAP_TPDHeader *tsdhdr, int maxindex )
{
void *newdataitems = NULL;
int count;
if ( tsdhdr == NULL ) {
/* allocate a new thread private data header */
if (( tsdhdr = PR_Calloc( 1, sizeof( PRLDAP_TPDHeader ))) == NULL ) {
return( NULL );
}
(void)PR_SetThreadPrivate( prldap_tpdindex, tsdhdr );
}
/*
* Make the size of the new array the next highest multiple of
* the array increment value that is greater than maxindex.
*/
count = PRLDAP_TPD_ARRAY_INCREMENT *
( 1 + ( maxindex / PRLDAP_TPD_ARRAY_INCREMENT ));
/* increase the size of the data item array if necessary */
if ( count > tsdhdr->ptpdh_tpd_count ) {
newdataitems = (PRLDAP_ErrorInfo *)PR_Calloc( count, sizeof( void * ));
if ( newdataitems == NULL ) {
return( NULL );
}
if ( tsdhdr->ptpdh_dataitems != NULL ) { /* preserve old data */
memcpy( newdataitems, tsdhdr->ptpdh_dataitems,
tsdhdr->ptpdh_tpd_count * sizeof( void * ));
PR_Free( tsdhdr->ptpdh_dataitems );
}
tsdhdr->ptpdh_tpd_count = count;
tsdhdr->ptpdh_dataitems = newdataitems;
}
return( tsdhdr );
}
/*
* Function: prldap_tsd_destroy()
* Description: Free a thread-private data array. Installed as an NSPR TPD
* destructor function
* Returns: nothing.
* Note: this function assumes that each TPD item installed at the PRLDAP
* level can be freed with a call to PR_Free().
*/
static void
prldap_tsd_destroy( void *priv )
{
PRLDAP_TPDHeader *tsdhdr;
int i;
tsdhdr = (PRLDAP_TPDHeader *)priv;
if ( tsdhdr != NULL ) {
if ( tsdhdr->ptpdh_dataitems != NULL ) {
for ( i = 0; i < tsdhdr->ptpdh_tpd_count; ++i ) {
if ( tsdhdr->ptpdh_dataitems[ i ] != NULL ) {
PR_Free( tsdhdr->ptpdh_dataitems[ i ] );
tsdhdr->ptpdh_dataitems[ i ] = NULL;
}
}
PR_Free( tsdhdr->ptpdh_dataitems );
tsdhdr->ptpdh_dataitems = NULL;
}
PR_Free( tsdhdr );
}
}
#endif
#ifdef _SOLARIS_SDK
#pragma init(prldap_nspr_init)
static mutex_t nspr_init_lock = DEFAULTMUTEX;
static mutex_t nspr_idle_lock = DEFAULTMUTEX;
static cond_t nspr_idle_cond = DEFAULTCV;
static int nspr_pr_init_is_done = 0;
static int nspr_initialized = 0;
void *
prldap_nspr_idle_primordial_thread(void *arg) {
/*
* Make sure PR_Init finishes before any other thread can continue
*/
(void) mutex_lock(&nspr_idle_lock);
if (PR_Initialized() == PR_FALSE)
PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
nspr_pr_init_is_done = 1;
(void) cond_signal(&nspr_idle_cond);
(void) mutex_unlock(&nspr_idle_lock);
/* Debug only */
syslog(LOG_DEBUG, "NSPR is initialized by the"
"idle primordial thread tid %ld created by thread "
"tid %ld", thr_self(), (long)arg);
pause();
}
/*
* Initialize NSPR once
*
* Ideally this should be done in .init of NSPR.
* This is a workaround so only main thread can initialize
* NSPR but main() does not need to call PR_Init().
* The future direction is NSPR free so we don't want programs
* to call PR_Init().
*
* For most of cases, programs link libldap (-lldap)
* and .init is executed before the control is transfered to
* main().
* But for programs linking libnsl (-lnsl), libldap is loaded
* via dlopen("nss_ldap.so.1", RTLD_LAZY) so the thread loads
* libldap is not necessary a main or a primordial
* thread. In the latter case, an idle primordial thread is created
* to initialize NSPR so NSPR won't be initialized by non-primordial
* threads.
* libldap is built with "-z nodelete" so libldap and libnspr4.so
* are persistent in the address space.
*/
void
prldap_nspr_init(void) {
struct sigaction action;
/*
* For performance reason, test it here first
*/
if (nspr_initialized != 0)
return;
(void) mutex_lock(&nspr_init_lock);
/* Make sure PR_Init() is executed only once */
if (nspr_initialized == 0) {
/*
* PR_Init changes the signal handler of SIGPIPE to SIG_IGN.
* Save the original and restore it after PR_Init.
*/
(void) sigaction(SIGPIPE, NULL, &action);
if (thr_self() == 1) {
/* main thread */
if (PR_Initialized() == PR_FALSE)
PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
nspr_initialized = 1;
} else {
if (thr_create(NULL, NULL,
prldap_nspr_idle_primordial_thread,
(void *)thr_self(), THR_DETACHED, NULL) != 0) {
syslog(LOG_ERR,
"libldap:.init: Can't create thread. "
"%s", strerror(errno));
} else {
/*
* Make sure PR_Init finishes before any other thread
* can continue.
* It's unlikely, but not impossible that this thread
* finishes dlopen and starts to call
* LDAP API when the idle thread still has not
* finished PR_Init() yet.
*/
(void) mutex_lock(&nspr_idle_lock);
while (nspr_pr_init_is_done == 0) {
(void) cond_wait(&nspr_idle_cond,
&nspr_idle_lock);
}
(void) mutex_unlock(&nspr_idle_lock);
nspr_initialized = 1;
}
}
/*
* Restore signal handling attributes of SIGPIPE
*/
(void) sigaction(SIGPIPE, &action, NULL);
}
(void) mutex_unlock(&nspr_init_lock);
}
#endif