xref: /netbsd-src/external/bsd/openldap/dist/tests/progs/slapd-mtread.c (revision b7b7574d3bf8eeb51a1fa3977b59142ec6434a55)
1 /*	$NetBSD: slapd-mtread.c,v 1.1.1.1 2014/05/28 09:58:54 tron Exp $	*/
2 
3 /* $OpenLDAP$ */
4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5  *
6  * Copyright 1999-2014 The OpenLDAP Foundation.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted only as authorized by the OpenLDAP
11  * Public License.
12  *
13  * A copy of this license is available in file LICENSE in the
14  * top-level directory of the distribution or, alternatively, at
15  * <http://www.OpenLDAP.org/license.html>.
16  */
17 /* ACKNOWLEDGEMENTS:
18  * This work was initially developed by Kurt Spanier for inclusion
19  * in OpenLDAP Software.
20  */
21 
22 /*
23  * This tool is a MT reader.  It behaves like slapd-read however
24  * with one or more threads simultaneously using the same connection.
25  * If -M is enabled, then M threads will also perform write operations.
26  */
27 
28 #include "portable.h"
29 
30 #include <stdio.h>
31 #include "ldap_pvt_thread.h"
32 
33 #include "ac/stdlib.h"
34 
35 #include "ac/ctype.h"
36 #include "ac/param.h"
37 #include "ac/socket.h"
38 #include "ac/string.h"
39 #include "ac/unistd.h"
40 #include "ac/wait.h"
41 
42 #include "ldap.h"
43 #include "lutil.h"
44 
45 #include "ldap_pvt.h"
46 
47 #include "slapd-common.h"
48 
49 #define MAXCONN	512
50 #define LOOPS	100
51 #define RETRIES	0
52 #define DEFAULT_BASE	"ou=people,dc=example,dc=com"
53 
54 static void
55 do_conn( char *uri, char *manager, struct berval *passwd,
56 	LDAP **ld, int nobind, int maxretries, int conn_num );
57 
58 static void
59 do_read( LDAP *ld, char *entry,
60 	char **attrs, int noattrs, int nobind, int maxloop,
61 	int maxretries, int delay, int force, int chaserefs, int idx );
62 
63 static void
64 do_random( LDAP *ld,
65 	char *sbase, char *filter, char **attrs, int noattrs, int nobind,
66 	int innerloop, int maxretries, int delay, int force, int chaserefs,
67 	int idx );
68 
69 static void
70 do_random2( LDAP *ld,
71 	char *sbase, char *filter, char **attrs, int noattrs, int nobind,
72 	int innerloop, int maxretries, int delay, int force, int chaserefs,
73 	int idx );
74 
75 static void *
76 do_onethread( void *arg );
77 
78 static void *
79 do_onerwthread( void *arg );
80 
81 #define MAX_THREAD	1024
82 /* Use same array for readers and writers, offset writers by MAX_THREAD */
83 int	rt_pass[MAX_THREAD*2];
84 int	rt_fail[MAX_THREAD*2];
85 int	*rwt_pass = rt_pass + MAX_THREAD;
86 int	*rwt_fail = rt_fail + MAX_THREAD;
87 ldap_pvt_thread_t	rtid[MAX_THREAD*2], *rwtid = rtid + MAX_THREAD;
88 
89 /*
90  * Shared globals (command line args)
91  */
92 LDAP		*ld = NULL;
93 char		*entry = NULL;
94 char		*filter  = NULL;
95 int		loops = LOOPS;
96 int		outerloops = 1;
97 int		retries = RETRIES;
98 int		delay = 0;
99 int		force = 0;
100 int		chaserefs = 0;
101 char		*srchattrs[] = { "1.1", NULL };
102 char		**attrs = srchattrs;
103 int		noattrs = 0;
104 int		nobind = 0;
105 int		threads = 1;
106 int		rwthreads = 0;
107 int		verbose = 0;
108 
109 int		noconns = 1;
110 LDAP		**lds = NULL;
111 
112 static void
113 thread_error(int idx, char *string)
114 {
115 	char		thrstr[BUFSIZ];
116 
117 	snprintf(thrstr, BUFSIZ, "error on tidx: %d: %s", idx, string);
118 	tester_error( thrstr );
119 }
120 
121 static void
122 thread_output(int idx, char *string)
123 {
124 	char		thrstr[BUFSIZ];
125 
126 	snprintf(thrstr, BUFSIZ, "tidx: %d says: %s", idx, string);
127 	tester_error( thrstr );
128 }
129 
130 static void
131 thread_verbose(int idx, char *string)
132 {
133 	char		thrstr[BUFSIZ];
134 
135 	if (!verbose)
136 		return;
137 	snprintf(thrstr, BUFSIZ, "tidx: %d says: %s", idx, string);
138 	tester_error( thrstr );
139 }
140 
141 static void
142 usage( char *name )
143 {
144         fprintf( stderr,
145 		"usage: %s "
146 		"-H <uri> | ([-h <host>] -p <port>) "
147 		"-D <manager> "
148 		"-w <passwd> "
149 		"-e <entry> "
150 		"[-A] "
151 		"[-C] "
152 		"[-F] "
153 		"[-N] "
154 		"[-v] "
155 		"[-c connections] "
156 		"[-f filter] "
157 		"[-i <ignore>] "
158 		"[-l <loops>] "
159 		"[-L <outerloops>] "
160 		"[-m threads] "
161 		"[-M threads] "
162 		"[-r <maxretries>] "
163 		"[-t <delay>] "
164 		"[-T <attrs>] "
165 		"[<attrs>] "
166 		"\n",
167 		name );
168 	exit( EXIT_FAILURE );
169 }
170 
171 int
172 main( int argc, char **argv )
173 {
174 	int		i;
175 	char		*uri = NULL;
176 	char		*host = "localhost";
177 	int		port = -1;
178 	char		*manager = NULL;
179 	struct berval	passwd = { 0, NULL };
180 	char		outstr[BUFSIZ];
181 	int		ptpass;
182 	int		testfail = 0;
183 
184 
185 	tester_init( "slapd-mtread", TESTER_READ );
186 
187 	/* by default, tolerate referrals and no such object */
188 	tester_ignore_str2errlist( "REFERRAL,NO_SUCH_OBJECT" );
189 
190 	while ( (i = getopt( argc, argv, "ACc:D:e:Ff:H:h:i:L:l:M:m:p:r:t:T:w:v" )) != EOF ) {
191 		switch ( i ) {
192 		case 'A':
193 			noattrs++;
194 			break;
195 
196 		case 'C':
197 			chaserefs++;
198 			break;
199 
200 		case 'H':		/* the server uri */
201 			uri = strdup( optarg );
202 			break;
203 
204 		case 'h':		/* the servers host */
205 			host = strdup( optarg );
206 
207 		case 'i':
208 			tester_ignore_str2errlist( optarg );
209 			break;
210 
211 		case 'N':
212 			nobind++;
213 			break;
214 
215 		case 'v':
216 			verbose++;
217 			break;
218 
219 		case 'p':		/* the servers port */
220 			if ( lutil_atoi( &port, optarg ) != 0 ) {
221 				usage( argv[0] );
222 			}
223 			break;
224 
225 		case 'D':		/* the servers manager */
226 			manager = strdup( optarg );
227 			break;
228 
229 		case 'w':		/* the server managers password */
230 			passwd.bv_val = strdup( optarg );
231 			passwd.bv_len = strlen( optarg );
232 			memset( optarg, '*', passwd.bv_len );
233 			break;
234 
235 		case 'c':		/* the number of connections */
236 			if ( lutil_atoi( &noconns, optarg ) != 0 ) {
237 				usage( argv[0] );
238 			}
239 			break;
240 
241 		case 'e':		/* DN to search for */
242 			entry = strdup( optarg );
243 			break;
244 
245 		case 'f':		/* the search request */
246 			filter = strdup( optarg );
247 			break;
248 
249 		case 'F':
250 			force++;
251 			break;
252 
253 		case 'l':		/* the number of loops */
254 			if ( lutil_atoi( &loops, optarg ) != 0 ) {
255 				usage( argv[0] );
256 			}
257 			break;
258 
259 		case 'L':		/* the number of outerloops */
260 			if ( lutil_atoi( &outerloops, optarg ) != 0 ) {
261 				usage( argv[0] );
262 			}
263 			break;
264 
265 		case 'M':		/* the number of R/W threads */
266 			if ( lutil_atoi( &rwthreads, optarg ) != 0 ) {
267 				usage( argv[0] );
268 			}
269 			if (rwthreads > MAX_THREAD)
270 				rwthreads = MAX_THREAD;
271 			break;
272 
273 		case 'm':		/* the number of threads */
274 			if ( lutil_atoi( &threads, optarg ) != 0 ) {
275 				usage( argv[0] );
276 			}
277 			if (threads > MAX_THREAD)
278 				threads = MAX_THREAD;
279 			break;
280 
281 		case 'r':		/* the number of retries */
282 			if ( lutil_atoi( &retries, optarg ) != 0 ) {
283 				usage( argv[0] );
284 			}
285 			break;
286 
287 		case 't':		/* delay in seconds */
288 			if ( lutil_atoi( &delay, optarg ) != 0 ) {
289 				usage( argv[0] );
290 			}
291 			break;
292 
293 		case 'T':
294 			attrs = ldap_str2charray( optarg, "," );
295 			if ( attrs == NULL ) {
296 				usage( argv[0] );
297 			}
298 			break;
299 
300 		default:
301 			usage( argv[0] );
302 			break;
303 		}
304 	}
305 
306 	if (( entry == NULL ) || ( port == -1 && uri == NULL ))
307 		usage( argv[0] );
308 
309 	if ( *entry == '\0' ) {
310 		fprintf( stderr, "%s: invalid EMPTY entry DN.\n",
311 				argv[0] );
312 		exit( EXIT_FAILURE );
313 	}
314 
315 	if ( argv[optind] != NULL ) {
316 		attrs = &argv[optind];
317 	}
318 
319 	if (noconns < 1)
320 		noconns = 1;
321 	if (noconns > MAXCONN)
322 		noconns = MAXCONN;
323 	lds = (LDAP **) calloc( sizeof(LDAP *), noconns);
324 	if (lds == NULL) {
325 		fprintf( stderr, "%s: Memory error: calloc noconns.\n",
326 				argv[0] );
327 		exit( EXIT_FAILURE );
328 	}
329 
330 	uri = tester_uri( uri, host, port );
331 	/* One connection and one connection only */
332 	do_conn( uri, manager, &passwd, &ld, nobind, retries, 0 );
333 	lds[0] = ld;
334 	for(i = 1; i < noconns; i++) {
335 		do_conn( uri, manager, &passwd, &lds[i], nobind, retries, i );
336 	}
337 
338 	ldap_pvt_thread_initialize();
339 
340 	snprintf(outstr, BUFSIZ, "MT Test Start: conns: %d (%s)", noconns, uri);
341 	tester_error(outstr);
342 	snprintf(outstr, BUFSIZ, "Threads: RO: %d RW: %d", threads, rwthreads);
343 	tester_error(outstr);
344 
345 	/* Set up read only threads */
346 	for ( i = 0; i < threads; i++ ) {
347 		ldap_pvt_thread_create( &rtid[i], 0, do_onethread, &rtid[i]);
348 		snprintf(outstr, BUFSIZ, "Created RO thread %d", i);
349 		thread_verbose(-1, outstr);
350 	}
351 	/* Set up read/write threads */
352 	for ( i = 0; i < rwthreads; i++ ) {
353 		ldap_pvt_thread_create( &rwtid[i], 0, do_onerwthread, &rwtid[i]);
354 		snprintf(outstr, BUFSIZ, "Created RW thread %d", i + MAX_THREAD);
355 		thread_verbose(-1, outstr);
356 	}
357 
358 	ptpass =  outerloops * loops;
359 
360 	/* wait for read only threads to complete */
361 	for ( i = 0; i < threads; i++ )
362 		ldap_pvt_thread_join(rtid[i], NULL);
363 	/* wait for read/write threads to complete */
364 	for ( i = 0; i < rwthreads; i++ )
365 		ldap_pvt_thread_join(rwtid[i], NULL);
366 
367 	for(i = 0; i < noconns; i++) {
368 		if ( lds[i] != NULL ) {
369 			ldap_unbind_ext( lds[i], NULL, NULL );
370 		}
371 	}
372 	free( lds );
373 
374 	for ( i = 0; i < threads; i++ ) {
375 		snprintf(outstr, BUFSIZ, "RO thread %d pass=%d fail=%d", i,
376 			rt_pass[i], rt_fail[i]);
377 		tester_error(outstr);
378 		if (rt_fail[i] != 0 || rt_pass[i] != ptpass) {
379 			snprintf(outstr, BUFSIZ, "FAIL RO thread %d", i);
380 			tester_error(outstr);
381 			testfail++;
382 		}
383 	}
384 	for ( i = 0; i < rwthreads; i++ ) {
385 		snprintf(outstr, BUFSIZ, "RW thread %d pass=%d fail=%d", i + MAX_THREAD,
386 			rwt_pass[i], rwt_fail[i]);
387 		tester_error(outstr);
388 		if (rwt_fail[i] != 0 || rwt_pass[i] != ptpass) {
389 			snprintf(outstr, BUFSIZ, "FAIL RW thread %d", i);
390 			tester_error(outstr);
391 			testfail++;
392 		}
393 	}
394 	snprintf(outstr, BUFSIZ, "MT Test complete" );
395 	tester_error(outstr);
396 
397 	if (testfail)
398 		exit( EXIT_FAILURE );
399 	exit( EXIT_SUCCESS );
400 }
401 
402 static void *
403 do_onethread( void *arg )
404 {
405 	int		i, j, thisconn;
406 	LDAP		**mlds;
407 	char		thrstr[BUFSIZ];
408 	int		rc, refcnt = 0;
409 	int		idx = (ldap_pvt_thread_t *)arg - rtid;
410 
411 	mlds = (LDAP **) calloc( sizeof(LDAP *), noconns);
412 	if (mlds == NULL) {
413 		thread_error( idx, "Memory error: thread calloc for noconns" );
414 		exit( EXIT_FAILURE );
415 	}
416 
417 	for ( j = 0; j < outerloops; j++ ) {
418 		for(i = 0; i < noconns; i++) {
419 			mlds[i] = ldap_dup(lds[i]);
420 			if (mlds[i] == NULL) {
421 				thread_error( idx, "ldap_dup error" );
422 			}
423 		}
424 		rc = ldap_get_option(mlds[0], LDAP_OPT_SESSION_REFCNT, &refcnt);
425 		snprintf(thrstr, BUFSIZ,
426 			"RO Thread conns: %d refcnt: %d (rc = %d)",
427 			noconns, refcnt, rc);
428 		thread_verbose(idx, thrstr);
429 
430 		thisconn = (idx + j) % noconns;
431 		if (thisconn < 0 || thisconn >= noconns)
432 			thisconn = 0;
433 		if (mlds[thisconn] == NULL) {
434 			thread_error( idx, "(failed to dup)");
435 			tester_perror( "ldap_dup", "(failed to dup)" );
436 			exit( EXIT_FAILURE );
437 		}
438 		snprintf(thrstr, BUFSIZ, "Using conn %d", thisconn);
439 		thread_verbose(idx, thrstr);
440 		if ( filter != NULL ) {
441 			if (strchr(filter, '['))
442 				do_random2( mlds[thisconn], entry, filter, attrs,
443 					noattrs, nobind, loops, retries, delay, force,
444 					chaserefs, idx );
445 			else
446 				do_random( mlds[thisconn], entry, filter, attrs,
447 					noattrs, nobind, loops, retries, delay, force,
448 					chaserefs, idx );
449 
450 		} else {
451 			do_read( mlds[thisconn], entry, attrs,
452 				noattrs, nobind, loops, retries, delay, force,
453 				chaserefs, idx );
454 		}
455 		for(i = 0; i < noconns; i++) {
456 			(void) ldap_destroy(mlds[i]);
457 			mlds[i] = NULL;
458 		}
459 	}
460 	free( mlds );
461 	return( NULL );
462 }
463 
464 static void *
465 do_onerwthread( void *arg )
466 {
467 	int		i, j, thisconn;
468 	LDAP		**mlds, *ld;
469 	char		thrstr[BUFSIZ];
470 	char		dn[256], uids[32], cns[32], *base;
471 	LDAPMod		*attrp[5], attrs[4];
472 	char		*oc_vals[] = { "top", "OpenLDAPperson", NULL };
473 	char		*cn_vals[] = { NULL, NULL };
474 	char		*sn_vals[] = { NULL, NULL };
475 	char		*uid_vals[] = { NULL, NULL };
476 	int		ret;
477 	int		adds = 0;
478 	int		dels = 0;
479 	int		rc, refcnt = 0;
480 	int		idx = (ldap_pvt_thread_t *)arg - rtid;
481 
482 	mlds = (LDAP **) calloc( sizeof(LDAP *), noconns);
483 	if (mlds == NULL) {
484 		thread_error( idx, "Memory error: thread calloc for noconns" );
485 		exit( EXIT_FAILURE );
486 	}
487 
488 	snprintf(uids, sizeof(uids), "rwtest%04d", idx);
489 	snprintf(cns, sizeof(cns), "rwtest%04d", idx);
490 	/* add setup */
491 	for (i = 0; i < 4; i++) {
492 		attrp[i] = &attrs[i];
493 		attrs[i].mod_op = 0;
494 	}
495 	attrp[4] = NULL;
496 	attrs[0].mod_type = "objectClass";
497 	attrs[0].mod_values = oc_vals;
498 	attrs[1].mod_type = "cn";
499 	attrs[1].mod_values = cn_vals;
500 	cn_vals[0] = &cns[0];
501 	attrs[2].mod_type = "sn";
502 	attrs[2].mod_values = sn_vals;
503 	sn_vals[0] = &cns[0];
504 	attrs[3].mod_type = "uid";
505 	attrs[3].mod_values = uid_vals;
506 	uid_vals[0] = &uids[0];
507 
508 	for ( j = 0; j < outerloops; j++ ) {
509 		for(i = 0; i < noconns; i++) {
510 			mlds[i] = ldap_dup(lds[i]);
511 			if (mlds[i] == NULL) {
512 				thread_error( idx, "ldap_dup error" );
513 			}
514 		}
515 		rc = ldap_get_option(mlds[0], LDAP_OPT_SESSION_REFCNT, &refcnt);
516 		snprintf(thrstr, BUFSIZ,
517 			"RW Thread conns: %d refcnt: %d (rc = %d)",
518 			noconns, refcnt, rc);
519 		thread_verbose(idx, thrstr);
520 
521 		thisconn = (idx + j) % noconns;
522 		if (thisconn < 0 || thisconn >= noconns)
523 			thisconn = 0;
524 		if (mlds[thisconn] == NULL) {
525 			thread_error( idx, "(failed to dup)");
526 			tester_perror( "ldap_dup", "(failed to dup)" );
527 			exit( EXIT_FAILURE );
528 		}
529 		snprintf(thrstr, BUFSIZ, "START RW Thread using conn %d", thisconn);
530 		thread_verbose(idx, thrstr);
531 
532 		ld = mlds[thisconn];
533 		if (entry != NULL)
534 			base = entry;
535 		else
536 			base = DEFAULT_BASE;
537 		snprintf(dn, 256, "cn=%s,%s", cns, base);
538 
539 		adds = 0;
540 		dels = 0;
541 		for (i = 0; i < loops; i++) {
542 			ret = ldap_add_ext_s(ld, dn, &attrp[0], NULL, NULL);
543 			if (ret == LDAP_SUCCESS) {
544 				adds++;
545 				ret = ldap_delete_ext_s(ld, dn, NULL, NULL);
546 				if (ret == LDAP_SUCCESS) {
547 					dels++;
548 					rt_pass[idx]++;
549 				} else {
550 					thread_output(idx, ldap_err2string(ret));
551 					rt_fail[idx]++;
552 				}
553 			} else {
554 				thread_output(idx, ldap_err2string(ret));
555 				rt_fail[idx]++;
556 			}
557 		}
558 
559 		snprintf(thrstr, BUFSIZ,
560 			"INNER STOP RW Thread using conn %d (%d/%d)",
561 			thisconn, adds, dels);
562 		thread_verbose(idx, thrstr);
563 
564 		for(i = 0; i < noconns; i++) {
565 			(void) ldap_destroy(mlds[i]);
566 			mlds[i] = NULL;
567 		}
568 	}
569 
570 	free( mlds );
571 	return( NULL );
572 }
573 
574 static void
575 do_conn( char *uri, char *manager, struct berval *passwd,
576 	LDAP **ldp, int nobind, int maxretries, int conn_num )
577 {
578 	LDAP	*ld = NULL;
579 	int	version = LDAP_VERSION3;
580 	int  	i = 0, do_retry = maxretries;
581 	int     rc = LDAP_SUCCESS;
582 	char	thrstr[BUFSIZ];
583 
584 retry:;
585 	ldap_initialize( &ld, uri );
586 	if ( ld == NULL ) {
587 		snprintf( thrstr, BUFSIZ, "connection: %d", conn_num );
588 		tester_error( thrstr );
589 		tester_perror( "ldap_initialize", NULL );
590 		exit( EXIT_FAILURE );
591 	}
592 
593 	(void) ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
594 	(void) ldap_set_option( ld, LDAP_OPT_REFERRALS,
595 		chaserefs ? LDAP_OPT_ON : LDAP_OPT_OFF );
596 
597 	if ( do_retry == maxretries ) {
598 		snprintf( thrstr, BUFSIZ, "do_conn #%d\n", conn_num );
599 		thread_verbose( -1, thrstr );
600 	}
601 
602 	if ( nobind == 0 ) {
603 		rc = ldap_sasl_bind_s( ld, manager, LDAP_SASL_SIMPLE, passwd, NULL, NULL, NULL );
604 		if ( rc != LDAP_SUCCESS ) {
605 			snprintf( thrstr, BUFSIZ, "connection: %d", conn_num );
606 			tester_error( thrstr );
607 			tester_ldap_error( ld, "ldap_sasl_bind_s", NULL );
608 			switch ( rc ) {
609 			case LDAP_BUSY:
610 			case LDAP_UNAVAILABLE:
611 				if ( do_retry > 0 ) {
612 					ldap_unbind_ext( ld, NULL, NULL );
613 					ld = NULL;
614 					do_retry--;
615 					if ( delay != 0 ) {
616 					    sleep( delay );
617 					}
618 					goto retry;
619 				}
620 			/* fallthru */
621 			default:
622 				break;
623 			}
624 			exit( EXIT_FAILURE );
625 		}
626 	}
627 	*ldp = ld;
628 }
629 
630 static void
631 do_random( LDAP *ld,
632 	char *sbase, char *filter, char **srchattrs, int noattrs, int nobind,
633 	int innerloop, int maxretries, int delay, int force, int chaserefs,
634 	int idx )
635 {
636 	int  	i = 0, do_retry = maxretries;
637 	char	*attrs[ 2 ];
638 	int     rc = LDAP_SUCCESS;
639 	int	nvalues = 0;
640 	char	**values = NULL;
641 	LDAPMessage *res = NULL, *e = NULL;
642 	char	thrstr[BUFSIZ];
643 
644 	attrs[ 0 ] = LDAP_NO_ATTRS;
645 	attrs[ 1 ] = NULL;
646 
647 	snprintf( thrstr, BUFSIZ,
648 			"Read(%d): base=\"%s\", filter=\"%s\".\n",
649 			innerloop, sbase, filter );
650 	thread_verbose( idx, thrstr );
651 
652 	rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE,
653 		filter, attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res );
654 	switch ( rc ) {
655 	case LDAP_SIZELIMIT_EXCEEDED:
656 	case LDAP_TIMELIMIT_EXCEEDED:
657 	case LDAP_SUCCESS:
658 		nvalues = ldap_count_entries( ld, res );
659 		if ( nvalues == 0 ) {
660 			if ( rc ) {
661 				tester_ldap_error( ld, "ldap_search_ext_s", NULL );
662 			}
663 			break;
664 		}
665 
666 		values = malloc( ( nvalues + 1 ) * sizeof( char * ) );
667 		for ( i = 0, e = ldap_first_entry( ld, res ); e != NULL; i++, e = ldap_next_entry( ld, e ) )
668 		{
669 			values[ i ] = ldap_get_dn( ld, e );
670 		}
671 		values[ i ] = NULL;
672 
673 		ldap_msgfree( res );
674 
675 		if ( do_retry == maxretries ) {
676 			snprintf( thrstr, BUFSIZ,
677 				"Read base=\"%s\" filter=\"%s\" got %d values.\n",
678 				sbase, filter, nvalues );
679 			thread_verbose( idx, thrstr );
680 		}
681 
682 		for ( i = 0; i < innerloop; i++ ) {
683 			int	r = ((double)nvalues)*rand()/(RAND_MAX + 1.0);
684 
685 			do_read( ld, values[ r ],
686 				srchattrs, noattrs, nobind, 1, maxretries,
687 				delay, force, chaserefs, idx );
688 		}
689 		for( i = 0; i < nvalues; i++) {
690 			if (values[i] != NULL)
691 				ldap_memfree( values[i] );
692 		}
693 		free( values );
694 		break;
695 
696 	default:
697 		tester_ldap_error( ld, "ldap_search_ext_s", NULL );
698 		break;
699 	}
700 
701 	snprintf( thrstr, BUFSIZ, "Search done (%d).\n", rc );
702 	thread_verbose( idx, thrstr );
703 }
704 
705 /* substitute a generated int into the filter */
706 static void
707 do_random2( LDAP *ld,
708 	char *sbase, char *filter, char **srchattrs, int noattrs, int nobind,
709 	int innerloop, int maxretries, int delay, int force, int chaserefs,
710 	int idx )
711 {
712 	int  	i = 0, do_retry = maxretries;
713 	int     rc = LDAP_SUCCESS;
714 	int		lo, hi, range;
715 	int	flen;
716 	LDAPMessage *res = NULL, *e = NULL;
717 	char	*ptr, *ftail;
718 	char	thrstr[BUFSIZ];
719 	char	fbuf[BUFSIZ];
720 
721 	snprintf( thrstr, BUFSIZ,
722 			"Read(%d): base=\"%s\", filter=\"%s\".\n",
723 			innerloop, sbase, filter );
724 	thread_verbose( idx, thrstr );
725 
726 	ptr = strchr(filter, '[');
727 	if (!ptr)
728 		return;
729 	ftail = strchr(filter, ']');
730 	if (!ftail || ftail < ptr)
731 		return;
732 
733 	sscanf(ptr, "[%d-%d]", &lo, &hi);
734 	range = hi - lo + 1;
735 
736 	flen = ptr - filter;
737 	ftail++;
738 
739 	for ( i = 0; i < innerloop; i++ ) {
740 		int	r = ((double)range)*rand()/(RAND_MAX + 1.0);
741 		sprintf(fbuf, "%.*s%d%s", flen, filter, r, ftail);
742 
743 		rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE,
744 				fbuf, srchattrs, noattrs, NULL, NULL, NULL,
745 				LDAP_NO_LIMIT, &res );
746 		if ( res != NULL ) {
747 			ldap_msgfree( res );
748 		}
749 		if ( rc == 0 ) {
750 			rt_pass[idx]++;
751 		} else {
752 			int		first = tester_ignore_err( rc );
753 			char		buf[ BUFSIZ ];
754 
755 			rt_fail[idx]++;
756 			snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry );
757 
758 			/* if ignore.. */
759 			if ( first ) {
760 				/* only log if first occurrence */
761 				if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
762 					tester_ldap_error( ld, buf, NULL );
763 				}
764 				continue;
765 			}
766 
767 			/* busy needs special handling */
768 			tester_ldap_error( ld, buf, NULL );
769 			if ( rc == LDAP_BUSY && do_retry > 0 ) {
770 				do_retry--;
771 				continue;
772 			}
773 			break;
774 		}
775 	}
776 
777 	snprintf( thrstr, BUFSIZ, "Search done (%d).\n", rc );
778 	thread_verbose( idx, thrstr );
779 }
780 
781 static void
782 do_read( LDAP *ld, char *entry,
783 	char **attrs, int noattrs, int nobind, int maxloop,
784 	int maxretries, int delay, int force, int chaserefs, int idx )
785 {
786 	int  	i = 0, do_retry = maxretries;
787 	int     rc = LDAP_SUCCESS;
788 	char	thrstr[BUFSIZ];
789 
790 retry:;
791 	if ( do_retry == maxretries ) {
792 		snprintf( thrstr, BUFSIZ, "Read(%d): entry=\"%s\".\n",
793 			maxloop, entry );
794 		thread_verbose( idx, thrstr );
795 	}
796 
797 	snprintf(thrstr, BUFSIZ, "LD %p cnt: %d (retried %d) (%s)", \
798 		 (void *) ld, maxloop, (do_retry - maxretries), entry);
799 	thread_verbose( idx, thrstr );
800 
801 	for ( ; i < maxloop; i++ ) {
802 		LDAPMessage *res = NULL;
803 
804 		rc = ldap_search_ext_s( ld, entry, LDAP_SCOPE_BASE,
805 				NULL, attrs, noattrs, NULL, NULL, NULL,
806 				LDAP_NO_LIMIT, &res );
807 		if ( res != NULL ) {
808 			ldap_msgfree( res );
809 		}
810 
811 		if ( rc == 0 ) {
812 			rt_pass[idx]++;
813 		} else {
814 			int		first = tester_ignore_err( rc );
815 			char		buf[ BUFSIZ ];
816 
817 			rt_fail[idx]++;
818 			snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry );
819 
820 			/* if ignore.. */
821 			if ( first ) {
822 				/* only log if first occurrence */
823 				if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
824 					tester_ldap_error( ld, buf, NULL );
825 				}
826 				continue;
827 			}
828 
829 			/* busy needs special handling */
830 			tester_ldap_error( ld, buf, NULL );
831 			if ( rc == LDAP_BUSY && do_retry > 0 ) {
832 				do_retry--;
833 				goto retry;
834 			}
835 			break;
836 		}
837 	}
838 }
839