his appendix explains how to write multi-threaded LDAP clients.
LDAP structure, you need to set up the session so that the threads do not interfere with each other.
For example, in a single-threaded LDAP client, the LDAP structure contains the error code of the last LDAP operation. In a multi-threaded client, you need to set up the session so that each thread can have its own error code.
The LDAP_OPT_THREAD_FN_PTRS session option lets you set up an ldap_thread_fns structure identifying the functions that are called in multi-threaded environments (for example, functions to lock and unlock critical sections of code and to get and set errors).
Because this structure lets you specify these functions, you can use the LDAP API library in different types of threading environments.
ldap_thread_fns structure and identify the functions that you want to use.
The ldap_thread_fns structure has the following fields:
struct ldap_thread_fns {
void *(*ltf_mutex_alloc)( void );
void (*ltf_mutex_free)( void * );
int (*ltf_mutex_lock)( void * );
int (*ltf_mutex_unlock)( void * );
int (*ltf_get_errno)( void );
void (*ltf_set_errno)( int );
int (*ltf_get_lderrno)( char **, char **, void * );
void (*ltf_set_lderrno)( int, char *, char *, void * );
void *ltf_lderrno_arg;
} ;These fields are describe in more detail below:
Setting the Session Options
After you set up the ldap_thread_fns structure, you need to associate the structure with the current session. Call the ldap_set_option() function and pass LDAP_OPT_THREAD_FN_PTRS as the value of the option parameter. Pass a pointer to the ldap_thread_fns structure as the value of the optdata parameter.
For example, the following section of code sets up an ldap_thread_fns structure for an LDAP session.
#include <stdio.h>
#include <malloc.h>
#include <errno.h>
#include <pthread.h>
#include <ldap.h>
struct ldap_thread_fns tfns;
...
/* Set up the ldap_thread_fns structure with pointers
to the functions that you want called */
memset( &tfns, '\0', sizeof(struct ldap_thread_fns) );
/* Specify the functions that you want called */
/* Call the my_mutex_alloc() function whenever mutexes
need to be allocated */
tfns.ltf_mutex_alloc = (void *(*)(void)) my_mutex_alloc;
/* Call the my_mutex_free() function whenever mutexes
need to be destroyed */
tfns.ltf_mutex_free = (void (*)(void *)) my_mutex_free;
/* Call the pthread_mutex_lock() function whenever a
thread needs to lock a mutex. */
tfns.ltf_mutex_lock = (int (*)(void *)) pthread_mutex_lock;
/* Call the pthread_mutex_unlock() function whenever a
thread needs to unlock a mutex. */
tfns.ltf_mutex_unlock = (int (*)(void *)) pthread_mutex_unlock;
/* Call the get_errno() function to get the value of errno */
tfns.ltf_get_errno = get_errno;
/* Call the set_errno() function to set the value of errno */
tfns.ltf_set_errno = set_errno;
/* Call the get_ld_error() function to get error values from
calls to functions in the libldap library */
tfns.ltf_get_lderrno = get_ld_error;
/* Call the set_ld_error() function to set error values for
calls to functions in the libldap library */
tfns.ltf_set_lderrno = set_ld_error;
/* Don't pass any extra parameter to the functions for
getting and setting libldap function call errors */
tfns.ltf_lderrno_arg = NULL;
...
/* Set the session option that specifies the functions to call for multi-threaded clients */
if (ldap_set_option( ld, LDAP_OPT_THREAD_FN_PTRS, (void *) &tfns)!= 0) {
ldap_perror( ld, "ldap_set_option: thread pointers" );
}
...
Example of a Pthreads Client Application
The following example, which uses pthreads (POSIX threads) under Solaris, is the source code for a multi-threaded client. The client connects to a specified LDAP server and creates several threads to perform multiple search and update operations simultaneously on the directory.
#include <stdio.h>
#include <malloc.h>
#include <errno.h>
#include <pthread.h>
#include <ldap.h>
#define NAME NULL
#define PASSWORD NULL
#define BASE "o=Ace Industry, c=US"
static void *search_thread();
static void *modify_thread();
static void *add_thread();
static void *delete_thread();
static void *my_mutex_alloc();
static void my_mutex_free();
static void set_ld_error();
static int get_ld_error();
static void set_errno();
static int get_errno();
static void tsd_setup();
LDAP *ld;
pthread_key_t key;
main( int argc, char **argv )
{
pthread_attr_t attr;
pthread_t search_tid, search_tid2, search_tid3, search_tid4;
pthread_t modify_tid, add_tid, delete_tid;
void *status;
struct ldap_thread_fns tfns;
/* To run the client, enter the name and port of the LDAP server */
if ( argc != 3 ) {
fprintf( stderr, "usage: %s host port\n", argv[0] );
exit( 1 );
}
/* Allocate a key for thread-specific data */
if ( pthread_key_create( &key, free ) != 0 ) {
perror( "pthread_key_create" );
}
/* Set up thread-specific data for the error codes */
tsd_setup();
/* Initialize a session with the server */
if ( (ld = ldap_init( argv[1], atoi( argv[2] ) )) == NULL ) {
perror( "ldap_init" );
exit( 1 );
}
/* Specify the functions that you want to be called
in the ldap_thread_fns structure. */
memset( &tfns, '\0', sizeof(struct ldap_thread_fns) );
/* Function for allocating memory for a mutex */
tfns.ltf_mutex_alloc = (void *(*)(void)) my_mutex_alloc;
/* Function for destroying a mutex */
tfns.ltf_mutex_free = (void (*)(void *)) my_mutex_free;
/* Function for locking for a mutex */
tfns.ltf_mutex_lock = (int (*)(void *)) pthread_mutex_lock;
/* Function for unlocking for a mutex */
tfns.ltf_mutex_unlock = (int (*)(void *)) pthread_mutex_unlock;
/* Function for getting the value of the errno variable */
tfns.ltf_get_errno = get_errno;
/* Function for setting the value of the errno variable */
tfns.ltf_set_errno = set_errno;
/* Function for getting the error values for LDAP API calls */
tfns.ltf_get_lderrno = get_ld_error;
/* Function for setting the error values for LDAP API calls */
tfns.ltf_set_lderrno = set_ld_error;
/* Don't pass any extra parameters to the functions for
setting LDAP API error values */
tfns.ltf_lderrno_arg = NULL;
/* Set up the session to use these functions */
if ( ldap_set_option( ld, LDAP_OPT_THREAD_FN_PTRS, (void *) &tfns )
!= 0 ) {
ldap_perror( ld, "ldap_set_option: thread pointers" );
}
/* Connect and authenticate to the LDAP server */
if ( ldap_simple_bind_s( ld, NAME, PASSWORD ) != LDAP_SUCCESS ) {
ldap_perror( ld, "ldap_simple_bind_s" );
exit( 1 );
}
/* Initialize the threads attributes structure */
if ( pthread_attr_init( &attr ) != 0 ) {
perror( "pthread_attr_init" );
exit( 1 );
}
/* Set up the threads so they are not detachable */
pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );
/* Create a thread to search the directory */
if (pthread_create( &search_tid, &attr, search_thread, "1") != 0) {
perror( "pthread_create search_thread" );
exit( 1 );
}
/* Create a thread to modify an entry in the directory */
if (pthread_create( &modify_tid, &attr, modify_thread, "2") != 0) {
perror( "pthread_create modify_thread" );
exit( 1 );
}
/* Create another thread to search the directory */
if (pthread_create( &search_tid2, &attr, search_thread, "3")!= 0) {
perror( "pthread_create search_thread2" );
exit( 1 );
}
/* Create a thread to add an entry to the directory */
if ( pthread_create( &add_tid, &attr, add_thread, "4" ) != 0 ) {
perror( "pthread_create add_thread" );
exit( 1 );
}
/* Create a 3rd search thread */
if (pthread_create( &search_tid3, &attr, search_thread, "5")!= 0) {
perror( "pthread_create search_thread3" );
exit( 1 );
}
/* Create a thread to delete an entry from the directory */
if (pthread_create( &delete_tid, &attr, delete_thread, "6") != 0) {
perror( "pthread_create delete_thread" );
exit( 1 );
}
/* Create a 4th search thread */
if (pthread_create( &search_tid4, &attr, search_thread, "7")!= 0) {
perror( "pthread_create search_thread4" );
exit( 1 );
}
/* Wait for the threads to complete, and return the status
of each thread */
pthread_join( search_tid2, &status );
pthread_join( search_tid3, &status );
pthread_join( search_tid4, &status );
pthread_join( modify_tid, &status );
pthread_join( add_tid, &status );
pthread_join( delete_tid, &status );
pthread_join( search_tid, &status );
}
/* Thread for searching the directory */
static void *
search_thread( char *id )
{
LDAPMessage *res;
LDAPMessage *e;
char *a;
char **v;
char *dn;
BerElement *ber;
int i, rc, msgid;
void *tsd;
printf( "search_thread\n" );
/* Set up thread-specific data for LDAP error values */
tsd_setup();
for ( ;; ) {
printf( "%sSearching...\n", id );
/* Perform an asynchronous search */
if ( (msgid = ldap_search( ld, BASE, LDAP_SCOPE_SUBTREE,
"(objectclass=*)", NULL, 0 )) == -1 ) {
ldap_perror( ld, "ldap_search" );
continue;
}
/* Check the results of the search operation */
while ( (rc = ldap_result( ld, msgid, 0, NULL, &res ))
== LDAP_RES_SEARCH_ENTRY ) {
/* Iterate through each entry in the search results */
for ( e = ldap_first_entry( ld, res ); e != NULL;
e = ldap_next_entry( ld, e ) ) {
/* Get and print the DN of the entry */
dn = ldap_get_dn( ld, e );
printf( "%sdn: %s\n", id, dn );
free( dn );
/* Iterate through each attribute in an entry */
for ( a = ldap_first_attribute( ld, e, &ber );
a != NULL; a = ldap_next_attribute( ld, e, ber ) ) {
/* Get and print the values of the attr */
v = ldap_get_values( ld, e, a );
for ( i = 0; v && v[i] != 0; i++ ) {
printf( "%s%s: %s\n", id, a, v[i] );
}
/* Free the attribute values when done */
ldap_value_free( v );
/* Free the attribute name */
free( a );
}
}
/* Free the search results */
ldap_msgfree( res );
/* Print the thread ID */
printf( "%s\n", id );
}
/* If the search was unsuccessful, print the error message */
if ( rc == -1 || ldap_result2error( ld, res, 0 ) !=
LDAP_SUCCESS ) {
ldap_perror( ld, "ldap_search" );
} else {
/* Keep track of which threads are done */
printf( "%sDone with one round\n", id );
}
}
}
/* Thread for modifying an entry in the directory */
static void *
modify_thread( char *id )
{
LDAPMessage *res, *list;
LDAPMessage *e;
int i, modentry, entries, msgid, rc;
LDAPMod mod;
LDAPMod *mods[2];
char *vals[2];
char *dn;
printf( "modify_thread\n" );
/* Set up thread-specific data for LDAP error values */
tsd_setup();
/* First search the directory for entries */
if ( (msgid = ldap_search( ld, BASE, LDAP_SCOPE_SUBTREE,
"(objectclass=*)", NULL, 0 )) == -1 ) {
ldap_perror( ld, "ldap_search" );
exit( 1 );
}
/* Keep a list of the matching entries */
entries = 0;
list = NULL;
while ( (rc = ldap_result( ld, msgid, 0, NULL, &res ))
== LDAP_RES_SEARCH_ENTRY ) {
entries++;
ldap_add_result_entry( &list, res );
}
if ( rc == -1 || ldap_result2error( ld, res, 0 ) != LDAP_SUCCESS) {
ldap_perror( ld, "modify_thread: ldap_search" );
exit( 1 );
} else {
entries++;
printf( "%sModify got %d entries\n", id, entries );
}
mods[0] = &mod;
mods[1] = NULL;
vals[0] = "bar";
vals[1] = NULL;
for ( ;; ) {
/* Select a random entry to modify */
modentry = rand() % entries;
for ( i = 0, e = ldap_first_entry( ld, list ); e != NULL &&
i < modentry; i++, e = ldap_next_entry( ld, e ) ) {
/* NULL */
}
/* Make sure the entry is valid */
if ( e == NULL ) {
fprintf( stderr, "%sModify could not find entry %d of %d\n",
id, modentry, entries );
continue;
}
printf( "%sPicked entry %d of %d\n", id, i, entries );
/* Update the "foo" attribute of the entry */
dn = ldap_get_dn( ld, e );
mod.mod_op = LDAP_MOD_REPLACE;
mod.mod_type = "foo";
mod.mod_values = vals;
printf( "%sModifying (%s)\n", id, dn );
if ( ldap_modify_s( ld, dn, mods ) != LDAP_SUCCESS ) {
ldap_perror( ld, "ldap_modify_s" );
}
free( dn );
}
}
/* Thread for adding an entry to the directory */
static void *
add_thread( char *id )
{
LDAPMod mod[5];
LDAPMod *mods[6];
char dn[BUFSIZ], name[40];
char *cnvals[2], *snvals[2], *ocvals[3];
int i;
printf( "add_thread\n" );
/* Set up thread-specific data for LDAP error values */
tsd_setup();
/* Initialize the LDAPMod array */
for ( i = 0; i < 5; i++ ) {
mods[i] = &mod[i];
}
/* Set up the values in the entry's attributes */
mods[5] = NULL;
mod[0].mod_op = 0;
mod[0].mod_type = "cn";
mod[0].mod_values = cnvals;
cnvals[1] = NULL;
mod[1].mod_op = 0;
mod[1].mod_type = "sn";
mod[1].mod_values = snvals;
snvals[1] = NULL;
mod[2].mod_op = 0;
mod[2].mod_type = "objectclass";
mod[2].mod_values = ocvals;
ocvals[0] = "top";
ocvals[1] = "person";
ocvals[2] = NULL;
mods[3] = NULL;
for ( ;; ) {
/* Generate a random name for the new entry */
sprintf( name, "%d", rand() );
sprintf( dn, "cn=%s, o=Ace Industry, c=US", name );
cnvals[0] = name;
snvals[0] = name;
/* Add the new entry to the directory */
printf( "%sAdding entry (%s)\n", id, dn );
if ( ldap_add_s( ld, dn, mods ) != LDAP_SUCCESS ) {
ldap_perror( ld, "ldap_add_s" );
}
}
}
/* Thread for deleting an entry from the directory */
static void *
delete_thread( char *id )
{
LDAPMessage *res;
char dn[BUFSIZ], name[40];
int entries, msgid, rc;
printf( "delete_thread\n" );
/* Set up thread-specific data for LDAP error values */
tsd_setup();
/* First search the directory */
if ( (msgid = ldap_search( ld, BASE, LDAP_SCOPE_SUBTREE,
"(objectclass=*)", NULL, 0 )) == -1 ) {
ldap_perror( ld, "delete_thread: ldap_search_s" );
exit( 1 );
}
/* Keep a list of the matching entries */
entries = 0;
while ( (rc = ldap_result( ld, msgid, 0, NULL, &res ))
== LDAP_RES_SEARCH_ENTRY ) {
entries++;
ldap_msgfree( res );
}
entries++;
if ( rc == -1 || ldap_result2error( ld, res, 0 ) != LDAP_SUCCESS) {
ldap_perror( ld, "delete_thread: ldap_search" );
} else {
printf( "%sDelete got %d entries\n", id, entries );
}
for ( ;; ) {
/* Generate a random name */
sprintf( name, "%d", rand() );
sprintf( dn, "cn=%s, o=Ace Industry, c=US", name );
/* Delete that entry */
printf( "%sDeleting entry (%s)\n", id, dn );
if ( ldap_delete_s( ld, dn ) != LDAP_SUCCESS ) {
ldap_perror( ld, "ldap_delete_s" );
}
}
}
/* Function for allocating memory for mutexes */
static void *
my_mutex_alloc( void )
{
pthread_mutex_t *mutexp;
/* Allocate memory for the mutex and initialize it */
if ( (mutexp = malloc( sizeof(pthread_mutex_t) )) != NULL ) {
pthread_mutex_init( mutexp, NULL );
}
return( mutexp );
}
/* Function for freeing mutexes */
static void
my_mutex_free( void *mutexp )
{
pthread_mutex_destroy( (pthread_mutex_t *) mutexp );
}
/* Structure for LDAP error values */
struct ldap_error {
int le_errno; /* Corresponds to the LDAP error code */
char *le_matched; /* Matching components of the DN,
if an NO_SUCH_OBJECT error occurred */
char *le_errmsg; /* Error message */
};
/* Function for setting up thread-specific data */
static void
tsd_setup()
{
void *tsd;
/* Check if thread-specific data already exists */
tsd = pthread_getspecific( key );
if ( tsd != NULL ) {
fprintf( stderr, "tsd non-null!\n" );
pthread_exit( NULL );
}
/* Allocate memory for the LDAP error values */
tsd = (void *) calloc( 1, sizeof(struct ldap_error) );
/* Make the data specific to the calling thread */
pthread_setspecific( key, tsd );
}
/* Function for setting thread-specific LDAP error values */
static void
set_ld_error( int err, char *matched, char *errmsg, void *dummy )
{
struct ldap_error *le;
/* Get the data structure specific to the calling thread */
le = pthread_getspecific( key );
/* Set the error code returned by the LDAP operation */
le->le_errno = err;
/* Specify the components of the DN that matched (if
an "NO_SUCH_OBJECT" error occurred */
if ( le->le_matched != NULL ) {
ldap_memfree( le->le_matched );
}
le->le_matched = matched;
/* Specify the error message corresponding to the error code */
if ( le->le_errmsg != NULL ) {
ldap_memfree( le->le_errmsg );
}
le->le_errmsg = errmsg;
}
/* Function for getting the thread-specific LDAP error values */
static int
get_ld_error( char **matched, char **errmsg, void *dummy )
{
struct ldap_error *le;
/* Get the data values specific to the calling thread */
le = pthread_getspecific( key );
/* Retrieve the error values */
if ( matched != NULL ) {
*matched = le->le_matched;
}
if ( errmsg != NULL ) {
*errmsg = le->le_errmsg;
}
return( le->le_errno );
}
/* Function for setting the value of the errno variable */
static void
set_errno( int err )
{
errno = err;
}
/* Function for getting the value of the errno variable */
static int
get_errno( void )
{
return( errno );
}
Last modified: March 31, 1997
Copyright © 1997 Netscape
Communications Corporation