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