xref: /netbsd-src/external/bsd/openldap/dist/tests/progs/slapd-mtread.c (revision 549b59ed3ccf0d36d3097190a0db27b770f3a839)
1 /*	$NetBSD: slapd-mtread.c,v 1.3 2021/08/14 16:15:03 christos Exp $	*/
2 
3 /* $OpenLDAP$ */
4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5  *
6  * Copyright 1999-2021 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 <sys/cdefs.h>
29 __RCSID("$NetBSD: slapd-mtread.c,v 1.3 2021/08/14 16:15:03 christos Exp $");
30 
31 #include "portable.h"
32 
33 /* Requires libldap with threads */
34 #ifndef NO_THREADS
35 
36 #include <stdio.h>
37 #include "ldap_pvt_thread.h"
38 
39 #include "ac/stdlib.h"
40 
41 #include "ac/ctype.h"
42 #include "ac/param.h"
43 #include "ac/socket.h"
44 #include "ac/string.h"
45 #include "ac/unistd.h"
46 #include "ac/wait.h"
47 
48 #include "ldap.h"
49 #include "lutil.h"
50 
51 #include "ldap_pvt.h"
52 
53 #include "slapd-common.h"
54 
55 #define MAXCONN	512
56 #define LOOPS	100
57 #define RETRIES	0
58 #define DEFAULT_BASE	"ou=people,dc=example,dc=com"
59 
60 static void
61 do_read( LDAP *ld, char *entry,
62 	char **attrs, int noattrs, int nobind, int maxloop,
63 	int force, int idx );
64 
65 static void
66 do_random( LDAP *ld,
67 	char *sbase, char *filter, char **attrs, int noattrs, int nobind,
68 	int force, int idx );
69 
70 static void
71 do_random2( LDAP *ld,
72 	char *sbase, char *filter, char **attrs, int noattrs, int nobind,
73 	int force, 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 struct tester_conn_args	*config;
94 char		*entry = NULL;
95 char		*filter  = NULL;
96 int		force = 0;
97 char		*srchattrs[] = { "1.1", NULL };
98 char		**attrs = srchattrs;
99 int		noattrs = 0;
100 int		nobind = 0;
101 int		threads = 1;
102 int		rwthreads = 0;
103 int		verbose = 0;
104 
105 int		noconns = 1;
106 LDAP		**lds = NULL;
107 
108 static void
thread_error(int idx,char * string)109 thread_error(int idx, char *string)
110 {
111 	char		thrstr[BUFSIZ];
112 
113 	snprintf(thrstr, BUFSIZ, "error on tidx: %d: %s", idx, string);
114 	tester_error( thrstr );
115 }
116 
117 static void
thread_output(int idx,char * string)118 thread_output(int idx, char *string)
119 {
120 	char		thrstr[BUFSIZ];
121 
122 	snprintf(thrstr, BUFSIZ, "tidx: %d says: %s", idx, string);
123 	tester_error( thrstr );
124 }
125 
126 static void
thread_verbose(int idx,char * string)127 thread_verbose(int idx, char *string)
128 {
129 	char		thrstr[BUFSIZ];
130 
131 	if (!verbose)
132 		return;
133 	snprintf(thrstr, BUFSIZ, "tidx: %d says: %s", idx, string);
134 	tester_error( thrstr );
135 }
136 
137 static void
usage(char * name,char opt)138 usage( char *name, char opt )
139 {
140 	if ( opt ) {
141 		fprintf( stderr, "%s: unable to handle option \'%c\'\n\n",
142 			name, opt );
143 	}
144 
145 	fprintf( stderr, "usage: %s " TESTER_COMMON_HELP
146 		"-e <entry> "
147 		"[-A] "
148 		"[-F] "
149 		"[-N] "
150 		"[-v] "
151 		"[-c connections] "
152 		"[-f filter] "
153 		"[-m threads] "
154 		"[-M threads] "
155 		"[-T <attrs>] "
156 		"[<attrs>] "
157 		"\n",
158 		name );
159 	exit( EXIT_FAILURE );
160 }
161 
162 int
main(int argc,char ** argv)163 main( int argc, char **argv )
164 {
165 	int		i;
166 	char		*uri = NULL;
167 	char		*manager = NULL;
168 	struct berval	passwd = { 0, NULL };
169 	char		outstr[BUFSIZ];
170 	int		ptpass;
171 	int		testfail = 0;
172 
173 	config = tester_init( "slapd-mtread", TESTER_READ );
174 
175 	/* by default, tolerate referrals and no such object */
176 	tester_ignore_str2errlist( "REFERRAL,NO_SUCH_OBJECT" );
177 
178 	while ( (i = getopt( argc, argv, TESTER_COMMON_OPTS "Ac:e:Ff:M:m:NT:v" )) != EOF ) {
179 		switch ( i ) {
180 		case 'A':
181 			noattrs++;
182 			break;
183 
184 		case 'N':
185 			nobind = TESTER_INIT_ONLY;
186 			break;
187 
188 		case 'v':
189 			verbose++;
190 			break;
191 
192 		case 'c':		/* the number of connections */
193 			if ( lutil_atoi( &noconns, optarg ) != 0 ) {
194 				usage( argv[0], i );
195 			}
196 			break;
197 
198 		case 'e':		/* DN to search for */
199 			entry = optarg;
200 			break;
201 
202 		case 'f':		/* the search request */
203 			filter = optarg;
204 			break;
205 
206 		case 'F':
207 			force++;
208 			break;
209 
210 		case 'M':		/* the number of R/W threads */
211 			if ( lutil_atoi( &rwthreads, optarg ) != 0 ) {
212 				usage( argv[0], i );
213 			}
214 			if (rwthreads > MAX_THREAD)
215 				rwthreads = MAX_THREAD;
216 			break;
217 
218 		case 'm':		/* the number of threads */
219 			if ( lutil_atoi( &threads, optarg ) != 0 ) {
220 				usage( argv[0], i );
221 			}
222 			if (threads > MAX_THREAD)
223 				threads = MAX_THREAD;
224 			break;
225 
226 		case 'T':
227 			attrs = ldap_str2charray( optarg, "," );
228 			if ( attrs == NULL ) {
229 				usage( argv[0], i );
230 			}
231 			break;
232 
233 		default:
234 			if ( tester_config_opt( config, i, optarg ) == LDAP_SUCCESS ) {
235 				break;
236 			}
237 			usage( argv[0], i );
238 			break;
239 		}
240 	}
241 
242 	if ( entry == NULL )
243 		usage( argv[0], 0 );
244 
245 	if ( *entry == '\0' ) {
246 		fprintf( stderr, "%s: invalid EMPTY entry DN.\n",
247 				argv[0] );
248 		exit( EXIT_FAILURE );
249 	}
250 
251 	if ( argv[optind] != NULL ) {
252 		attrs = &argv[optind];
253 	}
254 
255 	if (noconns < 1)
256 		noconns = 1;
257 	if (noconns > MAXCONN)
258 		noconns = MAXCONN;
259 	lds = (LDAP **) calloc( sizeof(LDAP *), noconns);
260 	if (lds == NULL) {
261 		fprintf( stderr, "%s: Memory error: calloc noconns.\n",
262 				argv[0] );
263 		exit( EXIT_FAILURE );
264 	}
265 
266 	tester_config_finish( config );
267 	ldap_pvt_thread_initialize();
268 
269 	for (i = 0; i < noconns; i++) {
270 		tester_init_ld( &lds[i], config, nobind );
271 	}
272 
273 	snprintf(outstr, BUFSIZ, "MT Test Start: conns: %d (%s)", noconns, uri);
274 	tester_error(outstr);
275 	snprintf(outstr, BUFSIZ, "Threads: RO: %d RW: %d", threads, rwthreads);
276 	tester_error(outstr);
277 
278 	/* Set up read only threads */
279 	for ( i = 0; i < threads; i++ ) {
280 		ldap_pvt_thread_create( &rtid[i], 0, do_onethread, &rtid[i]);
281 		snprintf(outstr, BUFSIZ, "Created RO thread %d", i);
282 		thread_verbose(-1, outstr);
283 	}
284 	/* Set up read/write threads */
285 	for ( i = 0; i < rwthreads; i++ ) {
286 		ldap_pvt_thread_create( &rwtid[i], 0, do_onerwthread, &rwtid[i]);
287 		snprintf(outstr, BUFSIZ, "Created RW thread %d", i + MAX_THREAD);
288 		thread_verbose(-1, outstr);
289 	}
290 
291 	ptpass =  config->outerloops * config->loops;
292 
293 	/* wait for read only threads to complete */
294 	for ( i = 0; i < threads; i++ )
295 		ldap_pvt_thread_join(rtid[i], NULL);
296 	/* wait for read/write threads to complete */
297 	for ( i = 0; i < rwthreads; i++ )
298 		ldap_pvt_thread_join(rwtid[i], NULL);
299 
300 	for(i = 0; i < noconns; i++) {
301 		if ( lds[i] != NULL ) {
302 			ldap_unbind_ext( lds[i], NULL, NULL );
303 		}
304 	}
305 	free( lds );
306 
307 	for ( i = 0; i < threads; i++ ) {
308 		snprintf(outstr, BUFSIZ, "RO thread %d pass=%d fail=%d", i,
309 			rt_pass[i], rt_fail[i]);
310 		tester_error(outstr);
311 		if (rt_fail[i] != 0 || rt_pass[i] != ptpass) {
312 			snprintf(outstr, BUFSIZ, "FAIL RO thread %d", i);
313 			tester_error(outstr);
314 			testfail++;
315 		}
316 	}
317 	for ( i = 0; i < rwthreads; i++ ) {
318 		snprintf(outstr, BUFSIZ, "RW thread %d pass=%d fail=%d", i + MAX_THREAD,
319 			rwt_pass[i], rwt_fail[i]);
320 		tester_error(outstr);
321 		if (rwt_fail[i] != 0 || rwt_pass[i] != ptpass) {
322 			snprintf(outstr, BUFSIZ, "FAIL RW thread %d", i);
323 			tester_error(outstr);
324 			testfail++;
325 		}
326 	}
327 	snprintf(outstr, BUFSIZ, "MT Test complete" );
328 	tester_error(outstr);
329 
330 	if (testfail)
331 		exit( EXIT_FAILURE );
332 	exit( EXIT_SUCCESS );
333 }
334 
335 static void *
do_onethread(void * arg)336 do_onethread( void *arg )
337 {
338 	int		i, j, thisconn;
339 	LDAP		**mlds;
340 	char		thrstr[BUFSIZ];
341 	int		rc, refcnt = 0;
342 	int		idx = (ldap_pvt_thread_t *)arg - rtid;
343 
344 	mlds = (LDAP **) calloc( sizeof(LDAP *), noconns);
345 	if (mlds == NULL) {
346 		thread_error( idx, "Memory error: thread calloc for noconns" );
347 		exit( EXIT_FAILURE );
348 	}
349 
350 	for ( j = 0; j < config->outerloops; j++ ) {
351 		for(i = 0; i < noconns; i++) {
352 			mlds[i] = ldap_dup(lds[i]);
353 			if (mlds[i] == NULL) {
354 				thread_error( idx, "ldap_dup error" );
355 			}
356 		}
357 		rc = ldap_get_option(mlds[0], LDAP_OPT_SESSION_REFCNT, &refcnt);
358 		snprintf(thrstr, BUFSIZ,
359 			"RO Thread conns: %d refcnt: %d (rc = %d)",
360 			noconns, refcnt, rc);
361 		thread_verbose(idx, thrstr);
362 
363 		thisconn = (idx + j) % noconns;
364 		if (thisconn < 0 || thisconn >= noconns)
365 			thisconn = 0;
366 		if (mlds[thisconn] == NULL) {
367 			thread_error( idx, "(failed to dup)");
368 			tester_perror( "ldap_dup", "(failed to dup)" );
369 			exit( EXIT_FAILURE );
370 		}
371 		snprintf(thrstr, BUFSIZ, "Using conn %d", thisconn);
372 		thread_verbose(idx, thrstr);
373 		if ( filter != NULL ) {
374 			if (strchr(filter, '['))
375 				do_random2( mlds[thisconn], entry, filter, attrs,
376 					noattrs, nobind, force, idx );
377 			else
378 				do_random( mlds[thisconn], entry, filter, attrs,
379 					noattrs, nobind, force, idx );
380 
381 		} else {
382 			do_read( mlds[thisconn], entry, attrs, noattrs,
383 				nobind, config->loops, force, idx );
384 		}
385 		for(i = 0; i < noconns; i++) {
386 			(void) ldap_destroy(mlds[i]);
387 			mlds[i] = NULL;
388 		}
389 	}
390 	free( mlds );
391 	return( NULL );
392 }
393 
394 static void *
do_onerwthread(void * arg)395 do_onerwthread( void *arg )
396 {
397 	int		i, j, thisconn;
398 	LDAP		**mlds, *ld;
399 	char		thrstr[BUFSIZ];
400 	char		dn[256], uids[32], cns[32], *base;
401 	LDAPMod		*attrp[5], attrs[4];
402 	char		*oc_vals[] = { "top", "OpenLDAPperson", NULL };
403 	char		*cn_vals[] = { NULL, NULL };
404 	char		*sn_vals[] = { NULL, NULL };
405 	char		*uid_vals[] = { NULL, NULL };
406 	int		ret;
407 	int		adds = 0;
408 	int		dels = 0;
409 	int		rc, refcnt = 0;
410 	int		idx = (ldap_pvt_thread_t *)arg - rtid;
411 
412 	mlds = (LDAP **) calloc( sizeof(LDAP *), noconns);
413 	if (mlds == NULL) {
414 		thread_error( idx, "Memory error: thread calloc for noconns" );
415 		exit( EXIT_FAILURE );
416 	}
417 
418 	snprintf(uids, sizeof(uids), "rwtest%04d", idx);
419 	snprintf(cns, sizeof(cns), "rwtest%04d", idx);
420 	/* add setup */
421 	for (i = 0; i < 4; i++) {
422 		attrp[i] = &attrs[i];
423 		attrs[i].mod_op = 0;
424 	}
425 	attrp[4] = NULL;
426 	attrs[0].mod_type = "objectClass";
427 	attrs[0].mod_values = oc_vals;
428 	attrs[1].mod_type = "cn";
429 	attrs[1].mod_values = cn_vals;
430 	cn_vals[0] = &cns[0];
431 	attrs[2].mod_type = "sn";
432 	attrs[2].mod_values = sn_vals;
433 	sn_vals[0] = &cns[0];
434 	attrs[3].mod_type = "uid";
435 	attrs[3].mod_values = uid_vals;
436 	uid_vals[0] = &uids[0];
437 
438 	for ( j = 0; j < config->outerloops; j++ ) {
439 		for(i = 0; i < noconns; i++) {
440 			mlds[i] = ldap_dup(lds[i]);
441 			if (mlds[i] == NULL) {
442 				thread_error( idx, "ldap_dup error" );
443 			}
444 		}
445 		rc = ldap_get_option(mlds[0], LDAP_OPT_SESSION_REFCNT, &refcnt);
446 		snprintf(thrstr, BUFSIZ,
447 			"RW Thread conns: %d refcnt: %d (rc = %d)",
448 			noconns, refcnt, rc);
449 		thread_verbose(idx, thrstr);
450 
451 		thisconn = (idx + j) % noconns;
452 		if (thisconn < 0 || thisconn >= noconns)
453 			thisconn = 0;
454 		if (mlds[thisconn] == NULL) {
455 			thread_error( idx, "(failed to dup)");
456 			tester_perror( "ldap_dup", "(failed to dup)" );
457 			exit( EXIT_FAILURE );
458 		}
459 		snprintf(thrstr, BUFSIZ, "START RW Thread using conn %d", thisconn);
460 		thread_verbose(idx, thrstr);
461 
462 		ld = mlds[thisconn];
463 		if (entry != NULL)
464 			base = entry;
465 		else
466 			base = DEFAULT_BASE;
467 		snprintf(dn, 256, "cn=%s,%s", cns, base);
468 
469 		adds = 0;
470 		dels = 0;
471 		for (i = 0; i < config->loops; i++) {
472 			ret = ldap_add_ext_s(ld, dn, &attrp[0], NULL, NULL);
473 			if (ret == LDAP_SUCCESS) {
474 				adds++;
475 				ret = ldap_delete_ext_s(ld, dn, NULL, NULL);
476 				if (ret == LDAP_SUCCESS) {
477 					dels++;
478 					rt_pass[idx]++;
479 				} else {
480 					thread_output(idx, ldap_err2string(ret));
481 					rt_fail[idx]++;
482 				}
483 			} else {
484 				thread_output(idx, ldap_err2string(ret));
485 				rt_fail[idx]++;
486 			}
487 		}
488 
489 		snprintf(thrstr, BUFSIZ,
490 			"INNER STOP RW Thread using conn %d (%d/%d)",
491 			thisconn, adds, dels);
492 		thread_verbose(idx, thrstr);
493 
494 		for(i = 0; i < noconns; i++) {
495 			(void) ldap_destroy(mlds[i]);
496 			mlds[i] = NULL;
497 		}
498 	}
499 
500 	free( mlds );
501 	return( NULL );
502 }
503 
504 static void
do_random(LDAP * ld,char * sbase,char * filter,char ** srchattrs,int noattrs,int nobind,int force,int idx)505 do_random( LDAP *ld,
506 	char *sbase, char *filter, char **srchattrs, int noattrs, int nobind,
507 	int force, int idx )
508 {
509 	int  	i = 0, do_retry = config->retries;
510 	char	*attrs[ 2 ];
511 	int     rc = LDAP_SUCCESS;
512 	int	nvalues = 0;
513 	char	**values = NULL;
514 	LDAPMessage *res = NULL, *e = NULL;
515 	char	thrstr[BUFSIZ];
516 
517 	attrs[ 0 ] = LDAP_NO_ATTRS;
518 	attrs[ 1 ] = NULL;
519 
520 	snprintf( thrstr, BUFSIZ,
521 			"Read(%d): base=\"%s\", filter=\"%s\".\n",
522 			config->loops, sbase, filter );
523 	thread_verbose( idx, thrstr );
524 
525 	rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE,
526 		filter, attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &res );
527 	switch ( rc ) {
528 	case LDAP_SIZELIMIT_EXCEEDED:
529 	case LDAP_TIMELIMIT_EXCEEDED:
530 	case LDAP_SUCCESS:
531 		nvalues = ldap_count_entries( ld, res );
532 		if ( nvalues == 0 ) {
533 			if ( rc ) {
534 				tester_ldap_error( ld, "ldap_search_ext_s", NULL );
535 			}
536 			break;
537 		}
538 
539 		values = malloc( ( nvalues + 1 ) * sizeof( char * ) );
540 		if (values == NULL) {
541 			thread_error( idx, "(failed to malloc)");
542 			exit( EXIT_FAILURE );
543 		}
544 		for ( i = 0, e = ldap_first_entry( ld, res ); e != NULL; i++, e = ldap_next_entry( ld, e ) )
545 		{
546 			values[ i ] = ldap_get_dn( ld, e );
547 		}
548 		values[ i ] = NULL;
549 
550 		ldap_msgfree( res );
551 
552 		if ( do_retry == config->retries ) {
553 			snprintf( thrstr, BUFSIZ,
554 				"Read base=\"%s\" filter=\"%s\" got %d values.\n",
555 				sbase, filter, nvalues );
556 			thread_verbose( idx, thrstr );
557 		}
558 
559 		for ( i = 0; i < config->loops; i++ ) {
560 			int	r = ((double)nvalues)*rand()/(RAND_MAX + 1.0);
561 
562 			do_read( ld, values[ r ],
563 				srchattrs, noattrs, nobind, 1, force, idx );
564 		}
565 		for( i = 0; i < nvalues; i++) {
566 			if (values[i] != NULL)
567 				ldap_memfree( values[i] );
568 		}
569 		free( values );
570 		break;
571 
572 	default:
573 		tester_ldap_error( ld, "ldap_search_ext_s", NULL );
574 		break;
575 	}
576 
577 	snprintf( thrstr, BUFSIZ, "Search done (%d).\n", rc );
578 	thread_verbose( idx, thrstr );
579 }
580 
581 /* substitute a generated int into the filter */
582 static void
do_random2(LDAP * ld,char * sbase,char * filter,char ** srchattrs,int noattrs,int nobind,int force,int idx)583 do_random2( LDAP *ld,
584 	char *sbase, char *filter, char **srchattrs, int noattrs, int nobind,
585 	int force, int idx )
586 {
587 	int  	i = 0, do_retry = config->retries;
588 	int     rc = LDAP_SUCCESS;
589 	int		lo, hi, range;
590 	int	flen;
591 	LDAPMessage *res = NULL;
592 	char	*ptr, *ftail;
593 	char	thrstr[BUFSIZ];
594 	char	fbuf[BUFSIZ];
595 
596 	snprintf( thrstr, BUFSIZ,
597 			"Read(%d): base=\"%s\", filter=\"%s\".\n",
598 			config->loops, sbase, filter );
599 	thread_verbose( idx, thrstr );
600 
601 	ptr = strchr(filter, '[');
602 	if (!ptr)
603 		return;
604 	ftail = strchr(filter, ']');
605 	if (!ftail || ftail < ptr)
606 		return;
607 
608 	sscanf(ptr, "[%d-%d]", &lo, &hi);
609 	range = hi - lo + 1;
610 
611 	flen = ptr - filter;
612 	ftail++;
613 
614 	for ( i = 0; i < config->loops; i++ ) {
615 		int	r = ((double)range)*rand()/(RAND_MAX + 1.0);
616 		sprintf(fbuf, "%.*s%d%s", flen, filter, r, ftail);
617 
618 		rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE,
619 				fbuf, srchattrs, noattrs, NULL, NULL, NULL,
620 				LDAP_NO_LIMIT, &res );
621 		if ( res != NULL ) {
622 			ldap_msgfree( res );
623 		}
624 		if ( rc == 0 ) {
625 			rt_pass[idx]++;
626 		} else {
627 			int		first = tester_ignore_err( rc );
628 			char		buf[ BUFSIZ ];
629 
630 			rt_fail[idx]++;
631 			snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry );
632 
633 			/* if ignore.. */
634 			if ( first ) {
635 				/* only log if first occurrence */
636 				if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
637 					tester_ldap_error( ld, buf, NULL );
638 				}
639 				continue;
640 			}
641 
642 			/* busy needs special handling */
643 			tester_ldap_error( ld, buf, NULL );
644 			if ( rc == LDAP_BUSY && do_retry > 0 ) {
645 				do_retry--;
646 				continue;
647 			}
648 			break;
649 		}
650 	}
651 
652 	snprintf( thrstr, BUFSIZ, "Search done (%d).\n", rc );
653 	thread_verbose( idx, thrstr );
654 }
655 
656 static void
do_read(LDAP * ld,char * entry,char ** attrs,int noattrs,int nobind,int maxloop,int force,int idx)657 do_read( LDAP *ld, char *entry,
658 	char **attrs, int noattrs, int nobind, int maxloop,
659 	int force, int idx )
660 {
661 	int  	i = 0, do_retry = config->retries;
662 	int     rc = LDAP_SUCCESS;
663 	char	thrstr[BUFSIZ];
664 
665 retry:;
666 	if ( do_retry == config->retries ) {
667 		snprintf( thrstr, BUFSIZ, "Read(%d): entry=\"%s\".\n",
668 			maxloop, entry );
669 		thread_verbose( idx, thrstr );
670 	}
671 
672 	snprintf(thrstr, BUFSIZ, "LD %p cnt: %d (retried %d) (%s)", \
673 		 (void *) ld, maxloop, (do_retry - config->retries), entry);
674 	thread_verbose( idx, thrstr );
675 
676 	for ( ; i < maxloop; i++ ) {
677 		LDAPMessage *res = NULL;
678 
679 		rc = ldap_search_ext_s( ld, entry, LDAP_SCOPE_BASE,
680 				NULL, attrs, noattrs, NULL, NULL, NULL,
681 				LDAP_NO_LIMIT, &res );
682 		if ( res != NULL ) {
683 			ldap_msgfree( res );
684 		}
685 
686 		if ( rc == 0 ) {
687 			rt_pass[idx]++;
688 		} else {
689 			int		first = tester_ignore_err( rc );
690 			char		buf[ BUFSIZ ];
691 
692 			rt_fail[idx]++;
693 			snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry );
694 
695 			/* if ignore.. */
696 			if ( first ) {
697 				/* only log if first occurrence */
698 				if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
699 					tester_ldap_error( ld, buf, NULL );
700 				}
701 				continue;
702 			}
703 
704 			/* busy needs special handling */
705 			tester_ldap_error( ld, buf, NULL );
706 			if ( rc == LDAP_BUSY && do_retry > 0 ) {
707 				do_retry--;
708 				goto retry;
709 			}
710 			break;
711 		}
712 	}
713 }
714 
715 #else /* NO_THREADS */
716 
717 #include <stdio.h>
718 #include <stdlib.h>
719 
720 int
main(int argc,char ** argv)721 main( int argc, char **argv )
722 {
723 	fprintf( stderr, "%s: not available when configured --without-threads\n", argv[0] );
724 	exit( EXIT_FAILURE );
725 }
726 
727 #endif /* NO_THREADS */
728