/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * 
 * The contents of this file are subject to the Mozilla 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/MPL/
 * 
 * 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 the Initial Developer are Copyright (C) 1998-1999
 * the Initial Developer. All Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 * ***** END LICENSE BLOCK ***** */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <values.h>
#include <errno.h>
#include <pthread.h>
#include <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

static void *modify_thread();
static void *add_thread();
static void *delete_thread();
static void *bind_thread();
static void *compare_thread();
static void *search_thread();
static void *my_mutex_alloc();
static void my_mutex_free();
void *my_sema_alloc( void );
void my_sema_free( void * );
int my_sema_wait( void * );
int my_sema_post( void * );
static void set_ld_error();
static int  get_ld_error();
static void set_errno();
static int  get_errno();
static void tsd_setup();
static void tsd_cleanup();
static int get_random_id( void );
static char *get_id_str( int id );

/* 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;
int		maxid = MAXINT;
int		maxops = 0;		/* zero means no limit */
int		range_filters = 0;	/* if non-zero use >= and >= filters */

main( int argc, char **argv )

{
	pthread_attr_t			attr;
	pthread_t			*threadids;
	void				*status;
	struct ldap_thread_fns		tfns;
	struct ldap_extra_thread_fns	extrafns;
	int		rc, c, errflg, i, inited_attr;
	int		doadd, dodelete, domodify, docompare, dosearch, dobind;
	int		option_extthreads, option_restart;
	int		each_thread_count, thread_count;
	extern int	optind;
	extern char	*optarg;

	doadd = dodelete = domodify = docompare = dobind = dosearch = 0;
	option_extthreads = option_restart = 0;
	inited_attr = 0;
	errflg = 0;
	each_thread_count = 1;	/* how many of each type of thread? */
	rc = LDAP_SUCCESS;	/* optimistic */

	while (( c = getopt( argc, argv, "abcdmrsERi:n:o:S:" )) != EOF ) {
		switch( c ) {
		case 'a':		/* perform add operations */
			++doadd;
			break;
		case 'b':		/* perform bind operations */
			++dobind;
			break;
		case 'c':		/* perform compare operations */
			++docompare;
			break;
		case 'd':		/* perform delete operations */
			++dodelete;
			break;
		case 'm':		/* perform modify operations */
			++domodify;
			break;
		case 'r':		/* use range filters in searches */
			++range_filters;
			break;
		case 's':		/* perform search operations */
			++dosearch;
			break;
		case 'E':		/* use extended thread functions */
			++option_extthreads;
			break;
		case 'R':		/* turn on LDAP_OPT_RESTART */
			++option_restart;
			break;
		case 'i':		/* highest # used for entry names */
			maxid = atoi( optarg );
			break;
		case 'n':		/* # threads for each operation */
			if (( each_thread_count = atoi( optarg )) < 1 ) {
				fprintf( stderr, "thread count must be > 0\n" );
				++errflg;
			}
			break;
		case 'o':		/* operations to perform per thread */
			if (( maxops = atoi( optarg )) < 0 ) {
				fprintf( stderr,
				    "operation limit must be >= 0\n" );
				++errflg;
			}
			break;
		case 'S':		/* random number seed */
			if ( *optarg == 'r' ) {
				int	seed = (int)time( (time_t *)0 );
				srandom( seed );
				printf( "Random seed: %d\n", seed );
			} else {
				srandom( atoi( optarg ));
			}
			break;
		default:
			++errflg;
			break;
		}
	}
	
	/* Check command-line syntax. */
	thread_count = each_thread_count * ( doadd + dodelete + domodify
	    + dobind + docompare + dosearch );
	if ( thread_count < 1 ) {
		fprintf( stderr,
		    "Specify at least one of -a, -b, -c, -d, -m, or -s\n" );
		++errflg;
	}

	if ( errflg || argc - optind != 2 ) {
		fprintf( stderr, "usage: %s [-abcdmrsER] [-i id] [-n thr]"
		    " [-o oplim] [-S sd] host port\n", argv[0] );
		fputs( "\nWhere:\n"
		    "\t\"id\" is the highest entry id (name) to use"
			" (default is MAXINT)\n"
		    "\t\"thr\" is the number of threads for each operation"
			" (default is 1)\n"
		    "\t\"oplim\" is the number of ops done by each thread"
			" (default is infinite)\n"
		    "\t\"sd\" is a random() number seed (default is 1).  Use"
			" the letter r for\n"
			"\t\tsd to seed random() using time of day.\n"
		    "\t\"host\" is the hostname of an LDAP directory server\n"
		    "\t\"port\" is the TCP port of the LDAP directory server\n"
		    , stderr );
		fputs( "\nAnd the single character options are:\n"
		    "\t-a\tstart thread(s) to perform add operations\n"
		    "\t-b\tstart thread(s) to perform bind operations\n"
		    "\t-c\tstart thread(s) to perform compare operations\n"
		    "\t-d\tstart thread(s) to perform delete operations\n"
		    "\t-m\tstart thread(s) to perform modify operations\n"
		    "\t-s\tstart thread(s) to perform search operations\n"
		    "\t-r\tuse range filters in searches\n"
		    "\t-E\tinstall LDAP_OPT_EXTRA_THREAD_FN_PTRS\n"
		    "\t-R\tturn on LDAP_OPT_RESTART\n", stderr );

		return( LDAP_PARAM_ERROR );
	}

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

	/* Allocate space for thread ids */
	if (( threadids = (pthread_t *)calloc( thread_count,
	    sizeof( pthread_t ))) == NULL ) {
		rc = LDAP_LOCAL_ERROR;
		goto clean_up_and_return;
	}


	/* Initialize the LDAP session. */
	if (( ld = ldap_init( argv[optind], atoi( argv[optind+1] ))) == NULL ) {
		perror( "ldap_init" );
		rc = LDAP_LOCAL_ERROR;
		goto clean_up_and_return;
	}

	/* Set the function pointers for dealing with mutexes
	   and error information. */
	memset( &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 *) &tfns );
	if ( rc < 0 ) {
		rc = ldap_get_lderrno( ld, NULL, NULL );
		fprintf( stderr,
		    "ldap_set_option (LDAP_OPT_THREAD_FN_PTRS): %s\n",
		    ldap_err2string( rc ) );
		goto clean_up_and_return;
	}

	if ( option_extthreads ) {
		/* Set the function pointers for working with semaphores. */

		memset( &extrafns, '\0', sizeof(struct ldap_extra_thread_fns) );
		extrafns.ltf_mutex_trylock =
		    (int (*)(void *)) pthread_mutex_trylock;
		extrafns.ltf_sema_alloc = (void *(*)(void)) my_sema_alloc;
		extrafns.ltf_sema_free = (void (*)(void *)) my_sema_free;
		extrafns.ltf_sema_wait = (int (*)(void *)) my_sema_wait;
		extrafns.ltf_sema_post = (int (*)(void *)) my_sema_post;

		/* Set up this session to use those function pointers. */

		if ( ldap_set_option( ld, LDAP_OPT_EXTRA_THREAD_FN_PTRS,
		    (void *) &extrafns ) != 0 ) {
			rc = ldap_get_lderrno( ld, NULL, NULL );
			ldap_perror( ld, "ldap_set_option"
			    " (LDAP_OPT_EXTRA_THREAD_FN_PTRS)" );
			goto clean_up_and_return;
		}
	}


	if ( option_restart && ldap_set_option( ld, LDAP_OPT_RESTART,
	    LDAP_OPT_ON ) != 0 ) {
		rc = ldap_get_lderrno( ld, NULL, NULL );
		ldap_perror( ld, "ldap_set_option(LDAP_OPT_RESTART)" );
		goto clean_up_and_return;
	}

	/* 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 ) );
		goto clean_up_and_return;
	}

	/* Initialize the attribute. */
	if ( pthread_attr_init( &attr ) != 0 ) {
		perror( "pthread_attr_init" );
		rc = LDAP_LOCAL_ERROR;
		goto clean_up_and_return;
	}
	++inited_attr;

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

	/* Create all the requested threads */
	thread_count = 0;
	if ( domodify ) {
		for ( i = 0; i < each_thread_count; ++i ) {
			if ( pthread_create( &threadids[thread_count], &attr,
			    modify_thread, get_id_str(thread_count) ) != 0 ) {
				perror( "pthread_create modify_thread" );
			    rc = LDAP_LOCAL_ERROR;
			    goto clean_up_and_return;
			}
			++thread_count;
		}
	}

	if ( doadd ) {
		for ( i = 0; i < each_thread_count; ++i ) {
			if ( pthread_create( &threadids[thread_count], &attr,
			    add_thread, get_id_str(thread_count) ) != 0 ) {
				perror( "pthread_create add_thread" );
			    rc = LDAP_LOCAL_ERROR;
			    goto clean_up_and_return;
			}
			++thread_count;
		}
	}

	if ( dodelete ) {
		for ( i = 0; i < each_thread_count; ++i ) {
			if ( pthread_create( &threadids[thread_count], &attr,
			    delete_thread, get_id_str(thread_count) ) != 0 ) {
				perror( "pthread_create delete_thread" );
			    rc = LDAP_LOCAL_ERROR;
			    goto clean_up_and_return;
			}
			++thread_count;
		}
	}

	if ( dobind ) {
		for ( i = 0; i < each_thread_count; ++i ) {
			if ( pthread_create( &threadids[thread_count], &attr,
			    bind_thread, get_id_str(thread_count) ) != 0 ) {
				perror( "pthread_create bind_thread" );
			    rc = LDAP_LOCAL_ERROR;
			    goto clean_up_and_return;
			}
			++thread_count;
		}
	}

	if ( docompare ) {
		for ( i = 0; i < each_thread_count; ++i ) {
			if ( pthread_create( &threadids[thread_count], &attr,
			    compare_thread, get_id_str(thread_count) ) != 0 ) {
				perror( "pthread_create compare_thread" );
			    rc = LDAP_LOCAL_ERROR;
			    goto clean_up_and_return;
			}
			++thread_count;
		}
	}

	if ( dosearch ) {
		for ( i = 0; i < each_thread_count; ++i ) {
			if ( pthread_create( &threadids[thread_count], &attr,
			    search_thread, get_id_str(thread_count) ) != 0 ) {
				perror( "pthread_create search_thread" );
			    rc = LDAP_LOCAL_ERROR;
			    goto clean_up_and_return;
			}
			++thread_count;
		}
	}

	/* Wait until these threads exit. */
	for ( i = 0; i < thread_count; ++i ) {
		pthread_join( threadids[i], &status );
	}

clean_up_and_return:
	if ( ld != NULL ) {
		set_ld_error( 0, NULL, NULL, NULL );  /* disposes of memory */
		ldap_unbind( ld );
	}
	if ( threadids != NULL ) {
		free( threadids );
	}
	if ( inited_attr ) {
		pthread_attr_destroy( &attr );
	}
	tsd_cleanup();

	return( rc );
}


static void *
modify_thread( char *id )
{
	LDAPMessage	*res;
	LDAPMessage	*e;
	int		i, modentry, num_entries, msgid, parse_rc, finished;
	int		rc, opcount;
	LDAPMod		mod;
	LDAPMod		*mods[2];
	char		*vals[2];
	char		*dn;
	ldapmsgwrapper	*list, *lmwp, *lastlmwp;
	struct timeval	zerotime;
	void		*voidrc = (void *)0;

	zerotime.tv_sec = zerotime.tv_usec = 0L;

	printf( "Starting modify_thread %s.\n", id );
	opcount = 0;
	tsd_setup();

	rc = ldap_search_ext( ld, BASE, SCOPE, "(objectclass=*)",
		NULL, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &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, &zerotime, &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, &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;
		}
	}

	mods[0] = &mod;
	mods[1] = NULL;
	vals[0] = "bar";
	vals[1] = NULL;

	for ( ;; ) {
		modentry = random() % num_entries;
		for ( i = 0, lmwp = list; lmwp != NULL && i < modentry;
		    i++, lmwp = lmwp->lmw_next ) {
			/* NULL */
		}

		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 );
		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 ) {
			fprintf( stderr, "ldap_modify_ext_s: %s\n",
			    ldap_err2string( rc ) );
			if ( rc == LDAP_SERVER_DOWN ) {
				perror( "ldap_modify_ext_s" );
				voidrc = (void *)1;
				goto modify_cleanup_and_return;
			}
		}
		free( dn );

		++opcount;
		if ( maxops != 0 && opcount >= maxops ) {
			break;
		}
	}

modify_cleanup_and_return:
	printf( "Thread %s: attempted %d modify operations\n", id, opcount );
	set_ld_error( 0, NULL, NULL, NULL );	/* disposes of memory */
	tsd_cleanup();
	free( id );
	return voidrc;
}


static void *
add_thread( char *id )
{
	LDAPMod	mod[4];
	LDAPMod	*mods[5];
	char	dn[BUFSIZ], name[40];
	char	*cnvals[2], *snvals[2], *pwdvals[2], *ocvals[3];
	int	i, rc, opcount;
	void	*voidrc = (void *)0;

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

	for ( i = 0; i < 4; i++ ) {
		mods[i] = &mod[i];
	}

	mods[4] = 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;
	mod[3].mod_op = 0;
	mod[3].mod_type = "userPassword";
	mod[3].mod_values = pwdvals;
	pwdvals[1] = NULL;
	mods[4] = NULL;

	for ( ;; ) {
		sprintf( name, "%d", get_random_id() );
		sprintf( dn, "cn=%s, " BASE, name );
		cnvals[0] = name;
		snvals[0] = name;
		pwdvals[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 ) {
			fprintf( stderr, "ldap_add_ext_s: %s\n",
			    ldap_err2string( rc ) );
			if ( rc == LDAP_SERVER_DOWN ) {
				perror( "ldap_add_ext_s" );
				voidrc = (void *)1;
				goto add_cleanup_and_return;
			}
		}

		++opcount;
		if ( maxops != 0 && opcount >= maxops ) {
			break;
		}
	}

add_cleanup_and_return:
	printf( "Thread %s: attempted %d add operations\n", id, opcount );
	set_ld_error( 0, NULL, NULL, NULL );	/* disposes of memory */
	tsd_cleanup();
	free( id );
	return voidrc;
}


static void *
delete_thread( char *id )
{
	LDAPMessage	*res;
	char		dn[BUFSIZ], name[40];
	int		num_entries, msgid, rc, parse_rc, finished, opcount;
	struct timeval	zerotime;
	void		*voidrc = (void *)0;

	zerotime.tv_sec = zerotime.tv_usec = 0L;

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

	rc = ldap_search_ext( ld, BASE, SCOPE, "(objectclass=*)",
		NULL, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msgid );
	if ( rc != LDAP_SUCCESS ) {
		fprintf( stderr, "Thread %s error: Delete thread: "
		"ldap_search_ext: %s\n", id, ldap_err2string( rc ) );
		exit( 1 );
	}

	finished = 0;
	num_entries = 0;
	while ( !finished ) {
		rc = ldap_result( ld, msgid, LDAP_MSG_ONE, &zerotime, &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++;
			break;
		case LDAP_RES_SEARCH_REFERENCE:
			break;
		case LDAP_RES_SEARCH_RESULT:
			finished = 1;
			parse_rc = ldap_parse_result( ld, res, &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;
		}
	}

	for ( ;; ) {
		sprintf( name, "%d", get_random_id() );
		sprintf( dn, "cn=%s, " BASE, name );
		printf( "Thread %s: Deleting entry (%s)\n", id, dn );

		if (( rc = ldap_delete_ext_s( ld, dn, NULL, NULL ))
		    != LDAP_SUCCESS ) {
			ldap_perror( ld, "ldap_delete_ext_s" );
			if ( rc == LDAP_SERVER_DOWN ) {
				perror( "ldap_delete_ext_s" );
				voidrc = (void *)1;
				goto delete_cleanup_and_return;
			}
		}

		++opcount;
		if ( maxops != 0 && opcount >= maxops ) {
			break;
		}
	}

delete_cleanup_and_return:
	printf( "Thread %s: attempted %d delete operations\n", id, opcount );
	set_ld_error( 0, NULL, NULL, NULL );	/* disposes of memory */
	tsd_cleanup();
	free( id );
	return voidrc;
}


static void *
bind_thread( char *id )
{
	char		dn[BUFSIZ], name[40];
	int		rc, opcount;
	void		*voidrc = (void *)0;

	printf( "Starting bind_thread %s.\n", id );
	opcount = 0;
	tsd_setup();

	for ( ;; ) {
		sprintf( name, "%d", get_random_id() );
		sprintf( dn, "cn=%s, " BASE, name );
		printf( "Thread %s: Binding as entry (%s)\n", id, dn );

		if (( rc = ldap_simple_bind_s( ld, dn, name ))
		    != LDAP_SUCCESS ) {
			ldap_perror( ld, "ldap_simple_bind_s" );
			if ( rc == LDAP_SERVER_DOWN ) {
				perror( "ldap_simple_bind_s" );
				voidrc = (void *)1;
				goto bind_cleanup_and_return;
			}
		} else {
			printf( "Thread %s: bound as entry (%s)\n", id, dn );
		}

		++opcount;
		if ( maxops != 0 && opcount >= maxops ) {
			break;
		}
	}

bind_cleanup_and_return:
	printf( "Thread %s: attempted %d bind operations\n", id, opcount );
	set_ld_error( 0, NULL, NULL, NULL );	/* disposes of memory */
	tsd_cleanup();
	free( id );
	return voidrc;
}


static void *
compare_thread( char *id )
{
	char		dn[BUFSIZ], name[40], cmpval[40];
	int		rc, randval, opcount;
	struct berval	bv;
	void		*voidrc = (void *)0;

	printf( "Starting compare_thread %s.\n", id );
	opcount = 0;
	tsd_setup();

	for ( ;; ) {
		randval = get_random_id();
		sprintf( name, "%d", randval );
		sprintf( dn, "cn=%s, " BASE, name );
		sprintf( cmpval, "%d", randval + random() % 3 );
		bv.bv_val = cmpval;
		bv.bv_len = strlen( cmpval );

		printf( "Thread %s: Comparing cn in entry (%s) with %s\n",
		    id, dn, cmpval );

		rc = ldap_compare_ext_s( ld, dn, "cn", &bv, NULL, NULL );
		switch ( rc ) {
		case LDAP_COMPARE_TRUE:
			printf( "Thread %s: entry (%s) contains cn %s\n",
			    id, dn, cmpval );
			break;
		case LDAP_COMPARE_FALSE:
			printf( "Thread %s: entry (%s) doesn't contain cn %s\n",
			    id, dn, cmpval );
			break;
		default:
			ldap_perror( ld, "ldap_compare_ext_s" );
			if ( rc == LDAP_SERVER_DOWN ) {
				perror( "ldap_compare_ext_s" );
				voidrc = (void *)1;
				goto compare_cleanup_and_return;
			}
		}

		++opcount;
		if ( maxops != 0 && opcount >= maxops ) {
			break;
		}
	}

compare_cleanup_and_return:
	printf( "Thread %s: attempted %d compare operations\n", id, opcount );
	set_ld_error( 0, NULL, NULL, NULL );	/* disposes of memory */
	tsd_cleanup();
	free( id );
	return voidrc;
}


static void *
search_thread( char *id )
{
	LDAPMessage	*res, *entry;
	char		*dn, filter[40];
	int		rc, opcount;
	void		*voidrc = (void *)0;

	printf( "Starting search_thread %s.\n", id );
	opcount = 0;
	tsd_setup();

	for ( ;; ) {
		if ( range_filters ) {
			switch( get_random_id() % 3 ) {
			case 0:
				sprintf( filter, "(cn>=%d)", get_random_id());
				break;
			case 1:
				sprintf( filter, "(cn<=%d)", get_random_id());
				break;
			case 2:
				sprintf( filter, "(&(cn>=%d)(cn<=%d))",
				    get_random_id(), get_random_id() );
				break;
			}
		} else {
			sprintf( filter, "cn=%d", get_random_id() );
		}

		printf( "Thread %s: Searching for entry (%s)\n", id, filter );

		res = NULL;
		if (( rc = ldap_search_ext_s( ld, BASE, SCOPE, filter, NULL, 0,
		    NULL, NULL, NULL, 0, &res )) != LDAP_SUCCESS ) {
			ldap_perror( ld, "ldap_search_ext_s" );
			if ( rc == LDAP_SERVER_DOWN ) {
				perror( "ldap_search_ext_s" );
				voidrc = (void *)1;
				goto search_cleanup_and_return;
			}
		}
		if ( res != NULL ) {
			entry = ldap_first_entry( ld, res );
			if ( entry == NULL ) {
				printf( "Thread %s: found no entries\n", id );
			} else {
				dn = ldap_get_dn( ld, entry );
				printf(
				    "Thread %s: found entry (%s); %d total\n",
				    id, dn == NULL ? "(Null)" : dn,
				    ldap_count_entries( ld, res ));
				ldap_memfree( dn );
			}
			ldap_msgfree( res );
		}

		++opcount;
		if ( maxops != 0 && opcount >= maxops ) {
			break;
		}
	}

search_cleanup_and_return:
	printf( "Thread %s: attempted %d search operations\n", id, opcount );
	set_ld_error( 0, NULL, NULL, NULL );	/* disposes of memory */
	tsd_cleanup();
	free( id );
	return voidrc;
}


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 );
}


void *
my_sema_alloc( void )
{
	sema_t *semptr;

	if( (semptr = malloc( sizeof(sema_t) ) ) != NULL ) {
		sema_init( semptr, 0, USYNC_THREAD, NULL );
	}
	return ( semptr );
}


static void
my_mutex_free( void *mutexp )
{
	pthread_mutex_destroy( (pthread_mutex_t *) mutexp );
	free( mutexp );
}


void
my_sema_free( void *semptr )
{
	sema_destroy( (sema_t *) semptr );
	free( semptr );
}


int
my_sema_wait( void *semptr )
{
	if( semptr != NULL )
		return( sema_wait( (sema_t *) semptr ) );
	else
		return( -1 );
}


int
my_sema_post( void *semptr )
{
	if( semptr != NULL )
		return( sema_post( (sema_t *) semptr ) );
	else
		return( -1 );
}


struct ldap_error {
	int	le_errno;
	char	*le_matched;
	char	*le_errmsg;
};


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 );
}


static void
tsd_cleanup()
{
	void	*tsd;

	if (( tsd = pthread_getspecific( key )) != NULL ) {
		pthread_setspecific( key, NULL );
		free( tsd );
	}
}


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;
}


static int
get_ld_error( char **matchedp, char **errmsgp, void *dummy )
{
	struct ldap_error *le;

	le = pthread_getspecific( key );
	if ( matchedp != NULL ) {
		*matchedp = le->le_matched;
	}
	if ( errmsgp != NULL ) {
		*errmsgp = le->le_errmsg;
	}
	return( le->le_errno );
}


static void
set_errno( int err )
{
	errno = err;
}


static int
get_errno( void )
{
	return( errno );
}


static int
get_random_id()
{
	return( random() % maxid );
}


static char *
get_id_str( int id )
{
	char	idstr[ 10 ];

	sprintf( idstr, "%d", id );
	return( strdup( idstr ));
}
