From 39f0066db488364aefb67164e8d4671b487835e1 Mon Sep 17 00:00:00 2001
From: Kurt Spanier <ksp@openldap.org>
Date: Mon, 15 Feb 1999 10:49:20 +0000
Subject: [PATCH] Addition of a new Concurrency Test fro testing slapd
 performance and   correctness of locking schemas in backends. In back-bdb2
 open NEXTID during startup and close at shutdown.

---
 doc/devel/todo                      |   2 +-
 servers/slapd/back-bdb2/back-bdb2.h |   9 +-
 servers/slapd/back-bdb2/index.c     |  27 +--
 servers/slapd/back-bdb2/init.c      |   2 -
 servers/slapd/back-bdb2/nextid.c    |  42 ++--
 servers/slapd/back-bdb2/startup.c   |   2 +-
 servers/slapd/back-bdb2/txn.c       |  70 +++++-
 tests/Makefile.in                   |  33 ++-
 tests/data/do_add.1                 |  26 ++
 tests/data/do_read.0                |   5 +
 tests/data/do_search.0              |   5 +
 tests/data/slapd-bdb2-master.conf   |   1 +
 tests/scripts/defines.sh            |   1 +
 tests/scripts/test008-concurrency   |  68 ++++++
 tests/slapd-addel.c                 | 276 ++++++++++++++++++++++
 tests/slapd-read.c                  | 117 +++++++++
 tests/slapd-search.c                | 122 ++++++++++
 tests/slapd-tester.c                | 352 ++++++++++++++++++++++++++++
 18 files changed, 1103 insertions(+), 57 deletions(-)
 create mode 100644 tests/data/do_add.1
 create mode 100644 tests/data/do_read.0
 create mode 100644 tests/data/do_search.0
 create mode 100755 tests/scripts/test008-concurrency
 create mode 100644 tests/slapd-addel.c
 create mode 100644 tests/slapd-read.c
 create mode 100644 tests/slapd-search.c
 create mode 100644 tests/slapd-tester.c

diff --git a/doc/devel/todo b/doc/devel/todo
index c1781181d1..3a8e7e057a 100644
--- a/doc/devel/todo
+++ b/doc/devel/todo
@@ -32,7 +32,7 @@ Modify -lldap to be session-level multithreaded
 Port slapd (incl back-ldbm & tools) to NT
 MajorLDAP - Design and implement an LDAP-enabled mailing list manager
 Slapd-DB2 - Design and implement a backend to take full advantage
-	of the latest DB2 features. 
+	of the latest DB2 features <ksp@openldap.org>. 
 
 
 Medium projects
diff --git a/servers/slapd/back-bdb2/back-bdb2.h b/servers/slapd/back-bdb2/back-bdb2.h
index f3c0ec0d68..126c2fb6ce 100644
--- a/servers/slapd/back-bdb2/back-bdb2.h
+++ b/servers/slapd/back-bdb2/back-bdb2.h
@@ -90,7 +90,6 @@ struct dbcache {
 	int			dbc_refcnt;
 	int			dbc_maxids;
 	int			dbc_maxindirect;
-	time_t		dbc_lastref;
 	long		dbc_blksize;
 	char		*dbc_name;
 	LDBM		dbc_db;
@@ -146,6 +145,11 @@ typedef  struct _bdb2_txn_head {
 #define  BDB2_DB_ID2CHILDREN_FILE   3
 #define  BDB2_DB_OC_IDX_FILE        4
 
+	/*  a file pointer for the NEXTID file
+		(must be opened appropriately at backend
+		entry and closed on leave  */
+	FILE             *nextidFP;
+
 	/*  is the default attribute index set to non-none  */
 	int              withDefIDX;
 #define  BDB2_WITH_DEF_IDX          1
@@ -187,9 +191,6 @@ struct ldbminfo {
 	Avlnode			*li_attrs;
 	int			li_dbcachesize;
 	int			li_dbcachewsync;
-	struct dbcache		li_dbcache[MAXDBCACHE];
-	ldap_pvt_thread_mutex_t		li_dbcache_mutex;
-	ldap_pvt_thread_cond_t		li_dbcache_cv;
 
 	/*  a list of all files of the database  */
 	BDB2_TXN_HEAD		li_txn_head;
diff --git a/servers/slapd/back-bdb2/index.c b/servers/slapd/back-bdb2/index.c
index 4344606632..6cd9d31cf7 100644
--- a/servers/slapd/back-bdb2/index.c
+++ b/servers/slapd/back-bdb2/index.c
@@ -128,26 +128,26 @@ bdb2i_index_read(
 	realval = val;
 	tmpval = NULL;
 	if ( prefix != UNKNOWN_PREFIX ) {
-              unsigned int	len = strlen( val );
+        unsigned int	len = strlen( val );
 
-              if ( (len + 2) < sizeof(buf) ) {
+        if ( (len + 2) < sizeof(buf) ) {
 			realval = buf;
 		} else {
 			/* value + prefix + null */
 			tmpval = (char *) ch_malloc( len + 2 );
 			realval = tmpval;
 		}
-              realval[0] = prefix;
-              strcpy( &realval[1], val );
+        realval[0] = prefix;
+        strcpy( &realval[1], val );
 	}
 
 	key.dptr = realval;
 	key.dsize = strlen( realval ) + 1;
 
 	idl = bdb2i_idl_fetch( be, db, key );
-      if ( tmpval != NULL ) {
-              free( tmpval );
-      }
+    if ( tmpval != NULL ) {
+        free( tmpval );
+    }
 
 	bdb2i_cache_close( be, db );
 
@@ -168,7 +168,6 @@ add_value(
 {
 	int	rc;
 	Datum   key;
-	ID_BLOCK	*idl = NULL;
 	char	*tmpval = NULL;
 	char	*realval = val;
 	char	buf[BUFSIZ];
@@ -180,17 +179,17 @@ add_value(
 	Debug( LDAP_DEBUG_TRACE, "=> add_value( \"%c%s\" )\n", prefix, val, 0 );
 
 	if ( prefix != UNKNOWN_PREFIX ) {
-              unsigned int     len = strlen( val );
+        unsigned int     len = strlen( val );
 
-              if ( (len + 2) < sizeof(buf) ) {
+        if ( (len + 2) < sizeof(buf) ) {
 			realval = buf;
 		} else {
 			/* value + prefix + null */
 			tmpval = (char *) ch_malloc( len + 2 );
 			realval = tmpval;
 		}
-              realval[0] = prefix;
-              strcpy( &realval[1], val );
+        realval[0] = prefix;
+        strcpy( &realval[1], val );
 	}
 
 	key.dptr = realval;
@@ -202,10 +201,6 @@ add_value(
 		free( tmpval );
 	}
 
-	if( idl != NULL ) {
-		bdb2i_idl_free( idl );
-	}
-
 	ldap_pvt_thread_yield();
 
 	/* Debug( LDAP_DEBUG_TRACE, "<= add_value %d\n", rc, 0, 0 ); */
diff --git a/servers/slapd/back-bdb2/init.c b/servers/slapd/back-bdb2/init.c
index eaa557f9de..5f0c8cd3d1 100644
--- a/servers/slapd/back-bdb2/init.c
+++ b/servers/slapd/back-bdb2/init.c
@@ -177,8 +177,6 @@ bdb2i_back_db_init_internal(
 	ldap_pvt_thread_mutex_init( &li->li_add_mutex );
 	ldap_pvt_thread_mutex_init( &li->li_cache.c_mutex );
 	ldap_pvt_thread_mutex_init( &li->li_nextid_mutex );
-	ldap_pvt_thread_mutex_init( &li->li_dbcache_mutex );
-	ldap_pvt_thread_cond_init( &li->li_dbcache_cv );
 
 	/*  initialize the TP file head  */
 	if ( bdb2i_txn_head_init( &li->li_txn_head ) != 0 )
diff --git a/servers/slapd/back-bdb2/nextid.c b/servers/slapd/back-bdb2/nextid.c
index 711603a46c..129221e31e 100644
--- a/servers/slapd/back-bdb2/nextid.c
+++ b/servers/slapd/back-bdb2/nextid.c
@@ -13,32 +13,29 @@
 #include "slap.h"
 #include "back-bdb2.h"
 
+/*  XXX the separate handling of the NEXTID file is in contrast to TP  */
+/*  the NEXTID file is beeing opened during database start-up  */
 static ID
 next_id_read( BackendDB *be )
 {
 	struct ldbminfo	*li = (struct ldbminfo *) be->be_private;
+	BDB2_TXN_HEAD   *head = &li->li_txn_head;
+	FILE*	fp = head->nextidFP;
 	ID  	id;
 	char	buf[20];
-	char*	file = li->li_nextid_file; 
-	FILE*	fp;
 
-	if ( (fp = fopen( file, "r" )) == NULL ) {
-		Debug( LDAP_DEBUG_ANY,
-		    "next_id_read: could not open \"%s\"\n",
-		    file, 0, 0 );
-		return NOID;
-	}
+	/*  set the file pointer to the beginnig of the file  */
+	rewind( fp );
 
+	/*  read the nextid  */
 	if ( fgets( buf, sizeof(buf), fp ) == NULL ) {
 		Debug( LDAP_DEBUG_ANY,
 		   "next_id_read: could not fgets nextid from \"%s\"\n",
-		    file, 0, 0 );
-		fclose( fp );
+		    li->li_nextid_file, 0, 0 );
 		return NOID;
 	}
 
 	id = atol( buf );
-	fclose( fp );
 
 	if(id < 1) {
 		Debug( LDAP_DEBUG_ANY,
@@ -50,31 +47,30 @@ next_id_read( BackendDB *be )
 	return id;
 }
 
+/*  XXX the separate handling of the NEXTID file is in contrast to TP  */
+/*  the NEXTID file is beeing opened during database start-up  */
 static int
 next_id_write( BackendDB *be, ID id )
 {
 	struct ldbminfo	*li = (struct ldbminfo *) be->be_private;
+	BDB2_TXN_HEAD   *head = &li->li_txn_head;
+	FILE*	fp = head->nextidFP;
 	char	buf[20];
-	char*	file = li->li_nextid_file; 
-	FILE*	fp;
-	int		rc;
-
-	if ( (fp = fopen( file, "w" )) == NULL ) {
-		Debug( LDAP_DEBUG_ANY, "next_id_write(%ld): could not open \"%s\"\n",
-		    id, file, 0 );
-		return -1;
-	} 
+	int		rc = 0;
 
-	rc = 0;
+	/*  set the file pointer to the beginnig of the file  */
+	rewind( fp );
 
+	/*  write the nextid  */
 	if ( fprintf( fp, "%ld\n", id ) == EOF ) {
 		Debug( LDAP_DEBUG_ANY, "next_id_write(%ld): cannot fprintf\n",
 		    id, 0, 0 );
 		rc = -1;
 	}
 
-	if( fclose( fp ) != 0 ) {
-		Debug( LDAP_DEBUG_ANY, "next_id_write %ld: cannot fclose\n",
+	/*  if forced flushing of files is in effect, do so  */
+	if( li->li_dbcachewsync && ( fflush( fp ) != 0 )) {
+		Debug( LDAP_DEBUG_ANY, "next_id_write %ld: cannot fflush\n",
 		    id, 0, 0 );
 		rc = -1;
 	}
diff --git a/servers/slapd/back-bdb2/startup.c b/servers/slapd/back-bdb2/startup.c
index 07a4b8d7d9..024d3e238c 100644
--- a/servers/slapd/back-bdb2/startup.c
+++ b/servers/slapd/back-bdb2/startup.c
@@ -19,7 +19,7 @@ static void remove_old_locks( char *home );
 static void
 bdb2i_db_errcall( char *prefix, char *message )
 {
-	Debug( LDAP_DEBUG_ANY, "dbd2_db_errcall(): %s %s", prefix, message, 0 );
+	Debug( LDAP_DEBUG_ANY, "bdb2_db_errcall(): %s %s", prefix, message, 0 );
 }
 
 
diff --git a/servers/slapd/back-bdb2/txn.c b/servers/slapd/back-bdb2/txn.c
index 493dfa87fc..9dc238c4f7 100644
--- a/servers/slapd/back-bdb2/txn.c
+++ b/servers/slapd/back-bdb2/txn.c
@@ -39,16 +39,10 @@ bdb2i_txn_head_init( BDB2_TXN_HEAD *head )
 static void
 bdb2i_init_db_file_cache( struct ldbminfo *li, BDB2_TXN_FILES *fileinfo )
 {
-	time_t      curtime;
 	struct stat st;
 	char        buf[MAXPATHLEN];
 
-	ldap_pvt_thread_mutex_lock( &currenttime_mutex );
-	curtime = currenttime;
-	ldap_pvt_thread_mutex_unlock( &currenttime_mutex );
-
 	fileinfo->dbc_refcnt = 1;
-	fileinfo->dbc_lastref = curtime;
 
 	sprintf( buf, "%s%s%s", li->li_directory, DEFAULT_DIRSEP,
 					fileinfo->dbc_name );
@@ -66,6 +60,14 @@ bdb2i_init_db_file_cache( struct ldbminfo *li, BDB2_TXN_FILES *fileinfo )
 }
 
 
+/*  create a DB file cache entry for a specified index attribute
+	(if not already done); the function is called during config
+	file read for all index'ed attributes; if "default" index with
+	a non-none selection is given, this is remembered for run-time
+	extension of the list of index files; the function is also
+	called before add or modify operations to check for putative
+	new "default" index files; at that time, files are also opened
+*/
 void
 bdb2i_txn_attr_config(
 	struct ldbminfo  *li,
@@ -158,11 +160,45 @@ bdb2i_txn_attr_config(
 }
 
 
+/*  open the NEXTID file for read/write; if it does not exist,
+	create it (access to the file must be preceeded by a rewind)
+*/
+static int
+bdb2i_open_nextid( struct ldbminfo *li )
+{
+	BDB2_TXN_HEAD   *head = &li->li_txn_head;
+	FILE            *fp = NULL;
+	char            *file = li->li_nextid_file;
+
+	/*  try to open the file for read and write  */
+	if ((( fp = fopen( file, "r+" )) == NULL ) &&
+		(( fp = fopen( file, "w+" )) == NULL )) {
+
+			Debug( LDAP_DEBUG_ANY,
+				"bdb2i_open_nextid: could not open \"%s\"\n",
+				file, 0, 0 );
+			return( -1 );
+
+	}
+
+	/*  the file is open for read/write  */
+	head->nextidFP = fp;
+
+	return( 0 );
+}
+
+
+/*  open all DB during startup of the backend (necessary due to TP)
+	additional files may be opened during slapd life-time due to
+	default indexes (must be configured in slapd.conf;
+	see bdb2i_txn_attr_config)
+*/
 int
 bdb2i_txn_open_files( struct ldbminfo *li )
 {
 	BDB2_TXN_HEAD   *head = &li->li_txn_head;
 	BDB2_TXN_FILES  *dbFile;
+	int             rc;
 
 	for ( dbFile = head->dbFiles; dbFile; dbFile = dbFile->next ) {
 		char   fileName[MAXPATHLEN];
@@ -179,7 +215,7 @@ bdb2i_txn_open_files( struct ldbminfo *li )
 			Debug( LDAP_DEBUG_ANY,
 				"bdb2i_txn_open_files(): couldn't open file \"%s\" -- FATAL.\n",
 				dbFile->dbc_name, 0, 0 );
-			return( 1 );
+			return( -1 );
 
 		}
 
@@ -191,10 +227,22 @@ bdb2i_txn_open_files( struct ldbminfo *li )
 
 	}
 
-	return 0;
+	rc = bdb2i_open_nextid( li );
+
+	return rc;
 }
 
 
+/*  close the NEXTID file  */
+static void
+bdb2i_close_nextid( BDB2_TXN_HEAD *head )
+{
+	fclose( head->nextidFP );
+	head->nextidFP = NULL;
+}
+
+
+/*  close all DB files during shutdown of the backend  */
 void
 bdb2i_txn_close_files( BackendDB *be )
 {
@@ -207,9 +255,15 @@ bdb2i_txn_close_files( BackendDB *be )
 		ldbm_close( dbFile->dbc_db );
 
 	}
+
+	bdb2i_close_nextid( head );
+
 }
 
 
+/*  get the db_cache structure associated with a specified
+	DB file (replaces the on-the-fly opening of files in cache_open()
+*/
 BDB2_TXN_FILES *
 bdb2i_get_db_file_cache( struct ldbminfo *li, char *name )
 {
diff --git a/tests/Makefile.in b/tests/Makefile.in
index aea2c7481b..b0626ca9d6 100644
--- a/tests/Makefile.in
+++ b/tests/Makefile.in
@@ -4,7 +4,32 @@
 ## tests Makefile.in for OpenLDAP
 BUILD_BDB2 = @BUILD_BDB2@
 
-bdb2-local:	FORCE
+SRC      = slapd-tester.c slapd-search.c
+PROGRAMS = slapd-tester slapd-search slapd-read slapd-addel
+
+LDAP_INCDIR= ../include
+LDAP_LIBDIR= ../libraries
+
+XLIBS    = -lldap_r -llber -llutil
+
+build-tools: FORCE
+	$(MAKE) $(MFLAGS) load-tools
+
+load-tools: $(PROGRAMS)
+
+slapd-tester: slapd-tester.o 
+	$(LTLINK) -o $@ slapd-tester.o
+
+slapd-search: slapd-search.o $(LDAP_LIBLBER_DEPEND) $(LDAP_LIBLDAP_DEPEND)
+	$(LTLINK) -o $@ slapd-search.o $(LDAP_LIBPATH) $(XLIBS)
+
+slapd-read: slapd-read.o $(LDAP_LIBLBER_DEPEND) $(LDAP_LIBLDAP_DEPEND)
+	$(LTLINK) -o $@ slapd-read.o $(LDAP_LIBPATH) $(XLIBS)
+
+slapd-addel: slapd-addel.o $(LDAP_LIBLBER_DEPEND) $(LDAP_LIBLDAP_DEPEND)
+	$(LTLINK) -o $@ slapd-addel.o $(LDAP_LIBPATH) $(XLIBS)
+
+bdb2-local:	build-tools FORCE
 	@if test "$(BUILD_BDB2)" = "yes" ; then \
 		$(LN_S) $(srcdir)/data . ; \
 		echo "Initiating LDAP tests..." ; \
@@ -14,7 +39,7 @@ bdb2-local:	FORCE
 		echo "run configure with --enable-bdb2" ; \
 	fi
 
-all-local:	FORCE
+all-local:	build-tools FORCE
 	@-$(LN_S) $(srcdir)/data .
 	@echo "Initiating LDAP tests..."; \
 	$(MKDIR) test-db test-repl ; \
@@ -22,8 +47,12 @@ all-local:	FORCE
 
 clean-local:	FORCE
 	$(RM) test-db/[!C]* test-repl/[!C]* *core
+	$(RM) $(PROGRAMS)
+	$(RM) *.o
 
 veryclean-local: FORCE
 	@-$(RM) data
 	$(RM) -r test-db test-repl
+	$(RM) $(PROGRAMS)
+	$(RM) *.o
 
diff --git a/tests/data/do_add.1 b/tests/data/do_add.1
new file mode 100644
index 0000000000..8a57d95c91
--- /dev/null
+++ b/tests/data/do_add.1
@@ -0,0 +1,26 @@
+cn=James A Jones 2, ou=Alumni Association, ou=People, o=University of Michigan, c=US
+objectclass: top
+objectclass: person
+objectclass: organizationalPerson
+objectclass: newPilotPerson
+objectclass: umichPerson
+cn: James A Jones 2
+cn: James Jones
+cn: Jim Jones
+sn: Jones
+postaladdress: Alumni Association $ 111 Maple St $ Ann Arbor, MI 48109
+seealso: cn=All Staff, ou=Groups, o=University of Michigan, c=US
+uid: jaj
+krbname: jaj@umich.edu
+userpassword: jaj
+nobatchupdates: TRUE
+onvacation: FALSE
+homepostaladdress: 3882 Beverly Rd. $ Ann Arbor, MI 48105
+homephone: +1 313 555 4772
+multilinedescription: Outstanding
+title: Mad Cow Researcher, UM Alumni Association
+pager: +1 313 555 3923
+mail: jaj@mail.alumni.umich.edu
+facsimiletelephonenumber: +1 313 555 4332
+telephonenumber: +1 313 555 0895
+
diff --git a/tests/data/do_read.0 b/tests/data/do_read.0
new file mode 100644
index 0000000000..25dc6761dd
--- /dev/null
+++ b/tests/data/do_read.0
@@ -0,0 +1,5 @@
+cn=Barbara Jensen, ou=Information Technology Division, ou=People, o=University of Michigan, c=US
+cn=ITD Staff,ou=Groups,o=University of Michigan,c=US
+ou=Groups, o=University of Michigan, c=US
+ou=Alumni Association, ou=People, o=University of Michigan, c=US
+cn=James A Jones 1, ou=Alumni Association, ou=People, o=University of Michigan, c=US
diff --git a/tests/data/do_search.0 b/tests/data/do_search.0
new file mode 100644
index 0000000000..6e9cd1f3fa
--- /dev/null
+++ b/tests/data/do_search.0
@@ -0,0 +1,5 @@
+cn=Barbara Jensen
+cn=Bjorn Jensen
+cn=James A Jones 1
+cn=Bjorn Jensen
+cn=Alumni Assoc Staff
diff --git a/tests/data/slapd-bdb2-master.conf b/tests/data/slapd-bdb2-master.conf
index 53e6a91f2d..2dad68bfc0 100644
--- a/tests/data/slapd-bdb2-master.conf
+++ b/tests/data/slapd-bdb2-master.conf
@@ -13,6 +13,7 @@ argsfile    ./test-db/slapd.args
 
 backend		bdb2
 home		./test-db
+mpoolsize	2100000
 
 database	bdb2
 cachesize	4
diff --git a/tests/scripts/defines.sh b/tests/scripts/defines.sh
index 50c8497c45..b77c8f47a1 100755
--- a/tests/scripts/defines.sh
+++ b/tests/scripts/defines.sh
@@ -31,6 +31,7 @@ SLURPD=../servers/slurpd/slurpd
 LDAPSEARCH=../clients/tools/ldapsearch
 LDAPMODIFY=../clients/tools/ldapmodify
 LDAPADD=../clients/tools/ldapadd
+SLAPDTESTER=slapd-tester
 LVL=5
 PORT=9009
 SLAVEPORT=9010
diff --git a/tests/scripts/test008-concurrency b/tests/scripts/test008-concurrency
new file mode 100755
index 0000000000..815cdaf79e
--- /dev/null
+++ b/tests/scripts/test008-concurrency
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+if [ $# -eq 0 ]; then
+	SRCDIR="."
+else
+	SRCDIR=$1; shift
+fi
+if [ $# -eq 1 ]; then
+	BDB2=$1; shift
+fi
+
+echo "running defines.sh $SRCDIR $BDB2"
+
+. $SRCDIR/scripts/defines.sh $SRCDIR $BDB2
+
+echo "Datadir is $DATADIR"
+
+echo "Cleaning up in $DBDIR..."
+
+rm -f $DBDIR/[!C]*
+
+echo "Running ldif2ldbm to build slapd database..."
+$LDIF2LDBM -f $CONF -i $LDIF -e ../servers/slapd/tools
+RC=$?
+if [ $RC != 0 ]; then
+	echo "ldif2ldbm failed!"
+	exit $RC
+fi
+
+echo "Starting slapd on TCP/IP port $PORT..."
+$SLAPD -f $CONF -p $PORT -d $LVL $TIMING > $MASTERLOG 2>&1 &
+PID=$!
+
+echo "Waiting 5 seconds for slapd to start..."
+sleep 5
+
+echo "Using tester for concurrent server access..."
+$SLAPDTESTER -b "$BASEDN" -d "$DATADIR" -h localhost -p $PORT -D "$MANAGERDN" -w $PASSWD -l 100
+RC=$?
+
+if [ $RC != 0 ]; then
+	echo "slapd-tester failed!"
+	exit $RC
+fi 
+
+echo "Using ldapsearch to retrieve all the entries..."
+$LDAPSEARCH -L -S "" -b "$BASEDN" -h localhost -p $PORT \
+			'objectClass=*' > $SEARCHOUT 2>&1
+RC=$?
+
+kill -HUP $PID
+
+if [ $RC != 0 ]; then
+	echo "ldapsearch failed!"
+	exit $RC
+fi
+
+echo "Comparing retrieved entries to LDIF file used to create database"
+cmp $SEARCHOUT $LDIF
+if [ $? != 0 ]; then
+	echo "comparison failed - database was not created correctly"
+	exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+
+exit 0
diff --git a/tests/slapd-addel.c b/tests/slapd-addel.c
new file mode 100644
index 0000000000..a6c7238378
--- /dev/null
+++ b/tests/slapd-addel.c
@@ -0,0 +1,276 @@
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+#include <ac/wait.h>
+
+#include <sys/param.h>
+
+#include "lber.h"
+#include "ldap.h"
+
+#define LOOPS	100
+
+static char *
+get_add_entry( char *filename, LDAPMod ***mods );
+
+static void
+do_addel( char *host, int port, char *manager, char *passwd,
+	char *dn, LDAPMod **attrs, int maxloop );
+
+static void
+usage( char *name )
+{
+	fprintf( stderr, "usage: %s [-h <host>] -p port -D <managerDN> -w <passwd> -f <addfile> [-l <loops>]\n",
+			name );
+	exit( 1 );
+}
+
+int
+main( int argc, char **argv )
+{
+	int		i, j;
+	char        *host = "localhost";
+	int			port = -1;
+	char		*manager = NULL;
+	char		*passwd = NULL;
+	char		*filename = NULL;
+	char		*entry = NULL;
+	int			loops = LOOPS;
+	LDAPMod     **attrs = NULL;
+
+	while ( (i = getopt( argc, argv, "h:p:D:w:f:l:" )) != EOF ) {
+		switch( i ) {
+			case 'h':		/* the servers host */
+				host = strdup( optarg );
+			break;
+
+			case 'p':		/* the servers port */
+				port = atoi( optarg );
+				break;
+
+			case 'D':		/* the servers manager */
+				manager = strdup( optarg );
+			break;
+
+			case 'w':		/* the server managers password */
+				passwd = strdup( optarg );
+			break;
+
+			case 'f':		/* file with entry search request */
+				filename = strdup( optarg );
+				break;
+
+			case 'l':		/* the number of loops */
+				loops = atoi( optarg );
+				break;
+
+			default:
+				usage( argv[0] );
+				break;
+		}
+	}
+
+	if (( filename == NULL ) || ( port == -1 ) ||
+				( manager == NULL ) || ( passwd == NULL ))
+		usage( argv[0] );
+
+	entry = get_add_entry( filename, &attrs );
+	if (( entry == NULL ) || ( *entry == '\0' )) {
+
+		fprintf( stderr, "%s: invalid entry DN in file \"%s\".\n",
+				argv[0], filename );
+		exit( 1 );
+
+	}
+
+	if (( attrs == NULL ) || ( *attrs == '\0' )) {
+
+		fprintf( stderr, "%s: invalid attrs in file \"%s\".\n",
+				argv[0], filename );
+		exit( 1 );
+
+	}
+
+	do_addel( host, port, manager, passwd, entry, attrs, loops );
+
+	exit( 0 );
+}
+
+
+#define safe_realloc( ptr, size )   ( ptr == NULL ? malloc( size ) : \
+				realloc( ptr, size ))
+
+
+static void
+addmodifyop( LDAPMod ***pmodsp, int modop, char *attr, char *value, int vlen )
+{
+    LDAPMod		**pmods;
+    int			i, j;
+    struct berval	*bvp;
+
+    pmods = *pmodsp;
+    modop |= LDAP_MOD_BVALUES;
+
+    i = 0;
+    if ( pmods != NULL ) {
+		for ( ; pmods[ i ] != NULL; ++i ) {
+	    	if ( strcasecmp( pmods[ i ]->mod_type, attr ) == 0 &&
+		    	pmods[ i ]->mod_op == modop ) {
+				break;
+	    	}
+		}
+    }
+
+    if ( pmods == NULL || pmods[ i ] == NULL ) {
+		if (( pmods = (LDAPMod **)safe_realloc( pmods, (i + 2) *
+			sizeof( LDAPMod * ))) == NULL ) {
+	    		perror( "safe_realloc" );
+	    		exit( 1 );
+		}
+		*pmodsp = pmods;
+		pmods[ i + 1 ] = NULL;
+		if (( pmods[ i ] = (LDAPMod *)calloc( 1, sizeof( LDAPMod )))
+			== NULL ) {
+	    		perror( "calloc" );
+	    		exit( 1 );
+		}
+		pmods[ i ]->mod_op = modop;
+		if (( pmods[ i ]->mod_type = strdup( attr )) == NULL ) {
+	    	perror( "strdup" );
+	    	exit( 1 );
+		}
+    }
+
+    if ( value != NULL ) {
+		j = 0;
+		if ( pmods[ i ]->mod_bvalues != NULL ) {
+	    	for ( ; pmods[ i ]->mod_bvalues[ j ] != NULL; ++j ) {
+				;
+	    	}
+		}
+		if (( pmods[ i ]->mod_bvalues =
+			(struct berval **)safe_realloc( pmods[ i ]->mod_bvalues,
+			(j + 2) * sizeof( struct berval * ))) == NULL ) {
+	    		perror( "safe_realloc" );
+	    		exit( 1 );
+		}
+		pmods[ i ]->mod_bvalues[ j + 1 ] = NULL;
+		if (( bvp = (struct berval *)malloc( sizeof( struct berval )))
+			== NULL ) {
+	    		perror( "malloc" );
+	    		exit( 1 );
+		}
+		pmods[ i ]->mod_bvalues[ j ] = bvp;
+
+	    bvp->bv_len = vlen;
+	    if (( bvp->bv_val = (char *)malloc( vlen + 1 )) == NULL ) {
+			perror( "malloc" );
+			exit( 1 );
+	    }
+	    SAFEMEMCPY( bvp->bv_val, value, vlen );
+	    bvp->bv_val[ vlen ] = '\0';
+    }
+}
+
+
+static char *
+get_add_entry( char *filename, LDAPMod ***mods )
+{
+	FILE    *fp;
+	char    *entry = NULL;
+
+	if ( fp = fopen( filename, "r" )) {
+		char  line[BUFSIZ];
+
+		if ( fgets( line, BUFSIZ, fp )) {
+			char *nl;
+
+			if (( nl = strchr( line, '\r' )) || ( nl = strchr( line, '\n' )))
+				*nl = '\0';
+			entry = strdup( line );
+
+		}
+
+		while ( fgets( line, BUFSIZ, fp )) {
+			char	*nl;
+			char	*value;
+
+			if (( nl = strchr( line, '\r' )) || ( nl = strchr( line, '\n' )))
+				*nl = '\0';
+
+			if ( *line == '\0' ) break;
+			if ( !( value = strchr( line, ':' ))) break;
+
+			*value++ = '\0'; 
+			while ( *value && isblank( *value )) value++;
+
+			addmodifyop( mods, LDAP_MOD_ADD, line, value, strlen( value ));
+
+		}
+		fclose( fp );
+	}
+
+	return( entry );
+}
+
+
+static void
+do_addel(
+	char *host,
+	int port,
+	char *manager,
+	char *passwd,
+	char *entry,
+	LDAPMod **attrs,
+	int maxloop
+)
+{
+	LDAP	*ld;
+	int  	i;
+
+	if (( ld = ldap_init( host, port )) == NULL ) {
+		perror( "ldap_init" );
+		exit( 1 );
+	}
+
+	if ( ldap_bind_s( ld, manager, passwd, LDAP_AUTH_SIMPLE )
+				!= LDAP_SUCCESS ) {
+		ldap_perror( ld, "ldap_bind" );
+		 exit( 1 );
+	}
+
+
+	fprintf( stderr, "Add/Delete(%d): entry=\"%s\".\n", maxloop, entry );
+
+	for ( i = 0; i < maxloop; i++ ) {
+
+		/* add the entry */
+		if ( ldap_add_s( ld, entry, attrs ) != LDAP_SUCCESS ) {
+
+			ldap_perror( ld, "ldap_add" );
+			break;
+
+		}
+
+		/* wait a second for the add to really complete */
+		sleep( 1 );
+
+		/* now delete the entry again */
+		if ( ldap_delete_s( ld, entry ) != LDAP_SUCCESS ) {
+
+			ldap_perror( ld, "ldap_delete" );
+			break;
+
+		}
+
+	}
+
+	ldap_unbind( ld );
+}
+
+
diff --git a/tests/slapd-read.c b/tests/slapd-read.c
new file mode 100644
index 0000000000..da7c9df22c
--- /dev/null
+++ b/tests/slapd-read.c
@@ -0,0 +1,117 @@
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+#include <ac/wait.h>
+
+#include <sys/param.h>
+
+#include "lber.h"
+#include "ldap.h"
+
+#define LOOPS	100
+
+static void
+do_read( char *host, int port, char *entry, int maxloop );
+
+static void
+usage( char *name )
+{
+	fprintf( stderr, "usage: %s [-h <host>] -p port -e <entry> [-l <loops>]\n",
+			name );
+	exit( 1 );
+}
+
+int
+main( int argc, char **argv )
+{
+	int		i, j;
+	char        *host = "localhost";
+	int			port = -1;
+	char		*entry = NULL;
+	int			loops = LOOPS;
+
+	while ( (i = getopt( argc, argv, "h:p:e:l:" )) != EOF ) {
+		switch( i ) {
+			case 'h':		/* the servers host */
+				host = strdup( optarg );
+			break;
+
+			case 'p':		/* the servers port */
+				port = atoi( optarg );
+				break;
+
+			case 'e':		/* file with entry search request */
+				entry = strdup( optarg );
+				break;
+
+			case 'l':		/* the number of loops */
+				loops = atoi( optarg );
+				break;
+
+			default:
+				usage( argv[0] );
+				break;
+		}
+	}
+
+	if (( entry == NULL ) || ( port == -1 ))
+		usage( argv[0] );
+
+	if ( *entry == '\0' ) {
+
+		fprintf( stderr, "%s: invalid EMPTY entry DN.\n",
+				argv[0] );
+		exit( 1 );
+
+	}
+
+	do_read( host, port, entry, loops );
+
+	exit( 0 );
+}
+
+
+static void
+do_read( char *host, int port, char *entry, int maxloop )
+{
+	LDAP	*ld;
+	int  	i;
+	char	*attrs[] = { "cn", "sn", NULL };
+	char	*filter = "(objectclass=*)";
+
+	if (( ld = ldap_init( host, port )) == NULL ) {
+		perror( "ldap_init" );
+		exit( 1 );
+	}
+
+	if ( ldap_bind_s( ld, NULL, NULL, LDAP_AUTH_SIMPLE ) != LDAP_SUCCESS ) {
+		ldap_perror( ld, "ldap_bind" );
+		 exit( 1 );
+	}
+
+
+	fprintf( stderr, "Read(%d): entry=\"%s\".\n", maxloop, entry );
+
+	for ( i = 0; i < maxloop; i++ ) {
+		 LDAPMessage *res;
+
+		if ( ldap_search_s( ld, entry, LDAP_SCOPE_BASE,
+				filter, attrs, 0, &res ) != LDAP_SUCCESS ) {
+
+			ldap_perror( ld, "ldap_read" );
+			break;
+
+		}
+
+		ldap_msgfree( res );
+	}
+
+	ldap_unbind( ld );
+}
+
+
diff --git a/tests/slapd-search.c b/tests/slapd-search.c
new file mode 100644
index 0000000000..559e3bca2b
--- /dev/null
+++ b/tests/slapd-search.c
@@ -0,0 +1,122 @@
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+#include <ac/wait.h>
+
+#include <sys/param.h>
+
+#include "lber.h"
+#include "ldap.h"
+
+#define LOOPS	100
+
+static void
+do_search( char *host, int port, char *sbase, char *filter, int maxloop );
+
+static void
+usage( char *name )
+{
+	fprintf( stderr, "usage: %s [-h <host>] -p port -b <searchbase> -f <searchfiter> [-l <loops>]\n",
+			name );
+	exit( 1 );
+}
+
+int
+main( int argc, char **argv )
+{
+	int		i, j;
+	char        *host = "localhost";
+	int			port = -1;
+	char        *sbase = NULL;
+	char		*filter  = NULL;
+	int			loops = LOOPS;
+
+	while ( (i = getopt( argc, argv, "h:p:b:f:l:" )) != EOF ) {
+		switch( i ) {
+			case 'h':		/* the servers host */
+				host = strdup( optarg );
+			break;
+
+			case 'p':		/* the servers port */
+				port = atoi( optarg );
+				break;
+
+			case 'b':		/* file with search base */
+				sbase = strdup( optarg );
+			break;
+
+			case 'f':		/* the search request */
+				filter = strdup( optarg );
+				break;
+
+			case 'l':		/* number of loops */
+				loops = atoi( optarg );
+				break;
+
+			default:
+				usage( argv[0] );
+				break;
+		}
+	}
+
+	if (( sbase == NULL ) || ( filter == NULL ) || ( port == -1 ))
+		usage( argv[0] );
+
+	if ( *filter == '\0' ) {
+
+		fprintf( stderr, "%s: invalid EMPTY search filter.\n",
+				argv[0] );
+		exit( 1 );
+
+	}
+
+	do_search( host, port, sbase, filter, loops );
+
+	exit( 0 );
+}
+
+
+static void
+do_search( char *host, int port, char *sbase, char *filter, int maxloop )
+{
+	LDAP	*ld;
+	int  	i;
+	char	*attrs[] = { "cn", "sn", NULL };
+
+	if (( ld = ldap_init( host, port )) == NULL ) {
+		perror( "ldap_init" );
+		exit( 1 );
+	}
+
+	if ( ldap_bind_s( ld, NULL, NULL, LDAP_AUTH_SIMPLE ) != LDAP_SUCCESS ) {
+		ldap_perror( ld, "ldap_bind" );
+		 exit( 1 );
+	}
+
+
+	fprintf( stderr, "Search(%d): base=\"%s\", filter=\"%s\".\n",
+		maxloop, sbase, filter );
+
+	for ( i = 0; i < maxloop; i++ ) {
+		 LDAPMessage *res;
+
+		if ( ldap_search_s( ld, sbase, LDAP_SCOPE_SUBTREE,
+				filter, attrs, 0, &res ) != LDAP_SUCCESS ) {
+
+			ldap_perror( ld, "ldap_search" );
+			break;
+
+		}
+
+		ldap_msgfree( res );
+	}
+
+	ldap_unbind( ld );
+}
+
+
diff --git a/tests/slapd-tester.c b/tests/slapd-tester.c
new file mode 100644
index 0000000000..5bf9d4ed20
--- /dev/null
+++ b/tests/slapd-tester.c
@@ -0,0 +1,352 @@
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/unistd.h>
+#include <ac/wait.h>
+#include <dirent.h>
+
+#include <sys/param.h>
+
+#include "ldapconfig.h"
+
+
+#define SEARCHCMD		"slapd-search"
+#define READCMD			"slapd-read"
+#define ADDCMD			"slapd-addel"
+#define MAXARGS      	100
+#define MAXREQS			20
+#define LOOPS			"100"
+
+#define TSEARCHFILE		"do_search.0"
+#define TREADFILE		"do_read.0"
+#define TADDFILE		"do_add."
+
+static char *get_file_name( char *dirname, char *filename );
+static int  get_search_filters( char *filename, char *filters[] );
+static int  get_read_entries( char *filename, char *entries[] );
+static void fork_child( char *prog, char *args[] );
+static void	wait4kids( int nkidval );
+
+static int      maxkids = 20;
+static int      nkids;
+
+static void
+usage( char *name )
+{
+	fprintf( stderr, "usage: %s [-h <host>] -p <port> -D <manager> -w <passwd> -d <datadir> -b <baseDN> [-j <maxchild>] [-l <loops>]\n", name );
+	exit( 1 );
+}
+
+int
+main( int argc, char **argv )
+{
+	int		i, j;
+	char		*host = "localhost";
+	char		*port = NULL;
+	char		*manager = NULL;
+	char		*passwd = NULL;
+	char		*dirname = NULL;
+	char        *sbase = NULL;
+	char		*loops = LOOPS;
+	DIR			*datadir;
+	struct dirent	*file;
+	char		*sfile = NULL;
+	char		*sreqs[MAXREQS];
+	int         snum = 0;
+	char		*rfile = NULL;
+	char		*rreqs[MAXREQS];
+	int         rnum = 0;
+	char		*afiles[MAXREQS];
+	int         anum = 0;
+	char		*sargs[MAXARGS];
+	int			sanum;
+	char		scmd[MAXPATHLEN];
+	char		*rargs[MAXARGS];
+	int			ranum;
+	char		rcmd[MAXPATHLEN];
+	char		*aargs[MAXARGS];
+	int			aanum;
+	char		acmd[MAXPATHLEN];
+
+	while ( (i = getopt( argc, argv, "h:p:D:w:b:d:j:l:" )) != EOF ) {
+		switch( i ) {
+			case 'h':		/* slapd host */
+				host = strdup( optarg );
+			break;
+
+			case 'p':		/* the servers port number */
+				port = strdup( optarg );
+				break;
+
+			case 'D':		/* slapd manager */
+				manager = strdup( optarg );
+			break;
+
+			case 'w':		/* the managers passwd */
+				passwd = strdup( optarg );
+				break;
+
+			case 'b':		/* the base DN */
+				sbase = strdup( optarg );
+				break;
+
+			case 'd':		/* data directory */
+				dirname = strdup( optarg );
+			break;
+
+			case 'j':		/* the number of parallel clients */
+				maxkids = atoi( optarg );
+				break;
+
+			case 'l':		/* the number of loops per client */
+				loops = strdup( optarg );
+				break;
+
+			default:
+				usage( argv[0] );
+				break;
+		}
+	}
+
+	if (( dirname == NULL ) || ( sbase == NULL ) || ( port == NULL ) ||
+			( manager == NULL ) || ( passwd == NULL ))
+		usage( argv[0] );
+
+	/* get the file list */
+	if ( ( datadir = opendir( dirname )) == NULL ) {
+
+		fprintf( stderr, "%s: couldn't open data directory \"%s\".\n",
+					argv[0], dirname );
+		exit( 1 );
+
+	}
+
+	/*  look for search, read, and add/delete files */
+	for ( file = readdir( datadir ); file; file = readdir( datadir )) {
+
+		if ( !strcasecmp( file->d_name, TSEARCHFILE )) {
+			sfile = get_file_name( dirname, file->d_name );
+			continue;
+		} else if ( !strcasecmp( file->d_name, TREADFILE )) {
+			rfile = get_file_name( dirname, file->d_name );
+			continue;
+		} else if ( !strncasecmp( file->d_name, TADDFILE, strlen( TADDFILE ))
+			&& ( anum < MAXREQS )) {
+			afiles[anum++] = get_file_name( dirname, file->d_name );
+			continue;
+		}
+	}
+
+	/* look for search requests */
+	if ( sfile ) {
+		snum = get_search_filters( sfile, sreqs );
+	}
+
+	/* look for read requests */
+	if ( rfile ) {
+		rnum = get_read_entries( rfile, rreqs );
+	}
+
+	/*
+	 * generate the search clients
+	 */
+
+	sanum = 0;
+	sprintf( scmd, "%s", SEARCHCMD );
+	sargs[sanum++] = scmd;
+	sargs[sanum++] = "-h";
+	sargs[sanum++] = host;
+	sargs[sanum++] = "-p";
+	sargs[sanum++] = port;
+	sargs[sanum++] = "-b";
+	sargs[sanum++] = sbase;
+	sargs[sanum++] = "-l";
+	sargs[sanum++] = loops;
+	sargs[sanum++] = "-f";
+	sargs[sanum++] = NULL;		/* will hold the search request */
+	sargs[sanum++] = NULL;
+
+	/*
+	 * generate the read clients
+	 */
+
+	ranum = 0;
+	sprintf( rcmd, "%s", READCMD );
+	rargs[ranum++] = rcmd;
+	rargs[ranum++] = "-h";
+	rargs[ranum++] = host;
+	rargs[ranum++] = "-p";
+	rargs[ranum++] = port;
+	rargs[ranum++] = "-l";
+	rargs[ranum++] = loops;
+	rargs[ranum++] = "-e";
+	rargs[ranum++] = NULL;		/* will hold the read entry */
+	rargs[ranum++] = NULL;
+
+	/*
+	 * generate the add/delete clients
+	 */
+
+	aanum = 0;
+	sprintf( acmd, "%s", ADDCMD );
+	aargs[aanum++] = acmd;
+	aargs[aanum++] = "-h";
+	aargs[aanum++] = host;
+	aargs[aanum++] = "-p";
+	aargs[aanum++] = port;
+	aargs[aanum++] = "-D";
+	aargs[aanum++] = manager;
+	aargs[aanum++] = "-w";
+	aargs[aanum++] = passwd;
+	aargs[aanum++] = "-l";
+	aargs[aanum++] = loops;
+	aargs[aanum++] = "-f";
+	aargs[aanum++] = NULL;		/* will hold the add data file */
+	aargs[aanum++] = NULL;
+
+	for ( j = 0; j < MAXREQS; j++ ) {
+
+		if ( j < snum ) {
+
+			sargs[sanum - 2] = sreqs[j];
+			fork_child( scmd, sargs );
+
+		}
+
+		if ( j < rnum ) {
+
+			rargs[ranum - 2] = rreqs[j];
+			fork_child( rcmd, rargs );
+
+		}
+
+		if ( j < anum ) {
+
+			aargs[aanum - 2] = afiles[j];
+			fork_child( acmd, aargs );
+
+		}
+
+	}
+
+	wait4kids( -1 );
+
+	exit( 0 );
+}
+
+static char *
+get_file_name( char *dirname, char *filename )
+{
+	char buf[MAXPATHLEN];
+
+	sprintf( buf, "%s%s%s", dirname, DEFAULT_DIRSEP, filename );
+	return( strdup( buf ));
+}
+
+
+static int
+get_search_filters( char *filename, char *filters[] )
+{
+	FILE    *fp;
+	int     filter = 0;
+
+	if ( fp = fopen( filename, "r" )) {
+		char  line[BUFSIZ];
+
+		while (( filter < MAXREQS ) && ( fgets( line, BUFSIZ, fp ))) {
+			char *nl;
+
+			if (( nl = strchr( line, '\r' )) || ( nl = strchr( line, '\n' )))
+				*nl = '\0';
+			filters[filter++] = strdup( line );
+
+		}
+		fclose( fp );
+	}
+
+	return( filter );
+}
+
+
+static int
+get_read_entries( char *filename, char *entries[] )
+{
+	FILE    *fp;
+	int     entry = 0;
+
+	if ( fp = fopen( filename, "r" )) {
+		char  line[BUFSIZ];
+
+		while (( entry < MAXREQS ) && ( fgets( line, BUFSIZ, fp ))) {
+			char *nl;
+
+			if (( nl = strchr( line, '\r' )) || ( nl = strchr( line, '\n' )))
+				*nl = '\0';
+			entries[entry++] = strdup( line );
+
+		}
+		fclose( fp );
+	}
+
+	return( entry );
+}
+
+
+static void
+fork_child( char *prog, char *args[] )
+{
+	int	status, pid;
+
+	wait4kids( maxkids );
+
+	switch ( pid = fork() ) {
+	case 0:		/* child */
+		execvp( prog, args );
+		fprintf( stderr, "%s: ", prog );
+		perror( "execv" );
+		exit( -1 );
+		break;
+
+	case -1:	/* trouble */
+		fprintf( stderr, "Could not fork to run %s\n", prog );
+		perror( "fork" );
+		break;
+
+	default:	/* parent */
+		nkids++;
+		break;
+	}
+}
+
+static void
+wait4kids( int nkidval )
+{
+	int		status;
+	unsigned char	*p;
+
+	while ( nkids >= nkidval ) {
+		wait( &status );
+		p = (unsigned char *) &status;
+		if ( p[sizeof(int) - 1] == 0177 ) {
+			fprintf( stderr,
+			    "stopping: child stopped with signal %d\n",
+			    p[sizeof(int) - 2] );
+		} else if ( p[sizeof(int) - 1] != 0 ) {
+			fprintf( stderr, 
+			    "stopping: child terminated with signal %d\n",
+			    p[sizeof(int) - 1] );
+			exit( p[sizeof(int) - 1] );
+		} else if ( p[sizeof(int) - 2] != 0 ) {
+			fprintf( stderr, 
+			    "stopping: child exited with status %d\n",
+			    p[sizeof(int) - 2] );
+			exit( p[sizeof(int) - 2] );
+		} else {
+			nkids--;
+		}
+	}
+}
-- 
GitLab