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