]> git.sur5r.net Git - openldap/blob - tests/progs/slapd-tester.c
make referral chasing optional
[openldap] / tests / progs / slapd-tester.c
1 /* $OpenLDAP$ */
2 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
3  *
4  * Copyright 1999-2006 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 #include "portable.h"
21
22 #include <stdio.h>
23
24 #include <ac/stdlib.h>
25
26 #include <ac/ctype.h>
27 #include <ac/dirent.h>
28 #include <ac/param.h>
29 #include <ac/socket.h>
30 #include <ac/string.h>
31 #include <ac/unistd.h>
32 #include <ac/wait.h>
33
34
35 #include "ldap_defaults.h"
36 #include "lutil.h"
37
38 #include "ldap.h"
39 #include "slapd-common.h"
40
41 #define SEARCHCMD               "slapd-search"
42 #define READCMD                 "slapd-read"
43 #define ADDCMD                  "slapd-addel"
44 #define MODRDNCMD               "slapd-modrdn"
45 #define MODIFYCMD               "slapd-modify"
46 #define BINDCMD                 "slapd-bind"
47 #define MAXARGS                 100
48 #define MAXREQS                 5000
49 #define LOOPS                   100
50 #define OUTERLOOPS              "1"
51 #define RETRIES                 "0"
52
53 #define TSEARCHFILE             "do_search.0"
54 #define TREADFILE               "do_read.0"
55 #define TADDFILE                "do_add."
56 #define TMODRDNFILE             "do_modrdn.0"
57 #define TMODIFYFILE             "do_modify.0"
58 #define TBINDFILE               "do_bind.0"
59
60 static char *get_file_name( char *dirname, char *filename );
61 static int  get_search_filters( char *filename, char *filters[], char *attrs[], char *bases[] );
62 static int  get_read_entries( char *filename, char *entries[] );
63 static void fork_child( char *prog, char **args );
64 static void     wait4kids( int nkidval );
65
66 static int      maxkids = 20;
67 static int      nkids;
68
69 #ifdef HAVE_WINSOCK
70 static HANDLE   *children;
71 static char argbuf[BUFSIZ];
72 #define ArgDup(x) strdup(strcat(strcat(strcpy(argbuf,"\""),x),"\""))
73 #else
74 #define ArgDup(x) strdup(x)
75 #endif
76
77 static void
78 usage( char *name )
79 {
80         fprintf( stderr,
81                 "usage: %s "
82                 "-H <uri> | ([-h <host>] -p <port>) "
83                 "-D <manager> "
84                 "-w <passwd> "
85                 "-d <datadir> "
86                 "[-j <maxchild>] "
87                 "[-l <loops>] "
88                 "[-L <outerloops>] "
89                 "-P <progdir> "
90                 "[-r <maxretries>] "
91                 "[-t <delay>] "
92                 "[-F] "
93                 "[-C]\n",
94                 name );
95         exit( EXIT_FAILURE );
96 }
97
98 int
99 main( int argc, char **argv )
100 {
101         int             i, j;
102         char            *uri = NULL;
103         char            *host = "localhost";
104         char            *port = NULL;
105         char            *manager = NULL;
106         char            *passwd = NULL;
107         char            *dirname = NULL;
108         char            *progdir = NULL;
109         int             loops = LOOPS;
110         char            *outerloops = OUTERLOOPS;
111         char            *retries = RETRIES;
112         char            *delay = "0";
113         DIR             *datadir;
114         struct dirent   *file;
115         int             friendly = 0;
116         int             chaserefs = 0;
117         /* search */
118         char            *sfile = NULL;
119         char            *sreqs[MAXREQS];
120         char            *sattrs[MAXREQS];
121         char            *sbase[MAXREQS];
122         int             snum = 0;
123         char            *sargs[MAXARGS];
124         int             sanum;
125         char            scmd[MAXPATHLEN];
126         char            sloops[] = "18446744073709551615UL";
127         /* read */
128         char            *rfile = NULL;
129         char            *rreqs[MAXREQS];
130         int             rnum = 0;
131         char            *rargs[MAXARGS];
132         int             ranum;
133         char            rcmd[MAXPATHLEN];
134         char            rloops[] = "18446744073709551615UL";
135         /* addel */
136         char            *afiles[MAXREQS];
137         int             anum = 0;
138         char            *aargs[MAXARGS];
139         int             aanum;
140         char            acmd[MAXPATHLEN];
141         char            aloops[] = "18446744073709551615UL";
142         /* modrdn */
143         char            *mfile = NULL;
144         char            *mreqs[MAXREQS];
145         int             mnum = 0;
146         char            *margs[MAXARGS];
147         int             manum;
148         char            mcmd[MAXPATHLEN];
149         char            mloops[] = "18446744073709551615UL";
150         /* modify */
151         char            *modfile = NULL;
152         char            *modreqs[MAXREQS];
153         char            *moddn[MAXREQS];
154         int             modnum = 0;
155         char            *modargs[MAXARGS];
156         int             modanum;
157         char            modcmd[MAXPATHLEN];
158         char            modloops[] = "18446744073709551615UL";
159         /* bind */
160         char            *bfile = NULL;
161         char            *breqs[MAXREQS];
162         char            *bcreds[MAXREQS];
163         int             bnum = 0;
164         char            *bargs[MAXARGS];
165         int             banum;
166         char            bcmd[MAXPATHLEN];
167         char            bloops[] = "18446744073709551615UL";
168
169         char            *friendlyOpt = NULL;
170
171         tester_init( "slapd-tester" );
172
173         while ( (i = getopt( argc, argv, "CD:d:FH:h:j:l:L:P:p:r:t:w:" )) != EOF ) {
174                 switch( i ) {
175                 case 'C':
176                         chaserefs++;
177                         break;
178
179                 case 'D':               /* slapd manager */
180                         manager = ArgDup( optarg );
181                         break;
182
183                 case 'd':               /* data directory */
184                         dirname = strdup( optarg );
185                         break;
186
187                 case 'F':
188                         friendly++;
189                         break;
190
191                 case 'H':               /* slapd uri */
192                         uri = strdup( optarg );
193                         break;
194
195                 case 'h':               /* slapd host */
196                         host = strdup( optarg );
197                         break;
198
199                 case 'j':               /* the number of parallel clients */
200                         if ( lutil_atoi( &maxkids, optarg ) != 0 ) {
201                                 usage( argv[0] );
202                         }
203                         break;
204
205                 case 'l':               /* the number of loops per client */
206                         if ( lutil_atoi( &loops, optarg ) != 0 ) {
207                                 usage( argv[0] );
208                         }
209                         break;
210
211                 case 'L':               /* the number of outerloops per client */
212                         outerloops = strdup( optarg );
213                         break;
214
215                 case 'P':               /* prog directory */
216                         progdir = strdup( optarg );
217                         break;
218
219                 case 'p':               /* the servers port number */
220                         port = strdup( optarg );
221                         break;
222
223                 case 'r':               /* the number of retries in case of error */
224                         retries = strdup( optarg );
225                         break;
226
227                 case 't':               /* the delay in seconds between each retry */
228                         delay = strdup( optarg );
229                         break;
230
231                 case 'w':               /* the managers passwd */
232                         passwd = ArgDup( optarg );
233                         break;
234
235                 default:
236                         usage( argv[0] );
237                         break;
238                 }
239         }
240
241         if (( dirname == NULL ) || ( port == NULL && uri == NULL ) ||
242                         ( manager == NULL ) || ( passwd == NULL ) || ( progdir == NULL ))
243                 usage( argv[0] );
244
245 #ifdef HAVE_WINSOCK
246         children = malloc( maxkids * sizeof(HANDLE) );
247 #endif
248         /* get the file list */
249         if ( ( datadir = opendir( dirname )) == NULL ) {
250
251                 fprintf( stderr, "%s: couldn't open data directory \"%s\".\n",
252                                         argv[0], dirname );
253                 exit( EXIT_FAILURE );
254
255         }
256
257         /*  look for search, read, modrdn, and add/delete files */
258         for ( file = readdir( datadir ); file; file = readdir( datadir )) {
259
260                 if ( !strcasecmp( file->d_name, TSEARCHFILE )) {
261                         sfile = get_file_name( dirname, file->d_name );
262                         continue;
263                 } else if ( !strcasecmp( file->d_name, TREADFILE )) {
264                         rfile = get_file_name( dirname, file->d_name );
265                         continue;
266                 } else if ( !strcasecmp( file->d_name, TMODRDNFILE )) {
267                         mfile = get_file_name( dirname, file->d_name );
268                         continue;
269                 } else if ( !strcasecmp( file->d_name, TMODIFYFILE )) {
270                         modfile = get_file_name( dirname, file->d_name );
271                         continue;
272                 } else if ( !strncasecmp( file->d_name, TADDFILE, strlen( TADDFILE ))
273                         && ( anum < MAXREQS )) {
274                         afiles[anum++] = get_file_name( dirname, file->d_name );
275                         continue;
276                 } else if ( !strcasecmp( file->d_name, TBINDFILE )) {
277                         bfile = get_file_name( dirname, file->d_name );
278                         continue;
279                 }
280         }
281
282         closedir( datadir );
283
284         /* look for search requests */
285         if ( sfile ) {
286                 snum = get_search_filters( sfile, sreqs, sattrs, sbase );
287         }
288
289         /* look for read requests */
290         if ( rfile ) {
291                 rnum = get_read_entries( rfile, rreqs );
292         }
293
294         /* look for modrdn requests */
295         if ( mfile ) {
296                 mnum = get_read_entries( mfile, mreqs );
297         }
298
299         /* look for modify requests */
300         if ( modfile ) {
301                 modnum = get_search_filters( modfile, modreqs, NULL, moddn );
302         }
303
304         /* look for bind requests */
305         if ( bfile ) {
306                 bnum = get_search_filters( bfile, bcreds, NULL, breqs );
307         }
308
309         /* setup friendly option */
310
311         switch ( friendly ) {
312         case 0:
313                 break;
314
315         case 1:
316                 friendlyOpt = "-F";
317                 break;
318
319         default:
320                 /* NOTE: right now we don't need it more than twice */
321         case 2:
322                 friendlyOpt = "-FF";
323                 break;
324         }
325
326         snprintf( sloops, sizeof( sloops ), "%d", 10 * loops );
327         snprintf( rloops, sizeof( rloops ), "%d", 20 * loops );
328         snprintf( aloops, sizeof( aloops ), "%d", loops );
329         snprintf( mloops, sizeof( mloops ), "%d", loops );
330         snprintf( modloops, sizeof( modloops ), "%d", loops );
331         snprintf( bloops, sizeof( bloops ), "%d", 20 * loops );
332
333         /*
334          * generate the search clients
335          */
336
337         sanum = 0;
338         snprintf( scmd, sizeof scmd, "%s" LDAP_DIRSEP SEARCHCMD,
339                 progdir );
340         sargs[sanum++] = scmd;
341         if ( uri ) {
342                 sargs[sanum++] = "-H";
343                 sargs[sanum++] = uri;
344         } else {
345                 sargs[sanum++] = "-h";
346                 sargs[sanum++] = host;
347                 sargs[sanum++] = "-p";
348                 sargs[sanum++] = port;
349         }
350         sargs[sanum++] = "-D";
351         sargs[sanum++] = manager;
352         sargs[sanum++] = "-w";
353         sargs[sanum++] = passwd;
354         sargs[sanum++] = "-l";
355         sargs[sanum++] = sloops;
356         sargs[sanum++] = "-L";
357         sargs[sanum++] = outerloops;
358         sargs[sanum++] = "-r";
359         sargs[sanum++] = retries;
360         sargs[sanum++] = "-t";
361         sargs[sanum++] = delay;
362         if ( friendly ) {
363                 sargs[sanum++] = friendlyOpt;
364         }
365         if ( chaserefs ) {
366                 sargs[sanum++] = "-C";
367         }
368         sargs[sanum++] = "-b";
369         sargs[sanum++] = NULL;          /* will hold the search base */
370         sargs[sanum++] = "-f";
371         sargs[sanum++] = NULL;          /* will hold the search request */
372
373         sargs[sanum++] = NULL;
374         sargs[sanum] = NULL;            /* might hold the "attr" request */
375
376         sargs[sanum + 1] = NULL;
377
378         /*
379          * generate the read clients
380          */
381
382         ranum = 0;
383         snprintf( rcmd, sizeof rcmd, "%s" LDAP_DIRSEP READCMD,
384                 progdir );
385         rargs[ranum++] = rcmd;
386         if ( uri ) {
387                 rargs[ranum++] = "-H";
388                 rargs[ranum++] = uri;
389         } else {
390                 rargs[ranum++] = "-h";
391                 rargs[ranum++] = host;
392                 rargs[ranum++] = "-p";
393                 rargs[ranum++] = port;
394         }
395         rargs[ranum++] = "-l";
396         rargs[ranum++] = rloops;
397         rargs[ranum++] = "-L";
398         rargs[ranum++] = outerloops;
399         rargs[ranum++] = "-r";
400         rargs[ranum++] = retries;
401         rargs[ranum++] = "-t";
402         rargs[ranum++] = delay;
403         if ( friendly ) {
404                 rargs[ranum++] = friendlyOpt;
405         }
406         if ( chaserefs ) {
407                 rargs[ranum++] = "-C";
408         }
409         rargs[ranum++] = "-e";
410         rargs[ranum++] = NULL;          /* will hold the read entry */
411         rargs[ranum++] = NULL;
412
413         /*
414          * generate the modrdn clients
415          */
416
417         manum = 0;
418         snprintf( mcmd, sizeof mcmd, "%s" LDAP_DIRSEP MODRDNCMD,
419                 progdir );
420         margs[manum++] = mcmd;
421         if ( uri ) {
422                 margs[manum++] = "-H";
423                 margs[manum++] = uri;
424         } else {
425                 margs[manum++] = "-h";
426                 margs[manum++] = host;
427                 margs[manum++] = "-p";
428                 margs[manum++] = port;
429         }
430         margs[manum++] = "-D";
431         margs[manum++] = manager;
432         margs[manum++] = "-w";
433         margs[manum++] = passwd;
434         margs[manum++] = "-l";
435         margs[manum++] = mloops;
436         margs[manum++] = "-L";
437         margs[manum++] = outerloops;
438         margs[manum++] = "-r";
439         margs[manum++] = retries;
440         margs[manum++] = "-t";
441         margs[manum++] = delay;
442         if ( friendly ) {
443                 margs[manum++] = friendlyOpt;
444         }
445         if ( chaserefs ) {
446                 margs[manum++] = "-C";
447         }
448         margs[manum++] = "-e";
449         margs[manum++] = NULL;          /* will hold the modrdn entry */
450         margs[manum++] = NULL;
451         
452         /*
453          * generate the modify clients
454          */
455
456         modanum = 0;
457         snprintf( modcmd, sizeof modcmd, "%s" LDAP_DIRSEP MODIFYCMD,
458                 progdir );
459         modargs[modanum++] = modcmd;
460         if ( uri ) {
461                 modargs[modanum++] = "-H";
462                 modargs[modanum++] = uri;
463         } else {
464                 modargs[modanum++] = "-h";
465                 modargs[modanum++] = host;
466                 modargs[modanum++] = "-p";
467                 modargs[modanum++] = port;
468         }
469         modargs[modanum++] = "-D";
470         modargs[modanum++] = manager;
471         modargs[modanum++] = "-w";
472         modargs[modanum++] = passwd;
473         modargs[modanum++] = "-l";
474         modargs[modanum++] = modloops;
475         modargs[modanum++] = "-L";
476         modargs[modanum++] = outerloops;
477         modargs[modanum++] = "-r";
478         modargs[modanum++] = retries;
479         modargs[modanum++] = "-t";
480         modargs[modanum++] = delay;
481         if ( friendly ) {
482                 modargs[modanum++] = friendlyOpt;
483         }
484         if ( chaserefs ) {
485                 modargs[modanum++] = "-C";
486         }
487         modargs[modanum++] = "-e";
488         modargs[modanum++] = NULL;              /* will hold the modify entry */
489         modargs[modanum++] = "-a";;
490         modargs[modanum++] = NULL;              /* will hold the ava */
491         modargs[modanum++] = NULL;
492
493         /*
494          * generate the add/delete clients
495          */
496
497         aanum = 0;
498         snprintf( acmd, sizeof acmd, "%s" LDAP_DIRSEP ADDCMD,
499                 progdir );
500         aargs[aanum++] = acmd;
501         if ( uri ) {
502                 aargs[aanum++] = "-H";
503                 aargs[aanum++] = uri;
504         } else {
505                 aargs[aanum++] = "-h";
506                 aargs[aanum++] = host;
507                 aargs[aanum++] = "-p";
508                 aargs[aanum++] = port;
509         }
510         aargs[aanum++] = "-D";
511         aargs[aanum++] = manager;
512         aargs[aanum++] = "-w";
513         aargs[aanum++] = passwd;
514         aargs[aanum++] = "-l";
515         aargs[aanum++] = aloops;
516         aargs[aanum++] = "-L";
517         aargs[aanum++] = outerloops;
518         aargs[aanum++] = "-r";
519         aargs[aanum++] = retries;
520         aargs[aanum++] = "-t";
521         aargs[aanum++] = delay;
522         if ( friendly ) {
523                 aargs[aanum++] = friendlyOpt;
524         }
525         if ( chaserefs ) {
526                 aargs[aanum++] = "-C";
527         }
528         aargs[aanum++] = "-f";
529         aargs[aanum++] = NULL;          /* will hold the add data file */
530         aargs[aanum++] = NULL;
531
532         /*
533          * generate the bind clients
534          */
535
536         banum = 0;
537         snprintf( bcmd, sizeof bcmd, "%s" LDAP_DIRSEP BINDCMD,
538                 progdir );
539         bargs[banum++] = bcmd;
540         if ( uri ) {
541                 bargs[banum++] = "-H";
542                 bargs[banum++] = uri;
543         } else {
544                 bargs[banum++] = "-h";
545                 bargs[banum++] = host;
546                 bargs[banum++] = "-p";
547                 bargs[banum++] = port;
548         }
549         bargs[banum++] = "-l";
550         bargs[banum++] = bloops;
551         bargs[banum++] = "-L";
552         bargs[banum++] = outerloops;
553 #if 0
554         bargs[banum++] = "-r";
555         bargs[banum++] = retries;
556         bargs[banum++] = "-t";
557         bargs[banum++] = delay;
558 #endif
559         if ( friendly ) {
560                 bargs[banum++] = friendlyOpt;
561         }
562         if ( chaserefs ) {
563                 bargs[banum++] = "-C";
564         }
565         bargs[banum++] = "-D";
566         bargs[banum++] = NULL;
567         bargs[banum++] = "-w";
568         bargs[banum++] = NULL;
569         bargs[banum++] = NULL;
570
571         for ( j = 0; j < MAXREQS; j++ ) {
572                 if ( j < snum ) {
573
574                         sargs[sanum - 2] = sreqs[j];
575                         sargs[sanum - 4] = sbase[j];
576                         if ( sattrs[j] != NULL ) {
577                                 sargs[sanum - 1] = "-a";
578                                 sargs[sanum] = sattrs[j];
579
580                         } else {
581                                 sargs[sanum - 1] = NULL;
582                         }
583                         fork_child( scmd, sargs );
584
585                 }
586
587                 if ( j < rnum ) {
588
589                         rargs[ranum - 2] = rreqs[j];
590                         fork_child( rcmd, rargs );
591
592                 }
593
594                 if ( j < mnum ) {
595
596                         margs[manum - 2] = mreqs[j];
597                         fork_child( mcmd, margs );
598
599                 }
600                 if ( j < modnum ) {
601
602                         modargs[modanum - 4] = moddn[j];
603                         modargs[modanum - 2] = modreqs[j];
604                         fork_child( modcmd, modargs );
605
606                 }
607
608                 if ( j < anum ) {
609
610                         aargs[aanum - 2] = afiles[j];
611                         fork_child( acmd, aargs );
612
613                 }
614
615                 if ( j < bnum ) {
616
617                         bargs[banum - 4] = breqs[j];
618                         bargs[banum - 2] = bcreds[j];
619                         fork_child( bcmd, bargs );
620
621                 }
622
623         }
624
625         wait4kids( -1 );
626
627         exit( EXIT_SUCCESS );
628 }
629
630 static char *
631 get_file_name( char *dirname, char *filename )
632 {
633         char buf[MAXPATHLEN];
634
635         snprintf( buf, sizeof buf, "%s" LDAP_DIRSEP "%s",
636                 dirname, filename );
637         return( strdup( buf ));
638 }
639
640
641 static int
642 get_search_filters( char *filename, char *filters[], char *attrs[], char *bases[] )
643 {
644         FILE    *fp;
645         int     filter = 0;
646
647         if ( (fp = fopen( filename, "r" )) != NULL ) {
648                 char  line[BUFSIZ];
649
650                 while (( filter < MAXREQS ) && ( fgets( line, BUFSIZ, fp ))) {
651                         char *nl;
652
653                         if (( nl = strchr( line, '\r' )) || ( nl = strchr( line, '\n' )))
654                                 *nl = '\0';
655                         bases[filter] = ArgDup( line );
656                         fgets( line, BUFSIZ, fp );
657                         if (( nl = strchr( line, '\r' )) || ( nl = strchr( line, '\n' )))
658                                 *nl = '\0';
659
660                         filters[filter] = ArgDup( line );
661                         if ( attrs ) {
662                                 if ( filters[filter][0] == '+') {
663                                         char    *sep = strchr( filters[filter], ':' );
664
665                                         if ( sep != NULL ) {
666                                                 attrs[ filter ] = &filters[ filter ][ 1 ];
667                                                 sep[ 0 ] = '\0';
668                                                 /* NOTE: don't free this! */
669                                                 filters[ filter ] = &sep[ 1 ];
670                                         }
671
672                                 } else {
673                                         attrs[ filter] = NULL;
674                                 }
675                         }
676                         filter++;
677
678                 }
679                 fclose( fp );
680         }
681
682         return( filter );
683 }
684
685
686 static int
687 get_read_entries( char *filename, char *entries[] )
688 {
689         FILE    *fp;
690         int     entry = 0;
691
692         if ( (fp = fopen( filename, "r" )) != NULL ) {
693                 char  line[BUFSIZ];
694
695                 while (( entry < MAXREQS ) && ( fgets( line, BUFSIZ, fp ))) {
696                         char *nl;
697
698                         if (( nl = strchr( line, '\r' )) || ( nl = strchr( line, '\n' )))
699                                 *nl = '\0';
700                         entries[entry++] = ArgDup( line );
701
702                 }
703                 fclose( fp );
704         }
705
706         return( entry );
707 }
708
709 #ifndef HAVE_WINSOCK
710 static void
711 fork_child( char *prog, char **args )
712 {
713         pid_t   pid;
714
715         wait4kids( maxkids );
716
717         switch ( pid = fork() ) {
718         case 0:         /* child */
719 #ifdef HAVE_EBCDIC
720                 /* The __LIBASCII execvp only handles ASCII "prog",
721                  * we still need to translate the arg vec ourselves.
722                  */
723                 { char *arg2[MAXREQS];
724                 int i;
725
726                 for (i=0; args[i]; i++) {
727                         arg2[i] = ArgDup(args[i]);
728                         __atoe(arg2[i]);
729                 }
730                 arg2[i] = NULL;
731                 args = arg2; }
732 #endif
733                 execvp( prog, args );
734                 tester_perror( "execvp" );
735                 exit( EXIT_FAILURE );
736                 break;
737
738         case -1:        /* trouble */
739                 tester_perror( "fork" );
740                 break;
741
742         default:        /* parent */
743                 nkids++;
744                 break;
745         }
746 }
747
748 static void
749 wait4kids( int nkidval )
750 {
751         int             status;
752
753         while ( nkids >= nkidval ) {
754                 wait( &status );
755
756                 if ( WIFSTOPPED(status) ) {
757                         fprintf( stderr,
758                             "stopping: child stopped with signal %d\n",
759                             (int) WSTOPSIG(status) );
760
761                 } else if ( WIFSIGNALED(status) ) {
762                         fprintf( stderr, 
763                             "stopping: child terminated with signal %d%s\n",
764                             (int) WTERMSIG(status),
765 #ifdef WCOREDUMP
766                                 WCOREDUMP(status) ? ", core dumped" : ""
767 #else
768                                 ""
769 #endif
770                                 );
771                         exit( WEXITSTATUS(status)  );
772
773                 } else if ( WEXITSTATUS(status) != 0 ) {
774                         fprintf( stderr, 
775                             "stopping: child exited with status %d\n",
776                             (int) WEXITSTATUS(status) );
777                         exit( WEXITSTATUS(status) );
778
779                 } else {
780                         nkids--;
781                 }
782         }
783 }
784 #else
785
786 static void
787 wait4kids( int nkidval )
788 {
789         int rc, i;
790
791         while ( nkids >= nkidval ) {
792                 rc = WaitForMultipleObjects( nkids, children, FALSE, INFINITE );
793                 for ( i=rc - WAIT_OBJECT_0; i<nkids-1; i++)
794                         children[i] = children[i+1];
795                 nkids--;
796         }
797 }
798
799 static void
800 fork_child( char *prog, char **args )
801 {
802         int rc;
803
804         wait4kids( maxkids );
805
806         rc = _spawnvp( _P_NOWAIT, prog, args );
807
808         if ( rc == -1 ) {
809                 tester_perror( "_spawnvp" );
810         } else {
811                 children[nkids++] = (HANDLE)rc;
812         }
813 }
814 #endif