<!--

Copyright 2000-2007 Sun Microsystems, Inc. All Rights Reserved.
Portions copyright 1999 Netscape Communications Corporation. All
Rights Reserved.

The contents of this document are subject to the terms of the
Creative Commons Attribution-ShareAlike 2.5 license or any later
version (the "License"). You may not use this document except in
compliance with the License.

See the License for the specific language governing
permissions and limitations under the License. You can obtain
a copy of the License at
http://creativecommons.org/licenses/by-sa/2.5/legalcode.

-->
<chapter id="csdk-multithread"><title>Writing Multithreaded Clients With &DirectorySDKForC;</title>
<indexterm>
<primary>C SDK</primary>
<secondary>multithreaded clients</secondary>
</indexterm><highlights>
<para>This chapter shows how to write multithreaded LDAP client applications.</para>
<itemizedlist>
<para>This chapter covers the following topics:</para>
<listitem><para><olink targetptr="bdajf">Specifying Thread Functions With Directory SDK for C</olink></para>
</listitem>
<listitem><para><olink targetptr="bdajj">POSIX Thread Client Application With Directory SDK for C</olink></para>
</listitem>
</itemizedlist>
<para>The &DirectorySDKForC; APIs are thread-safe. By default, the APIs
use POSIX thread-safe primitives. Therefore, unless you specify your own thread
functions, standard best practices for POSIX threading apply.</para>
</highlights>
<sect1 id="bdajf"><title>Specifying Thread Functions With Directory SDK for
C</title>
<para>You can write a multithreaded client with different threads accessing
the same <structname>LDAP</structname> structure. Use the POSIX thread API.
Alternatively, set up the session defining your own structures such that threads
do not interfere with data of other threads.</para>
<para>The <literal>LDAP_OPT_THREAD_FN_PTRS</literal> session option lets you
set up an <structname>ldap_thread_fns</structname> structure. The structure
identifies the functions that are called in multithreaded environments. For
example, the structure can define functions to lock critical sections of code
and to handle errors. As this structure lets you specify these functions,
you can use &DirectorySDKForC; in different types of threading environments.</para>
<sect2 id="bdajg"><title>Setting Up the <structname>ldap_thread_fns</structname> Structure
</title>
<para>You can write a multithreaded client in which different threads use
the same LDAP connection. Set up the <structname>ldap_thread_fns</structname> structure.
Then identify the functions that you want to use in the <structname>ldap_thread_fns
</structname> structure.</para>
<example id="ldap-thread-fns-prototype"><title><structname>ldap_thread_fns</structname> Structure
</title>
<programlisting>struct ldap_thread_fns {
  LDAP_TF_MUTEX_ALLOC_CALLBACK   *ltf_mutex_alloc;
  LDAP_TF_MUTEX_FREE_CALLBACK    *ltf_mutex_free;
  LDAP_TF_MUTEX_LOCK_CALLBACK    *ltf_mutex_lock;
  LDAP_TF_MUTEX_UNLOCK_CALLBACK  *ltf_mutex_unlock;
  LDAP_TF_GET_ERRNO_CALLBACK     *ltf_get_errno;
  LDAP_TF_SET_ERRNO_CALLBACK     *ltf_set_errno;
  LDAP_TF_GET_LDERRNO_CALLBACK   *ltf_get_lderrno;
  LDAP_TF_SET_LDERRNO_CALLBACK   *ltf_set_lderrno;
  void    *ltf_lderrno_arg;
};</programlisting>
</example>
<para>The fields of the<?Pub Caret> <structname>ldap_thread_fns</structname> structure
are described in the following table.</para>
<table frame="topbot" pgwide="1" id="ldap-thread-fns-fields"><title><structname>ldap_thread_fns
</structname> Field Descriptions</title>
<tgroup cols="2"><colspec colnum="1" colwidth="25*"><colspec colnum="2"
colwidth="75*">
<thead>
<row>
<entry>
<para>Field</para></entry>
<entry>
<para>Description</para></entry>
</row>
</thead>
<tbody>
<row>
<entry>
<para><structfield>*ltf_mutex_alloc</structfield></para></entry>
<entry>
<para>Function pointer for allocating a mutex. This function is called by
the client when needed if the function pointer is not <literal>NULL</literal>.</para>
</entry>
</row>
<row>
<entry>
<para><structfield>*ltf_mutex_free</structfield></para></entry>
<entry>
<para>Function pointer for freeing a mutex. This function is called by the
client when needed if the function pointer is not <literal>NULL</literal>.</para>
</entry>
</row>
<row>
<entry>
<para><structfield>*ltf_mutex_lock</structfield></para></entry>
<entry>
<para>Function pointer for locking critical sections of code. This function
is called by the client when needed if the function pointer is not <literal>NULL</literal>.
</para></entry>
</row>
<row>
<entry>
<para><structfield>*ltf_mutex_unlock</structfield></para></entry>
<entry>
<para>Function pointer for unlocking critical sections of code. This function
is called by the client when needed if the function pointer is not <literal>NULL</literal>.
</para></entry>
</row>
<row>
<entry>
<para><structfield>*ltf_get_errno</structfield></para></entry>
<entry>
<para>Function pointer for getting the value of the <systemitem>errno</systemitem> variable.
This function is called by the client when needed if the function pointer
is not <literal>NULL</literal>. In a threaded environment, <systemitem>errno</systemitem> is
typically redefined. The error structure has a value for each thread, rather
than a global value for the entire process. This redefinition is done at compile
time. The <filename>libldap</filename> library does not know what method your
code and your threading environment use to get the value of <systemitem>errno</systemitem> for
each thread. The library therefore calls this function to return the value
of <systemitem>errno</systemitem>.</para></entry>
</row>
<row>
<entry>
<para><structfield>*ltf_set_errno</structfield></para></entry>
<entry>
<para>Function pointer for setting the value of the <systemitem>errno</systemitem> variable.
This function is called by the client when needed if the function pointer
is not <literal>NULL</literal>. In a threaded environment, <systemitem>errno</systemitem> is
typically redefined. The error structure has a value for each thread, rather
than a global value for the entire process. This redefinition is done at compile
time. The <filename>libldap</filename> library does not know what method your
code and your threading environment use to get the value of <systemitem>errno</systemitem> for
each thread. The library therefore calls this function to set the value of <systemitem>
errno</systemitem>.</para></entry>
</row>
<row>
<entry>
<para><structfield>*ltf_get_lderrno</structfield></para></entry>
<entry>
<para>Function pointer for getting error values from calls to functions in
the <filename>libldap</filename> library. This function is called by the client
when needed if the function pointer is not <literal>NULL</literal>. If this
function pointer is not set, the <filename>libldap</filename> library records
these errors in fields in the <structname>LDAP</structname> structure.</para>
</entry>
</row>
<row>
<entry>
<para><structfield>*ltf_set_lderrno</structfield></para></entry>
<entry>
<para>Function pointer for setting error values from calls to functions in
the <filename>libldap</filename> library. This function is called by the client
when needed if the function pointer is not <literal>NULL</literal>. If this
function pointer is not set, the <filename>libldap</filename> library records
these errors in fields in the <structname>LDAP</structname> structure.</para>
</entry>
</row>
<row>
<entry>
<para><structfield>*ltf_lderrno_arg</structfield></para></entry>
<entry>
<para>Additional parameter passed to the functions for getting and setting
error values from calls to functions in the <filename>libldap</filename> library. <literal>
*ltf_get_lderrno</literal> and <literal>*ltf_set_lderrno</literal> identify
these functions.</para></entry>
</row>
</tbody>
</tgroup>
</table>
</sect2>
<sect2 id="bdajh"><title>Setting Up the <structname>ldap_extra_thread_fns</structname> Structure
</title>
<para>&DirectorySDKForC; provides a structure, <structname>ldap_extra_thread_fns
</structname>, that specifies <emphasis>additional</emphasis> thread functions
for locking. The structure also specifies thread functions for <firstterm>semaphores
</firstterm>, which are protected variables. The <structname>ldap_extra_thread_fns
</structname> structure is defined in the following example.</para>
<example id="ldap-extra-thread-fns-prototype"><title>Declaring the <structname>ldap_extra_thread_fns
</structname> Structure</title>
<programlisting>struct ldap_extra_thread_fns {
  LDAP_TF_MUTEX_TRYLOCK_CALLBACK  *ltf_mutex_trylock;
  LDAP_TF_SEMA_ALLOC_CALLBACK     *ltf_sema_alloc;
  LDAP_TF_SEMA_FREE_CALLBACK      *ltf_sema_free;
  LDAP_TF_SEMA_WAIT_CALLBACK      *ltf_sema_wait;
  LDAP_TF_SEMA_POST_CALLBACK      *ltf_sema_post;
  LDAP_TF_THREADID_CALLBACK       *ltf_threadid_fn;
};</programlisting>
</example>
<para>&DirectorySDKForC; supports only the <literal>LDAP_TF_TREADID_CALLBACK
*ltf_threadid_fn</literal> function. You use this function callback in a multithreaded
application to improve the performance of thread locking. The supported function
must return an identifier that is unique to the calling thread, like <function>pthread_self
</function> does. If any of the other extra thread callback functions are
set, the extra functions are ignored. </para></sect2>
<sect2 id="bdaji"><title>Setting Session Options</title>
<para>After you set up the <structname>ldap_thread_fns</structname> structure,
associate the structure with the current session. Call the <function>ldap_set_option
</function> function and pass <literal>LDAP_OPT_THREAD_FN_PTRS</literal> as
the value of the <literal>option</literal> parameter. Pass a pointer to the <structname>
ldap_thread_fns</structname> structure as the value of the <literal>optdata</literal> parameter.
</para>
<example id="ldap-thread-fns-example"><title>Associating the <structname>ldap_thread_fns
</structname> Structure With the Current Session</title>
<programlisting>#include &lt;stdio.h>
#include &lt;malloc.h>
#include &lt;errno.h>
#include &lt;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( &amp;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&rsquo;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 *) &amp;tfns) != 0) {
  ldap_perror( ld, "ldap_set_option: thread pointers" );
}
...</programlisting>
</example>
<para>If you <emphasis>also</emphasis> set up the <structname>ldap_extra_thread_fns
</structname> structure, associate the structure with the current session.
Call the <function>ldap_set_option</function> function, passing <literal>LDAP_OPT_EXTRA_THREAD_FN_PTRS
</literal> as the value of the <literal>option</literal> parameter. Also pass
a pointer to the <structname>ldap_extra_thread_fns</structname> structure
as the value of the <literal>optdata</literal> parameter.</para></sect2>
</sect1>
<sect1 id="bdajj"><title>POSIX Thread Client Application With Directory SDK
for C</title>
<indexterm>
<primary>C SDK</primary>
<secondary>POSIX threading</secondary>
</indexterm>
<para>The following example which uses <firstterm>pthreads</firstterm> (POSIX
threads) on Solaris systems, is the source code for a multithreaded client.
The client connects to a specified LDAP server. The client then creates several
threads to perform multiple search and update operations simultaneously on
the directory.</para>
<example id="pthread-example"><title>Using POSIX Threading on a Solaris System</title>
<programlisting>#include &lt;stdio.h>
#include &lt;malloc.h>
#include &lt;errno.h>
#include &lt;pthread.h>
#include &lt;synch.h>
#include "ldap.h"

/* Authentication and search information. */
#define NAME         "cn=Directory Manager"
#define PASSWORD     "rtfm11111"
#define BASE         "dc=example,dc=com"
#define SCOPE        LDAP_SCOPE_SUBTREE

/* Function declarations */
static void *search_thread();
static void *modify_thread();
static void *add_thread();
static void *delete_thread();
static void set_ld_error();
static int  get_ld_error();
static void set_errno();
static int  get_errno();
static void tsd_setup();

/* Linked list of LDAPMessage structs for search results. */
typedef struct ldapmsgwrapper {
    LDAPMessage      *lmw_messagep;
    struct ldapmsgwrapper  *lmw_next;
} ldapmsgwrapper;

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;
  struct ldap_extra_thread_fns extrafns;
  int rc;

  /* Check command-line syntax. */
  if ( argc != 3 ) {
    fprintf( stderr, "usage: %s &lt;host> &lt;port>\n", argv[0] );
    exit( 1 );
  }

  /* Create a key. */
  if ( pthread_key_create( &amp;key, free ) != 0 ) {
    perror( "pthread_key_create" );
  }
  tsd_setup();

  /* Initialize the LDAP session. Use prldap_init() for IPv6. */
  if ( (ld = ldap_init( argv[1], atoi( argv[2] ) )) == NULL ) {
    perror( "ldap_init" );
    exit( 1 );
  }

  /* Set the function pointers for dealing with mutexes
     and error information. */
  memset( &amp;tfns, '\0', sizeof(struct ldap_thread_fns) );
  tfns.ltf_mutex_alloc = (void *(*)(void)) my_mutex_alloc;
  tfns.ltf_mutex_free = (void (*)(void *)) my_mutex_free;
  tfns.ltf_mutex_lock = (int (*)(void *)) pthread_mutex_lock;
  tfns.ltf_mutex_unlock = (int (*)(void *)) pthread_mutex_unlock;
  tfns.ltf_get_errno = get_errno;
  tfns.ltf_set_errno = set_errno;
  tfns.ltf_get_lderrno = get_ld_error;
  tfns.ltf_set_lderrno = set_ld_error;
  tfns.ltf_lderrno_arg = NULL;

  /* Set up this session to use those function pointers. */
  rc = ldap_set_option( ld, LDAP_OPT_THREAD_FN_PTRS, (void *) &amp;tfns );
  if ( rc &lt; 0 ) {
    fprintf( stderr, 
             "ldap_set_option (LDAP_OPT_THREAD_FN_PTRS): %s\n",
             ldap_err2string( rc ) );
    exit( 1 );
  }

  /* Set the function pointers for working with semaphores. */
  memset( &amp;extrafns, '\0', sizeof(struct ldap_extra_thread_fns) );
  extrafns.ltf_mutex_trylock = (int (*)(void *)) = null;
  extrafns.ltf_sema_alloc = (void *(*)(void)) = null;
  extrafns.ltf_sema_free = (void (*)(void *)) = null;
  extrafns.ltf_sema_wait = (int (*)(void *)) = null;
  extrafns.ltf_sema_post = (int (*)(void *)) = null;
  extrafns.ltf_threadid_fn = (void * (*)(void) )pthread_self;
  /* Set up this session to use those function pointers. */
  rc = ldap_set_option( ld,
                        LDAP_OPT_EXTRA_THREAD_FN_PTRS,
                        (void *) &amp;extrafns );
  if ( rc &lt; 0 ) {
    fprintf( stderr, 
             "ldap_set_option (LDAP_OPT_EXTRA_THREAD_FN_PTRS): %s\n", 
             ldap_err2string( rc ) );
    exit( 1 );
  }

  /* Attempt to bind to the server. */
  rc = ldap_simple_bind_s( ld, NAME, PASSWORD );
  if ( rc != LDAP_SUCCESS ) {
    fprintf( stderr, "ldap_simple_bind_s: %s\n", ldap_err2string( rc ) );
    exit( 1 );
  }

  /* Initialize the attribute. */
  if ( pthread_attr_init( &amp;attr ) != 0 ) {
    perror( "pthread_attr_init" );
    exit( 1 );
  }

  /* Specify that the threads are joinable. */
  pthread_attr_setdetachstate( &amp;attr, PTHREAD_CREATE_JOINABLE );

  /* Create seven threads: one for adding, one for modifying,
     one for deleting, and four for searching. */
  if (pthread_create(&amp;search_tid, &amp;attr, search_thread, "1") != 0) {
    perror( "pthread_create search_thread" );
    exit( 1 );
  }
  if (pthread_create(&amp;modify_tid, &amp;attr, modify_thread, "2") != 0) {
    perror( "pthread_create modify_thread" );
    exit( 1 );
  }
  if (pthread_create(&amp;search_tid2, &amp;attr, search_thread, "3") != 0) {
    perror( "pthread_create search_thread2" );
    exit( 1 );
  }
  if (pthread_create(&amp;add_tid, &amp;attr, add_thread, "4" ) != 0) {
    perror( "pthread_create add_thread" );
    exit( 1 );
  }
  if (pthread_create(&amp;search_tid3, &amp;attr, search_thread, "5") != 0) {
    perror( "phread_create search_thread3" );
    exit( 1 );
  }
  if (pthread_create(&amp;delete_tid, &amp;attr, delete_thread, "6") != 0) {
    perror( "pthread_create delete_thread" );
    exit( 1 );
  }
  if (pthread_create(&amp;search_tid4, &amp;attr, search_thread, "7") != 0) {
    perror( "pthread_create search_thread4" );
    exit( 1 );
  }

  /* Wait until these threads exit. */
  pthread_join( modify_tid, &amp;status );
  pthread_join( add_tid, &amp;status );
  pthread_join( delete_tid, &amp;status );
  pthread_join( search_tid, &amp;status );
  pthread_join( search_tid2, &amp;status );
  pthread_join( search_tid3, &amp;status );
  pthread_join( search_tid4, &amp;status );
}

/* Thread for searching the directory.
   The results are not printed out. */
static void *
search_thread( char *id )
{
  LDAPMessage  *res;
  LDAPMessage  *e;
  char    *a;
  char    **v;
  char    *dn;
  BerElement  *ber;
  int    i, rc, parse_rc, msgid, finished;
  int    num_entries, num_refs;
  void    *tsd;
  struct timeval  zerotime;
  zerotime.tv_sec = zerotime.tv_usec = 0L;

  printf( "Starting search_thread %s.\n", id );
  tsd_setup();
  /* Continually search the directory. */
  for ( ;; ) {
    printf( "Thread %s: Searching...\n", id );
    finished = 0;
    num_entries = 0;
    num_refs = 0;
    rc = ldap_search_ext( ld, BASE, SCOPE, "(objectclass=*)",
      NULL, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &amp;msgid );
    if ( rc != LDAP_SUCCESS ) {
      fprintf( stderr, "Thread %s error: ldap_search: %s\n",
        id, ldap_err2string( rc ) );
      continue;
    }

    /* Iterate through the results.  In this example,
       don't print out all the results.  (It's easier
       to see the output from the other threads this way.) */
    while ( !finished ) {
      rc = ldap_result( ld, msgid, LDAP_MSG_ONE, &amp;zerotime, &amp;res );
      switch ( rc ) {
      case -1:
        rc = ldap_get_lderrno( ld, NULL, NULL );
        fprintf( stderr, "ldap_result: %s\n", ldap_err2string( rc ) );
        finished = 1;
        break;
      case 0:
        break;
      /* Keep track of the number of entries found. */
      case LDAP_RES_SEARCH_ENTRY:
        num_entries++;
        break;
      /* Keep track of the number of search references. */
      case LDAP_RES_SEARCH_REFERENCE:
        num_refs++;
        break;
      case LDAP_RES_SEARCH_RESULT:
        finished = 1;
        parse_rc = ldap_parse_result( ld, res, &amp;rc,
                     NULL, NULL, NULL, NULL, 1 );
        if ( parse_rc != LDAP_SUCCESS ) {
          fprintf( stderr,
                   "Thread %s error: can't parse result code.\n",
                   id );
          break;
        } else {
          if ( rc != LDAP_SUCCESS ) {
            fprintf( stderr, 
                     "Thread %s error: ldap_search: %s\n", 
                     id, ldap_err2string( rc ) );
          } else {
            printf( "Thread %s: Got %d results and %d references.\n", 
                    id, num_entries, num_refs );
          }
        }
        break;
      default:
        break;
      }
    }
  }
}

/* Thread for modifying directory entries.
This thread searches for entries and randomly selects entries from
the search results for modification. */
static void *
modify_thread( char *id )
{
  LDAPMessage      *res;
  LDAPMessage      *e;
  int              i, modentry, num_entries, msgid, rc, parse_rc, finished;
  LDAPMod          mod;
  LDAPMod          *mods[2];
  char             *vals[2];
  char            *dn;
  ldapmsgwrapper  *list, *lmwp, *lastlmwp;
  struct timeval  zerotime;
  zerotime.tv_sec = zerotime.tv_usec = 0L;

  printf( "Starting modify_thread %s.\n", id );
  tsd_setup();
  rc = ldap_search_ext( ld, BASE, SCOPE, "(objectclass=*)",
    NULL, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &amp;msgid );
  if ( rc != LDAP_SUCCESS ) {
    fprintf( stderr, "Thread %s error: Modify thread: "
    "ldap_search_ext: %s\n", id, ldap_err2string( rc ) );
    exit( 1 );
  }
  list = lastlmwp = NULL;
  finished = 0;
  num_entries = 0;
  while ( !finished ) {
    rc = ldap_result( ld, msgid, LDAP_MSG_ONE, &amp;zerotime, &amp;res );
    switch ( rc ) {
    case -1:
      rc = ldap_get_lderrno( ld, NULL, NULL );
      fprintf( stderr, "ldap_result: %s\n", ldap_err2string( rc ) );
      exit( 1 );
      break;
    case 0:
      break;

    /* Keep track of the number of entries found. */
    case LDAP_RES_SEARCH_ENTRY:
      num_entries++;
      if (( lmwp = (ldapmsgwrapper *)
        malloc( sizeof( ldapmsgwrapper ))) == NULL ) {
        fprintf( stderr, "Thread %s: Modify thread: Cannot malloc\n", id );
        exit( 1 );
      }
      lmwp->lmw_messagep = res;
      lmwp->lmw_next = NULL;
      if ( lastlmwp == NULL ) {
        list = lastlmwp = lmwp;
      } else {
        lastlmwp->lmw_next = lmwp;
      }
      lastlmwp = lmwp;
      break;
    case LDAP_RES_SEARCH_REFERENCE:
      break;
    case LDAP_RES_SEARCH_RESULT:
      finished = 1;
      parse_rc = ldap_parse_result( ld, res, &amp;rc, NULL, NULL,
                                    NULL, NULL, 1 );
      if ( parse_rc != LDAP_SUCCESS ) {
        fprintf( stderr,
                 "Thread %s error: can't parse result code.\n",
                 id );
        exit( 1 );
      } else {
        if ( rc != LDAP_SUCCESS ) {
          fprintf( stderr, 
                   "Thread %s error: ldap_search: %s\n", 
                   id, ldap_err2string( rc ) );
        } else {
          printf( "Thread %s: Got %d results.\n", id, num_entries );
        }
      }
      break;
    default:
      break;
    }
  }

  /* Set up the modifications to be made. */
  mods[0] = &amp;mod;
  mods[1] = NULL;
  vals[0] = "bar";
  vals[1] = NULL;

  /* Modify randomly selected entries. */
  for ( ;; ) {

    /* Randomly select the entries. */
    modentry = rand() % num_entries;
    for ( i = 0, lmwp = list; lmwp != NULL &amp;&amp; i &lt; modentry;
        i++, lmwp = lmwp->lmw_next ) {
      /* Keep iterating. */
    }
    if ( lmwp == NULL ) {
      fprintf( stderr,
          "Thread %s: Modify thread could not find entry %d of %d\n",
          id, modentry, num_entries );
      continue;
    }
    e = lmwp->lmw_messagep;
    printf( "Thread %s: Modify thread picked entry %d of %d\n",
            id, i, num_entries );

    /* Perform the modification. */
    dn = ldap_get_dn( ld, e );
    mod.mod_op = LDAP_MOD_REPLACE;
    mod.mod_type = "description";
    mod.mod_values = vals;
    printf( "Thread %s: Modifying (%s)\n", id, dn );
    rc = ldap_modify_ext_s( ld, dn, mods, NULL, NULL );
            if ( rc != LDAP_SUCCESS ) {
      rc = ldap_get_lderrno( ld, NULL, NULL );
      fprintf( stderr, "ldap_modify_ext_s: %s\n", ldap_err2string( rc ) );
    }
    free( dn );
  }
}

/* Thread for adding directory entries.
   This thread randomly generates DNs for entries and attempts to
   add them 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, rc;

  printf( "Starting add_thread %s.\n", id );
  tsd_setup();

  /* Set up the entry to be added. */
  for ( i = 0; i &lt; 5; i++ ) {
    mods[i] = &amp;mod[i];
  }
  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;

  /* Randomly generate DNs and add entries. */
  for ( ;; ) {
    sprintf( name, "%d", rand() );
    sprintf( dn, "cn=%s, " BASE, name );
    cnvals[0] = name;
    snvals[0] = name;
    printf( "Thread %s: Adding entry (%s)\n", id, dn );
    rc = ldap_add_ext_s( ld, dn, mods, NULL, NULL );
            if ( rc != LDAP_SUCCESS ) {
      rc = ldap_get_lderrno( ld, NULL, NULL );
      fprintf( stderr, "ldap_add_ext_s: %s\n", ldap_err2string( rc ) );
    }
  }
}

/* Thread for deleting directory entries.
This thread randomly selects entries for deletion. */
static void *
delete_thread( char *id )
{
  LDAPMessage  *res;
  char    dn[BUFSIZ], name[40];

  printf( "Starting delete_thread %s.\n", id );
  tsd_setup();

  /* Randomly select entries for deletion. */
  for ( ;; ) {
    sprintf( name, "%d", rand() );
    sprintf( dn, "cn=%s, " BASE, name );
    printf( "Thread %s: Deleting entry (%s)\n", id, dn );
    if ( ldap_delete_ext_s( ld, dn, NULL, NULL ) != LDAP_SUCCESS ) {
      ldap_perror( ld, "ldap_delete_ext_s" );
    }
  }
}

/* Function for allocating a mutex. */
static void *
my_mutex_alloc( void )
{
  pthread_mutex_t  *mutexp;
  if ( (mutexp = malloc( sizeof(pthread_mutex_t) )) != NULL ) {
    pthread_mutex_init( mutexp, NULL );
  }
  return( mutexp );
}

/* Function for freeing a mutex. */
static void
my_mutex_free( void *mutexp )
{
  pthread_mutex_destroy( (pthread_mutex_t *) mutexp );
  free( mutexp );
}

/* Error structure. */
struct ldap_error {
  int  le_errno;
  char  *le_matched;
  char  *le_errmsg;
};

/* Function to set up thread-specific data. */
static void
tsd_setup()
{
  void  *tsd;
  tsd = pthread_getspecific( key );
  if ( tsd != NULL ) {
    fprintf( stderr, "tsd non-null!\n" );
    pthread_exit( NULL );
  }
  tsd = (void *) calloc( 1, sizeof(struct ldap_error) );
  pthread_setspecific( key, tsd );
}

/* Function for setting an LDAP error. */
static void
set_ld_error( int err, char *matched, char *errmsg, void *dummy )
{
  struct ldap_error *le;
  le = pthread_getspecific( key );
  le->le_errno = err;
  if ( le->le_matched != NULL ) {
    ldap_memfree( le->le_matched );
  }
  le->le_matched = matched;
  if ( le->le_errmsg != NULL ) {
    ldap_memfree( le->le_errmsg );
  }
  le->le_errmsg = errmsg;
}

/* Function for getting an LDAP error. */
static int
get_ld_error( char **matched, char **errmsg, void *dummy )
{
  struct ldap_error *le;
  le = pthread_getspecific( key );
  if ( matched != NULL ) {
    *matched = le->le_matched;
  }
  if ( errmsg != NULL ) {
    *errmsg = le->le_errmsg;
  }
  return( le->le_errno );
}

/* Function for setting errno. */
static void
set_errno( int err )
{
  errno = err;
}

/* Function for getting errno. */
static int
get_errno( void )
{
  return( errno );
}</programlisting>
</example>
</sect1>
</chapter>
