Home | History | Annotate | Line # | Download | only in progs
      1 /*	$NetBSD: slapd-mtread.c,v 1.4 2025/09/05 21:16:33 christos Exp $	*/
      2 
      3 /* $OpenLDAP$ */
      4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      5  *
      6  * Copyright 1999-2024 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.4 2025/09/05 21:16:33 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
    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
    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
    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
    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
    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 *
    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 *
    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
    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 			ldap_msgfree( res );
    537 			break;
    538 		}
    539 
    540 		values = malloc( ( nvalues + 1 ) * sizeof( char * ) );
    541 		if (values == NULL) {
    542 			thread_error( idx, "(failed to malloc)");
    543 			exit( EXIT_FAILURE );
    544 		}
    545 		for ( i = 0, e = ldap_first_entry( ld, res ); e != NULL; i++, e = ldap_next_entry( ld, e ) )
    546 		{
    547 			values[ i ] = ldap_get_dn( ld, e );
    548 		}
    549 		values[ i ] = NULL;
    550 
    551 		ldap_msgfree( res );
    552 
    553 		if ( do_retry == config->retries ) {
    554 			snprintf( thrstr, BUFSIZ,
    555 				"Read base=\"%s\" filter=\"%s\" got %d values.\n",
    556 				sbase, filter, nvalues );
    557 			thread_verbose( idx, thrstr );
    558 		}
    559 
    560 		for ( i = 0; i < config->loops; i++ ) {
    561 			int	r = ((double)nvalues)*rand()/(RAND_MAX + 1.0);
    562 
    563 			do_read( ld, values[ r ],
    564 				srchattrs, noattrs, nobind, 1, force, idx );
    565 		}
    566 		for( i = 0; i < nvalues; i++) {
    567 			if (values[i] != NULL)
    568 				ldap_memfree( values[i] );
    569 		}
    570 		free( values );
    571 		break;
    572 
    573 	default:
    574 		tester_ldap_error( ld, "ldap_search_ext_s", NULL );
    575 		ldap_msgfree( res );
    576 		break;
    577 	}
    578 
    579 	snprintf( thrstr, BUFSIZ, "Search done (%d).\n", rc );
    580 	thread_verbose( idx, thrstr );
    581 }
    582 
    583 /* substitute a generated int into the filter */
    584 static void
    585 do_random2( LDAP *ld,
    586 	char *sbase, char *filter, char **srchattrs, int noattrs, int nobind,
    587 	int force, int idx )
    588 {
    589 	int  	i = 0, do_retry = config->retries;
    590 	int     rc = LDAP_SUCCESS;
    591 	int		lo, hi, range;
    592 	int	flen;
    593 	LDAPMessage *res = NULL;
    594 	char	*ptr, *ftail;
    595 	char	thrstr[BUFSIZ];
    596 	char	fbuf[BUFSIZ];
    597 
    598 	snprintf( thrstr, BUFSIZ,
    599 			"Read(%d): base=\"%s\", filter=\"%s\".\n",
    600 			config->loops, sbase, filter );
    601 	thread_verbose( idx, thrstr );
    602 
    603 	ptr = strchr(filter, '[');
    604 	if (!ptr)
    605 		return;
    606 	ftail = strchr(filter, ']');
    607 	if (!ftail || ftail < ptr)
    608 		return;
    609 
    610 	sscanf(ptr, "[%d-%d]", &lo, &hi);
    611 	range = hi - lo + 1;
    612 
    613 	flen = ptr - filter;
    614 	ftail++;
    615 
    616 	for ( i = 0; i < config->loops; i++ ) {
    617 		int	r = ((double)range)*rand()/(RAND_MAX + 1.0);
    618 		sprintf(fbuf, "%.*s%d%s", flen, filter, r, ftail);
    619 
    620 		rc = ldap_search_ext_s( ld, sbase, LDAP_SCOPE_SUBTREE,
    621 				fbuf, srchattrs, noattrs, NULL, NULL, NULL,
    622 				LDAP_NO_LIMIT, &res );
    623 		if ( res != NULL ) {
    624 			ldap_msgfree( res );
    625 		}
    626 		if ( rc == 0 ) {
    627 			rt_pass[idx]++;
    628 		} else {
    629 			int		first = tester_ignore_err( rc );
    630 			char		buf[ BUFSIZ ];
    631 
    632 			rt_fail[idx]++;
    633 			snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry );
    634 
    635 			/* if ignore.. */
    636 			if ( first ) {
    637 				/* only log if first occurrence */
    638 				if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
    639 					tester_ldap_error( ld, buf, NULL );
    640 				}
    641 				continue;
    642 			}
    643 
    644 			/* busy needs special handling */
    645 			tester_ldap_error( ld, buf, NULL );
    646 			if ( rc == LDAP_BUSY && do_retry > 0 ) {
    647 				do_retry--;
    648 				continue;
    649 			}
    650 			break;
    651 		}
    652 	}
    653 
    654 	snprintf( thrstr, BUFSIZ, "Search done (%d).\n", rc );
    655 	thread_verbose( idx, thrstr );
    656 }
    657 
    658 static void
    659 do_read( LDAP *ld, char *entry,
    660 	char **attrs, int noattrs, int nobind, int maxloop,
    661 	int force, int idx )
    662 {
    663 	int  	i = 0, do_retry = config->retries;
    664 	int     rc = LDAP_SUCCESS;
    665 	char	thrstr[BUFSIZ];
    666 
    667 retry:;
    668 	if ( do_retry == config->retries ) {
    669 		snprintf( thrstr, BUFSIZ, "Read(%d): entry=\"%s\".\n",
    670 			maxloop, entry );
    671 		thread_verbose( idx, thrstr );
    672 	}
    673 
    674 	snprintf(thrstr, BUFSIZ, "LD %p cnt: %d (retried %d) (%s)", \
    675 		 (void *) ld, maxloop, (do_retry - config->retries), entry);
    676 	thread_verbose( idx, thrstr );
    677 
    678 	for ( ; i < maxloop; i++ ) {
    679 		LDAPMessage *res = NULL;
    680 
    681 		rc = ldap_search_ext_s( ld, entry, LDAP_SCOPE_BASE,
    682 				NULL, attrs, noattrs, NULL, NULL, NULL,
    683 				LDAP_NO_LIMIT, &res );
    684 		if ( res != NULL ) {
    685 			ldap_msgfree( res );
    686 		}
    687 
    688 		if ( rc == 0 ) {
    689 			rt_pass[idx]++;
    690 		} else {
    691 			int		first = tester_ignore_err( rc );
    692 			char		buf[ BUFSIZ ];
    693 
    694 			rt_fail[idx]++;
    695 			snprintf( buf, sizeof( buf ), "ldap_search_ext_s(%s)", entry );
    696 
    697 			/* if ignore.. */
    698 			if ( first ) {
    699 				/* only log if first occurrence */
    700 				if ( ( force < 2 && first > 0 ) || abs(first) == 1 ) {
    701 					tester_ldap_error( ld, buf, NULL );
    702 				}
    703 				continue;
    704 			}
    705 
    706 			/* busy needs special handling */
    707 			tester_ldap_error( ld, buf, NULL );
    708 			if ( rc == LDAP_BUSY && do_retry > 0 ) {
    709 				do_retry--;
    710 				goto retry;
    711 			}
    712 			break;
    713 		}
    714 	}
    715 }
    716 
    717 #else /* NO_THREADS */
    718 
    719 #include <stdio.h>
    720 #include <stdlib.h>
    721 
    722 int
    723 main( int argc, char **argv )
    724 {
    725 	fprintf( stderr, "%s: not available when configured --without-threads\n", argv[0] );
    726 	exit( EXIT_FAILURE );
    727 }
    728 
    729 #endif /* NO_THREADS */
    730