Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/boost_1_34_1/tools/jam/src/execnt.c @ 29

Last change on this file since 29 was 29, checked in by landauf, 17 years ago

updated boost from 1_33_1 to 1_34_1

File size: 34.1 KB
Line 
1/*
2 * Copyright 1993, 1995 Christopher Seiwald.
3 *
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
6
7/*  This file is ALSO:
8 *  Copyright 2001-2004 David Abrahams.
9 *  Distributed under the Boost Software License, Version 1.0.
10 *  (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
11 */
12
13# include "jam.h"
14# include "lists.h"
15# include "execcmd.h"
16# include "pathsys.h"
17# include "debug.h"
18# include <errno.h>
19# include <assert.h>
20# include <ctype.h>
21# include <time.h>
22
23# ifdef USE_EXECNT
24
25# define WIN32_LEAN_AND_MEAN
26# include <windows.h>           /* do the ugly deed */
27# include <process.h>
28# if !defined( __BORLANDC__ )
29# include <tlhelp32.h>
30# endif
31
32# if !defined( __BORLANDC__ ) && !defined( OS_OS2 )
33# define wait my_wait
34static int my_wait( int *status );
35# endif
36
37/*
38 * execnt.c - execute a shell command on Windows NT and Windows 95/98
39 *
40 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
41 * The default is:
42 *
43 *      /bin/sh -c %            [ on UNIX/AmigaOS ]
44 *      cmd.exe /c %            [ on Windows NT ]
45 *
46 * Each word must be an individual element in a jam variable value.
47 *
48 * In $(JAMSHELL), % expands to the command string and ! expands to
49 * the slot number (starting at 1) for multiprocess (-j) invocations.
50 * If $(JAMSHELL) doesn't include a %, it is tacked on as the last
51 * argument.
52 *
53 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
54 *
55 * External routines:
56 *      execcmd() - launch an async command execution
57 *      execwait() - wait and drive at most one execution completion
58 *
59 * Internal routines:
60 *      onintr() - bump intr to note command interruption
61 *
62 * 04/08/94 (seiwald) - Coherent/386 support added.
63 * 05/04/94 (seiwald) - async multiprocess interface
64 * 01/22/95 (seiwald) - $(JAMSHELL) support
65 * 06/02/97 (gsar)    - full async multiprocess support for Win32
66 */
67
68static int intr = 0;
69static int cmdsrunning = 0;
70static void (*istat)( int );
71
72static int  is_nt_351        = 0;
73static int  is_win95         = 1;
74static int  is_win95_defined = 0;
75
76
77static struct
78{
79        int     pid; /* on win32, a real process handle */
80        void    (*func)( void *closure, int status, timing_info* );
81        void    *closure;
82        char    *tempfile;
83
84} cmdtab[ MAXJOBS ] = {{0}};
85
86
87static void
88set_is_win95( void )
89{
90  OSVERSIONINFO  os_info;
91
92  os_info.dwOSVersionInfoSize = sizeof(os_info);
93  os_info.dwPlatformId        = VER_PLATFORM_WIN32_WINDOWS;
94  GetVersionEx( &os_info );
95 
96  is_win95         = (os_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);
97  is_win95_defined = 1;
98 
99  /* now, test wether we're running Windows 3.51                */
100  /* this is later used to limit the system call command length */
101  if (os_info.dwPlatformId ==  VER_PLATFORM_WIN32_NT)
102    is_nt_351 = os_info.dwMajorVersion == 3;
103}
104
105int maxline()
106{
107    if (!is_win95_defined)
108        set_is_win95();
109   
110    /* Set the maximum command line length according to the OS */
111    return is_nt_351 ? 996
112        : is_win95 ? 1023
113        : 2047;
114}
115
116static void
117free_argv( char** args )
118{
119  free( args[0] );
120  free( args );
121}
122
123/* Convert a command string into arguments for spawnvp.  The original
124 * code, inherited from ftjam, tried to break up every argument on the
125 * command-line, dealing with quotes, but that's really a waste of
126 * time on Win32, at least.  It turns out that all you need to do is
127 * get the raw path to the executable in the first argument to
128 * spawnvp, and you can pass all the rest of the command-line
129 * arguments to spawnvp in one, un-processed string.
130 *
131 * New strategy: break the string in at most one place.
132 */
133static char**
134string_to_args( const char*  string )
135{
136    int src_len;
137    int in_quote;
138    char* line;
139    char const* src;
140    char* dst;
141    char** argv;
142
143    /* drop leading and trailing whitespace if any */
144    while (isspace(*string))
145        ++string;
146 
147    src_len = strlen( string );
148    while ( src_len > 0 && isspace( string[src_len - 1] ) )
149        --src_len;
150
151    /* Copy the input string into a buffer we can modify
152     */
153    line = (char*)malloc( src_len+1 );
154    if (!line)
155        return 0;
156
157    if ( DEBUG_PROFILE )
158        profile_memory( src_len+1 );
159
160    /* allocate the argv array.
161     *   element 0: stores the path to the executable
162     *   element 1: stores the command-line arguments to the executable
163     *   element 2: NULL terminator
164     */
165    argv = (char**)malloc( 3 * sizeof(char*) );
166    if (!argv)
167    {
168        free( line );
169        return 0;
170    }
171
172    if ( DEBUG_PROFILE )
173        profile_memory( 3 * sizeof(char*) );
174   
175    /* Strip quotes from the first command-line argument and find
176     * where it ends.  Quotes are illegal in Win32 pathnames, so we
177     * don't need to worry about preserving escaped quotes here.
178     * Spaces can't be escaped in Win32, only enclosed in quotes, so
179     * removing backslash escapes is also a non-issue.
180     */
181    in_quote = 0;
182    for ( src = string, dst = line ; *src; src++ )
183    {
184        if (*src == '"')
185            in_quote = !in_quote;
186        else if (!in_quote && isspace(*src))
187            break;
188        else
189            *dst++ = *src;
190    }
191    *dst++ = 0;
192    argv[0] = line;
193
194    /* skip whitespace in src */
195    while (isspace(*src))
196        ++src;
197
198    argv[1] = dst;
199
200        /* Copy the rest of the arguments verbatim */
201   
202    src_len -= src - string;
203
204    /* Use strncat because it appends a trailing nul */
205    *dst = 0;
206    strncat(dst, src, src_len);
207
208    argv[2] = 0;
209   
210    return argv;
211}
212
213
214
215/* process a "del" or "erase" command under Windows 95/98 */
216static int
217process_del( char*  command )
218{
219  char** arg;
220  char*  p = command, *q;
221  int    wildcard = 0, result = 0;
222
223  /* first of all, skip the command itself */
224  if ( p[0] == 'd' )
225    p += 3; /* assumes "del..;" */
226  else if ( p[0] == 'e' )
227    p += 5; /* assumes "erase.." */
228  else
229    return 1; /* invalid command */
230
231  /* process all targets independently */
232  for (;;)
233  {
234    /* skip leading spaces */
235    while ( *p && isspace(*p) )
236      p++;
237     
238    /* exit if we encounter an end of string */
239    if (!*p)
240      return 0;
241     
242    /* ignore toggles/flags */
243    while (*p == '/')
244    {
245      p++;
246      while ( *p && isalnum(*p) )
247          p++;
248      while (*p && isspace(*p) )
249          ++p;
250    }
251
252   
253    {
254      int  in_quote = 0;
255      int  wildcard = 0;
256      int  go_on    = 1;
257     
258      q = p;
259      while (go_on)
260      {
261        switch (*p)
262        {
263          case '"':
264            in_quote = !in_quote;
265            break;
266         
267          case '?':
268          case '*':
269            if (!in_quote)
270              wildcard = 1;
271            break;
272           
273          case '\0':
274            if (in_quote)
275              return 1;
276            /* fall-through */
277             
278          case ' ':
279          case '\t':
280            if (!in_quote)
281            {
282              int    len = p - q;
283              int    result;
284              char*  line;
285             
286              /* q..p-1 contains the delete argument */
287              if ( len <= 0 )
288                return 1;
289 
290              line = (char*)malloc( len+4+1 );
291              if (!line)
292                return 1;
293              if ( DEBUG_PROFILE )
294                  profile_memory( len+4+1 );
295               
296              strncpy( line, "del ", 4 );
297              strncpy( line+4, q, len );
298              line[len+4] = '\0';
299             
300              if ( wildcard )
301                result = system( line );
302              else
303                result = !DeleteFile( line+4 );
304 
305              free( line );
306              if (result)
307                return 1;
308               
309              go_on = 0;
310            }
311           
312          default:
313            ;
314        }
315        p++;
316      } /* while (go_on) */
317    }
318  }
319}
320
321
322/*
323 * onintr() - bump intr to note command interruption
324 */
325
326void
327onintr( int disp )
328{
329        intr++;
330        printf( "...interrupted\n" );
331}
332
333/*
334 * can_spawn() - If the command is suitable for execution via spawnvp,
335 * return a number >= the number of characters it would occupy on the
336 * command-line.  Otherwise, return zero.
337 */
338long can_spawn(char* command)
339{
340    char *p;
341   
342    char inquote = 0;
343
344    /* Move to the first non-whitespace */
345    command += strspn( command, " \t" );
346
347    p = command;
348   
349    /* Look for newlines and unquoted i/o redirection */
350    do
351    {
352        p += strcspn( p, "'\n\"<>|" );
353
354        switch (*p)
355        {
356        case '\n':
357            /* skip over any following spaces */
358            while( isspace( *p ) )
359                ++p;
360            /* Must use a .bat file if there is anything significant
361             * following the newline
362             */
363            if (*p)
364                return 0;
365            break;
366           
367        case '"':
368        case '\'':
369            if (p > command && p[-1] != '\\')
370            {
371                if (inquote == *p)
372                    inquote = 0;
373                else if (inquote == 0)
374                    inquote = *p;
375            }
376               
377            ++p;
378            break;
379           
380        case '<':
381        case '>':
382        case '|':
383            if (!inquote)
384                return 0;
385            ++p;
386            break;
387        }
388    }
389    while (*p);
390
391    /* Return the number of characters the command will occupy
392     */
393    return p - command;
394}
395
396void execnt_unit_test()
397{
398#if !defined(NDEBUG)       
399    /* vc6 preprocessor is broken, so assert with these strings gets
400     * confused. Use a table instead.
401     */
402    typedef struct test { char* command; int result; } test;
403    test tests[] = {
404        { "x", 0 },
405        { "x\n ", 0 },
406        { "x\ny", 1 },
407        { "x\n\n y", 1 },
408        { "echo x > foo.bar", 1 },
409        { "echo x < foo.bar", 1 },
410        { "echo x \">\" foo.bar", 0 },
411        { "echo x \"<\" foo.bar", 0 },
412        { "echo x \\\">\\\" foo.bar", 1 },
413        { "echo x \\\"<\\\" foo.bar", 1 }
414    };
415    int i;
416    for ( i = 0; i < sizeof(tests)/sizeof(*tests); ++i)
417    {
418        assert( !can_spawn( tests[i].command ) == tests[i].result );
419    }
420
421    {
422        char* long_command = malloc(MAXLINE + 10);
423        assert( long_command != 0 );
424        memset( long_command, 'x', MAXLINE + 9 );
425        long_command[MAXLINE + 9] = 0;
426        assert( can_spawn( long_command ) == MAXLINE + 9);
427        free( long_command );
428    }
429
430    {
431        /* Work around vc6 bug; it doesn't like escaped string
432         * literals inside assert
433         */
434        char** argv = string_to_args("\"g++\" -c -I\"Foobar\"");
435        char const expected[] = "-c -I\"Foobar\""; 
436       
437        assert(!strcmp(argv[0], "g++"));
438        assert(!strcmp(argv[1], expected));
439        free_argv(argv);
440    }
441#endif
442}
443
444/* 64-bit arithmetic helpers */
445
446/* Compute the carry bit from the addition of two 32-bit unsigned numbers */
447#define add_carry_bit(a, b) ( (((a) | (b)) >> 31) & (~((a) + (b)) >> 31) & 0x1 )
448
449/* Compute the high 32 bits of the addition of two 64-bit unsigned numbers, h1l1 and h2l2 */
450#define add_64_hi(h1, l1, h2, l2) ((h1) + (h2) + add_carry_bit(l1, l2))
451
452/* Add two 64-bit unsigned numbers, h1l1 and h2l2 */
453static FILETIME add_64(
454    unsigned long h1, unsigned long l1,
455    unsigned long h2, unsigned long l2)
456{
457    FILETIME result;
458    result.dwLowDateTime = l1 + l2;
459    result.dwHighDateTime = add_64_hi(h1, l1, h2, l2);
460
461    return result;
462}
463
464static FILETIME add_FILETIME(FILETIME t1, FILETIME t2)
465{
466    return add_64(
467        t1.dwHighDateTime, t1.dwLowDateTime
468      , t2.dwHighDateTime, t2.dwLowDateTime);
469}
470static FILETIME negate_FILETIME(FILETIME t)
471{
472    /* 2s complement negation */
473    return add_64(~t.dwHighDateTime, ~t.dwLowDateTime, 0, 1);
474}
475
476/* COnvert a FILETIME to a number of seconds */
477static double filetime_seconds(FILETIME t)
478{
479    return t.dwHighDateTime * (double)(1UL << 31) * 2 + t.dwLowDateTime * 1.0e-7;
480}
481
482static void
483record_times(int pid, timing_info* time)
484{
485    FILETIME creation, exit, kernel, user;
486    if (GetProcessTimes((HANDLE)pid, &creation, &exit, &kernel, &user))
487    {
488        /* Compute the elapsed time */
489#if 0 /* We don't know how to get this number this on Unix */
490        time->elapsed = filetime_seconds(
491            add_FILETIME( exit, negate_FILETIME(creation) )
492        );
493#endif
494
495        time->system = filetime_seconds(kernel);
496        time->user = filetime_seconds(user);           
497    }
498       
499    CloseHandle((HANDLE)pid);
500}
501   
502
503/*
504 * execcmd() - launch an async command execution
505 */
506
507void
508execcmd( 
509        char *string,
510        void (*func)( void *closure, int status, timing_info* ),
511        void *closure,
512        LIST *shell )
513{
514    int pid;
515    int slot;
516    int raw_cmd = 0 ;
517    char *argv_static[ MAXARGC + 1 ];   /* +1 for NULL */
518    char **argv = argv_static;
519    char *p;
520
521    /* Check to see if we need to hack around the line-length limitation. */
522    /* Look for a JAMSHELL setting of "%", indicating that the command
523     * should be invoked directly */
524    if ( shell && !strcmp(shell->string,"%") && !list_next(shell) )
525    {
526        raw_cmd = 1;
527        shell = 0;
528    }
529
530    if ( !is_win95_defined )
531        set_is_win95();
532         
533    /* Find a slot in the running commands table for this one. */
534    if ( is_win95 )
535    {
536        /* only synchronous spans are supported on Windows 95/98 */
537        slot = 0;
538    }
539    else
540    {
541        for( slot = 0; slot < MAXJOBS; slot++ )
542            if( !cmdtab[ slot ].pid )
543                break;
544    }
545    if( slot == MAXJOBS )
546    {
547        printf( "no slots for child!\n" );
548        exit( EXITBAD );
549    }
550 
551    if( !cmdtab[ slot ].tempfile )
552    {
553        const char *tempdir = path_tmpdir();
554        DWORD procID = GetCurrentProcessId();
555 
556        /* SVA - allocate 64 other just to be safe */
557        cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 64 );
558        if ( DEBUG_PROFILE )
559            profile_memory( strlen( tempdir ) + 64 );
560 
561        sprintf( cmdtab[ slot ].tempfile, "%s\\jam%d-%02d.bat", 
562                 tempdir, procID, slot );               
563    }
564
565    /* Trim leading, ending white space */
566
567    while( isspace( *string ) )
568        ++string;
569
570    /* Write to .BAT file unless the line would be too long and it
571     * meets the other spawnability criteria.
572     */
573    if( raw_cmd && can_spawn( string ) >= MAXLINE )
574    {
575        if( DEBUG_EXECCMD )
576            printf("Executing raw command directly\n");       
577    }
578    else
579    {
580        FILE *f = 0;
581        int tries = 0;
582        raw_cmd = 0;
583       
584        /* Write command to bat file. For some reason this open can
585           fails intermitently. But doing some retries works. Most likely
586           this is due to a previously existing file of the same name that
587           happens to be opened by an active virus scanner. Pointed out,
588           and fix by Bronek Kozicki. */
589        for (; !f && tries < 4; ++tries)
590        {
591            f = fopen( cmdtab[ slot ].tempfile, "w" );
592            if ( !f && tries < 4 ) Sleep( 250 );
593        }
594        if (!f)
595        {
596            printf( "failed to write command file!\n" );
597            exit( EXITBAD );
598        }
599        fputs( string, f );
600        fclose( f );
601
602        string = cmdtab[ slot ].tempfile;
603       
604        if( DEBUG_EXECCMD )
605        {
606            if (shell)
607                printf("using user-specified shell: %s", shell->string);
608            else
609                printf("Executing through .bat file\n");
610        }
611    }
612
613    /* Forumulate argv */
614    /* If shell was defined, be prepared for % and ! subs. */
615    /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */
616
617    if( shell )
618    {
619        int i;
620        char jobno[4];
621        int gotpercent = 0;
622
623        sprintf( jobno, "%d", slot + 1 );
624
625        for( i = 0; shell && i < MAXARGC; i++, shell = list_next( shell ) )
626        {
627            switch( shell->string[0] )
628            {
629            case '%':   argv[i] = string; gotpercent++; break;
630            case '!':   argv[i] = jobno; break;
631            default:    argv[i] = shell->string;
632            }
633            if( DEBUG_EXECCMD )
634                printf( "argv[%d] = '%s'\n", i, argv[i] );
635        }
636
637        if( !gotpercent )
638            argv[i++] = string;
639
640        argv[i] = 0;
641    }
642    else if (raw_cmd)
643    {
644        argv = string_to_args(string);
645    }
646    else
647    {
648        /* don't worry, this is ignored on Win95/98, see later.. */
649        argv[0] = "cmd.exe";
650        argv[1] = "/Q/C";               /* anything more is non-portable */
651        argv[2] = string;
652        argv[3] = 0;
653    }
654
655    /* Catch interrupts whenever commands are running. */
656
657    if( !cmdsrunning++ )
658        istat = signal( SIGINT, onintr );
659
660    /* Start the command */
661
662    /* on Win95, we only do a synchronous call */
663    if ( is_win95 )
664    {
665        static const char* hard_coded[] =
666            {
667                "del", "erase", "copy", "mkdir", "rmdir", "cls", "dir",
668                "ren", "rename", "move", 0
669            };
670         
671        const char**  keyword;
672        int           len, spawn = 1;
673        int           result;
674        timing_info time = {0,0};
675         
676        for ( keyword = hard_coded; keyword[0]; keyword++ )
677        {
678            len = strlen( keyword[0] );
679            if ( strnicmp( string, keyword[0], len ) == 0 &&
680                 !isalnum(string[len]) )
681            {
682                /* this is one of the hard coded symbols, use 'system' to run */
683                /* them.. except for "del"/"erase"                            */
684                if ( keyword - hard_coded < 2 )
685                    result = process_del( string );
686                else
687                    result = system( string );
688
689                spawn  = 0;
690                break;
691            }
692        }
693         
694        if (spawn)
695        {
696            char**  args;
697           
698            /* convert the string into an array of arguments */
699            /* we need to take care of double quotes !!      */
700            args = string_to_args( string );
701            if ( args )
702            {
703#if 0
704                char** arg;
705                fprintf( stderr, "%s: ", args[0] );
706                arg = args+1;
707                while ( arg[0] )
708                {
709                    fprintf( stderr, " {%s}", arg[0] );
710                    arg++;
711                }
712                fprintf( stderr, "\n" );
713#endif             
714                result = spawnvp( P_WAIT, args[0], args );
715                record_times(result, &time);
716                free_argv( args );
717            }
718            else
719                result = 1;
720        }
721        func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK, &time );
722        return;
723    }
724
725    if( DEBUG_EXECCMD )
726    {
727        char **argp = argv;
728
729        printf("Executing command");
730        while(*argp != 0)
731        {
732            printf(" [%s]", *argp);
733            argp++;
734        }
735        printf("\n");
736    }
737
738    /* the rest is for Windows NT only */
739    /* spawn doesn't like quotes around the command name */
740    if ( argv[0][0] == '"')
741    {
742        int l = strlen(argv[0]);
743
744        /* Clobber any closing quote, shortening the string by one
745         * element */
746        if (argv[0][l-1] == '"')
747            argv[0][l-1] = '\0';
748       
749        /* Move everything *including* the original terminating zero
750         * back one place in memory, covering up the opening quote */
751        memmove(argv[0],argv[0]+1,l);
752    }
753    if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 )
754    {
755        perror( "spawn" );
756        exit( EXITBAD );
757    }
758    /* Save the operation for execwait() to find. */
759
760    cmdtab[ slot ].pid = pid;
761    cmdtab[ slot ].func = func;
762    cmdtab[ slot ].closure = closure;
763
764    /* Wait until we're under the limit of concurrent commands. */
765    /* Don't trust globs.jobs alone.                            */
766
767    while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs )
768        if( !execwait() )
769            break;
770   
771    if (argv != argv_static)
772    {
773        free_argv(argv);
774    }
775}
776
777/*
778 * execwait() - wait and drive at most one execution completion
779 */
780
781int
782execwait()
783{
784        int i;
785        int status, w;
786        int rstat;
787    timing_info time;
788
789        /* Handle naive make1() which doesn't know if cmds are running. */
790
791        if( !cmdsrunning )
792            return 0;
793
794    if ( is_win95 )
795        return 0;
796         
797        /* Pick up process pid and status */
798   
799    while( ( w = wait( &status ) ) == -1 && errno == EINTR )
800        ;
801
802        if( w == -1 )
803        {
804            printf( "child process(es) lost!\n" );
805            perror("wait");
806            exit( EXITBAD );
807        }
808
809        /* Find the process in the cmdtab. */
810
811        for( i = 0; i < MAXJOBS; i++ )
812            if( w == cmdtab[ i ].pid )
813                break;
814
815        if( i == MAXJOBS )
816        {
817            printf( "waif child found!\n" );
818            exit( EXITBAD );
819        }
820
821    record_times(cmdtab[i].pid, &time);
822   
823        /* Clear the temp file */
824    if ( cmdtab[i].tempfile )
825        unlink( cmdtab[ i ].tempfile );
826
827        /* Drive the completion */
828
829        if( !--cmdsrunning )
830            signal( SIGINT, istat );
831
832        if( intr )
833            rstat = EXEC_CMD_INTR;
834        else if( w == -1 || status != 0 )
835            rstat = EXEC_CMD_FAIL;
836        else
837            rstat = EXEC_CMD_OK;
838
839        cmdtab[ i ].pid = 0;
840        /* SVA don't leak temp files */
841        if(cmdtab[i].tempfile != NULL)
842        {
843            free(cmdtab[i].tempfile);
844            cmdtab[i].tempfile = NULL;
845        }
846        (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time );
847
848        return 1;
849}
850
851# if !defined( __BORLANDC__ )
852
853/* The possible result codes from check_process_exit, below */
854typedef enum { process_error, process_active, process_finished } process_state;
855
856/* Helper for my_wait() below.  Checks to see whether the process has
857 * exited and if so, records timing information.
858 */
859static process_state
860check_process_exit(
861    HANDLE process         /* The process we're looking at */
862   
863  , int* status            /* Storage for the finished process' exit
864                            * code.  If the process is still active
865                            * this location is left untouched. */
866   
867  , HANDLE* active_handles /* Storage for the process handle if it is
868                            * found to be still active, or NULL.  The
869                            * process is treated as though it is
870                            * complete.  */
871   
872  , int* num_active        /* The current length of active_handles */
873)
874{
875    DWORD exitcode;
876    process_state result;
877
878    /* Try to get the process exit code */
879    if (!GetExitCodeProcess(process, &exitcode))
880    {
881        result = process_error; /* signal an error */
882    }
883    else if (
884        exitcode == STILL_ACTIVE     /* If the process is still active */
885        && active_handles != 0       /* and we've been passed a place to buffer it */
886    )
887    {
888        active_handles[(*num_active)++] = process; /* push it onto the active stack */
889        result = process_active;
890    }
891    else
892    {
893        *status = (int)((exitcode & 0xff) << 8);
894        result = process_finished;
895    }
896   
897    return result;
898}
899
900static double
901running_time(HANDLE process)
902{
903    FILETIME creation, exit, kernel, user, current;
904    if (GetProcessTimes(process, &creation, &exit, &kernel, &user))
905    {
906        /* Compute the elapsed time */
907        GetSystemTimeAsFileTime(&current);
908        {
909            double delta = filetime_seconds(
910                add_FILETIME( current, negate_FILETIME(creation) )
911                );
912            return delta;
913        }
914    }
915    return 0.0;
916}
917
918static double
919creation_time(HANDLE process)
920{
921    FILETIME creation, exit, kernel, user, current;
922    if (GetProcessTimes(process, &creation, &exit, &kernel, &user))
923    {
924        return filetime_seconds(creation);
925    }
926    return 0.0;
927}
928
929/* it's just stupidly silly that one has to do this! */
930typedef struct PROCESS_BASIC_INFORMATION__ {
931    LONG ExitStatus;
932    PVOID PebBaseAddress;
933    ULONG AffinityMask;
934    LONG BasePriority;
935    ULONG UniqueProcessId;
936    ULONG InheritedFromUniqueProcessId;
937    } PROCESS_BASIC_INFORMATION_;
938typedef LONG (__stdcall * NtQueryInformationProcess__)(
939    HANDLE ProcessHandle,
940    LONG ProcessInformationClass,
941    PVOID ProcessInformation,
942    ULONG ProcessInformationLength,
943    PULONG ReturnLength);
944static NtQueryInformationProcess__ NtQueryInformationProcess_ = NULL;
945static HMODULE NTDLL_ = NULL;
946DWORD get_process_id(HANDLE process)
947{
948    PROCESS_BASIC_INFORMATION_ pinfo;
949    if ( ! NtQueryInformationProcess_ )
950    {
951        if ( ! NTDLL_ )
952        {
953            NTDLL_ = GetModuleHandleA("ntdll");
954        }
955        if ( NTDLL_ )
956        {
957            NtQueryInformationProcess_
958                = (NtQueryInformationProcess__)GetProcAddress( NTDLL_,"NtQueryInformationProcess" );
959        }
960    }
961    if ( NtQueryInformationProcess_ )
962    {
963        LONG r = (*NtQueryInformationProcess_)(
964            process,/* ProcessBasicInformation == */ 0,&pinfo,sizeof(PROCESS_BASIC_INFORMATION_),NULL);
965        return pinfo.UniqueProcessId;
966    }
967    else
968    {
969        return 0;
970    }
971}
972
973/* not really optimal, or efficient, but it's easier this way, and it's not
974like we are going to be killing thousands, or even tens or processes. */
975static void
976kill_all(DWORD pid, HANDLE process)
977{
978    HANDLE process_snapshot_h = INVALID_HANDLE_VALUE;
979    if ( !pid )
980    {
981        pid = get_process_id(process);
982    }
983    process_snapshot_h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
984   
985    if (INVALID_HANDLE_VALUE != process_snapshot_h)
986    {
987        BOOL ok = TRUE;
988        PROCESSENTRY32 pinfo;
989        pinfo.dwSize = sizeof(PROCESSENTRY32);
990        for (
991            ok = Process32First(process_snapshot_h,&pinfo);
992            TRUE == ok;
993            ok = Process32Next(process_snapshot_h,&pinfo) )
994        {
995            if (pinfo.th32ParentProcessID == pid)
996            {
997                /* found a child, recurse to kill it and anything else below it */
998                HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pinfo.th32ProcessID);
999                if (NULL != ph)
1000                {
1001                    kill_all(pinfo.th32ProcessID,ph);
1002                    CloseHandle(ph);
1003                }
1004            }
1005        }
1006        CloseHandle(process_snapshot_h);
1007    }
1008    /* now that the children are all dead, kill the root */
1009    TerminateProcess(process,-2);
1010}
1011
1012/* Recursive check if first process is parent (directly or indirectly) of
1013the second one. Both processes are passed as process ids, not handles.
1014Special return value 2 means that the second process is smss.exe and its
1015parent process is System (first argument is ignored) */
1016static int 
1017is_parent_child(DWORD parent, DWORD child)
1018{
1019    HANDLE process_snapshot_h = INVALID_HANDLE_VALUE;
1020
1021    if (!child)
1022        return 0;
1023    if (parent == child)
1024        return 1;
1025
1026    process_snapshot_h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
1027    if (INVALID_HANDLE_VALUE != process_snapshot_h)
1028    {
1029        BOOL ok = TRUE;
1030        PROCESSENTRY32 pinfo;
1031        pinfo.dwSize = sizeof(PROCESSENTRY32);
1032        for (
1033            ok = Process32First(process_snapshot_h, &pinfo); 
1034            ok == TRUE; 
1035            ok = Process32Next(process_snapshot_h, &pinfo) )
1036        {
1037            if (pinfo.th32ProcessID == child)
1038            {
1039                /*
1040                Unfortunately, process ids are not really unique. There might
1041                be spurious "parent and child" relationship match between
1042                two non-related processes if real parent process of a given
1043                process has exited (while child process kept running as an
1044                "orphan") and the process id of such parent process has been
1045                reused by internals of the operating system when creating
1046                another process. Thus additional check is needed - process
1047                creation time. This check may fail (ie. return 0) for system
1048                processes due to insufficient privileges, and that's OK. */
1049                double tchild = 0.0;
1050                double tparent = 0.0;
1051                HANDLE hchild = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ProcessID);
1052
1053                CloseHandle(process_snapshot_h);
1054
1055                /* csrss.exe may display message box like following:
1056                    xyz.exe - Unable To Locate Component
1057                    This application has failed to start because
1058                    boost_foo-bar.dll was not found. Re-installing the
1059                    application may fix the problem
1060                This actually happens when starting test process that depends
1061                on a dynamic library which failed to build. We want to
1062                automatically close these message boxes even though csrss.exe
1063                is not our child process. We may depend on the fact that (in
1064                all current versions of Windows) csrss.exe is directly
1065                child of smss.exe process, which in turn is directly child of
1066                System process, which always has process id == 4 .
1067                This check must be performed before comparison of process
1068                creation time */
1069                if (stricmp(pinfo.szExeFile, "csrss.exe") == 0
1070                    && is_parent_child(parent, pinfo.th32ParentProcessID) == 2)
1071                {
1072                    return 1;
1073                }
1074                else if (stricmp(pinfo.szExeFile, "smss.exe") == 0
1075                    && pinfo.th32ParentProcessID == 4)
1076                {
1077                    return 2;
1078                }
1079
1080                if (hchild != 0)
1081                {
1082                    HANDLE hparent = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ParentProcessID);
1083                    if (hparent != 0)
1084                    {
1085                        tchild = creation_time(hchild);
1086                        tparent = creation_time(hparent);
1087                       
1088                        CloseHandle(hparent);
1089                    }
1090                    CloseHandle(hchild);
1091                }
1092
1093                /* return 0 if one of the following is true:
1094                1. we failed to read process creation time
1095                2. child was created before alleged parent */
1096                if (tchild == 0.0 || tparent == 0.0 || tchild < tparent)
1097                    return 0;
1098
1099                return is_parent_child(parent, pinfo.th32ParentProcessID) & 1;
1100            }
1101        }
1102
1103        CloseHandle(process_snapshot_h);
1104    }
1105
1106    return 0;
1107}
1108
1109typedef struct PROCESS_HANDLE_ID {HANDLE h; DWORD pid;} PROCESS_HANDLE_ID;
1110
1111/* This function is called by the operating system for each topmost window. */
1112BOOL CALLBACK
1113window_enum(HWND hwnd, LPARAM lParam)
1114{
1115    char buf[7] = {0};
1116    PROCESS_HANDLE_ID p = *((PROCESS_HANDLE_ID*) (lParam));
1117    DWORD pid = 0;
1118    DWORD tid = 0;
1119
1120    /* we want to find and close any window that:
1121    1. is visible and
1122    2. is a dialog and
1123    3. is displayed by any of our child processes */
1124    if (!IsWindowVisible(hwnd))
1125        return TRUE;
1126
1127    if (!GetClassNameA(hwnd, buf, sizeof(buf)))
1128        return TRUE; /* failed to read class name; presume it's not a dialog */
1129 
1130    if (strcmp(buf, "#32770") != 0)
1131        return TRUE; /* not a dialog */
1132
1133    /* GetWindowThreadProcessId returns 0 on error, otherwise thread id
1134    of window message pump thread */
1135    tid = GetWindowThreadProcessId(hwnd, &pid);
1136 
1137    if (tid && is_parent_child(p.pid, pid))
1138    {
1139        /* ask really nice */
1140        PostMessageA(hwnd, WM_CLOSE, 0, 0);
1141        /* now wait and see if it worked. If not, insist */
1142        if (WaitForSingleObject(p.h, 200) == WAIT_TIMEOUT)
1143        {
1144            PostThreadMessageA(tid, WM_QUIT, 0, 0);
1145            WaitForSingleObject(p.h, 300);
1146        }
1147       
1148        /* done, we do not want to check any other window now */
1149        return FALSE;
1150    }
1151
1152    return TRUE;
1153}
1154
1155static void 
1156close_alert(HANDLE process)
1157{
1158    DWORD pid = get_process_id(process);
1159    /* If process already exited or we just cannot get its process id, do not
1160    go any further */
1161    if (pid)
1162    {
1163        PROCESS_HANDLE_ID p = {process, pid};
1164        EnumWindows(&window_enum, (LPARAM) &p);
1165    }
1166}
1167
1168static int
1169my_wait( int *status )
1170{
1171        int i, num_active = 0;
1172        DWORD exitcode, waitcode;
1173        HANDLE active_handles[MAXJOBS];
1174
1175        /* first see if any non-waited-for processes are dead,
1176         * and return if so.
1177         */
1178        for ( i = 0; i < globs.jobs; i++ )
1179    {
1180        int pid = cmdtab[i].pid;
1181       
1182            if ( pid )
1183        {
1184            process_state state
1185                = check_process_exit((HANDLE)pid, status, active_handles, &num_active);
1186           
1187            if ( state == process_error )
1188                goto FAILED;
1189            else if ( state == process_finished )
1190                return pid;
1191            }
1192        }
1193
1194        /* if a child exists, wait for it to die */
1195        if ( !num_active )
1196    {
1197            errno = ECHILD;
1198            return -1;
1199        }
1200   
1201    if ( globs.timeout > 0 )
1202    {
1203        unsigned int alert_wait = 1;
1204        /* with a timeout we wait for a finish or a timeout, we check every second
1205         to see if something timed out */
1206        for (waitcode = WAIT_TIMEOUT; waitcode == WAIT_TIMEOUT; ++alert_wait)
1207        {
1208            waitcode = WaitForMultipleObjects( num_active, active_handles, FALSE, 1*1000 /* 1 second */ );
1209            if ( waitcode == WAIT_TIMEOUT )
1210            {
1211                /* check if any jobs have surpassed the maximum run time. */
1212                for ( i = 0; i < num_active; ++i )
1213                {
1214                    double t = running_time(active_handles[i]);
1215
1216                    /* periodically (each 5 secs) check and close message boxes
1217                    displayed by any of our child processes */
1218                    if ((alert_wait % ((unsigned int) 5)) == 0)
1219                        close_alert(active_handles[i]);
1220
1221                    if ( t > (double)globs.timeout )
1222                    {
1223                        /* the job may have left an alert dialog around,
1224                        try and get rid of it before killing */
1225                        close_alert(active_handles[i]);
1226                        /* we have a "runaway" job, kill it */
1227                        kill_all(0,active_handles[i]);
1228                        /* indicate the job "finished" so we query its status below */
1229                        waitcode = WAIT_ABANDONED_0+i;
1230                    }
1231                }
1232            }
1233        }
1234    }
1235    else
1236    {
1237        /* no timeout, so just wait indefinately for something to finish */
1238        waitcode = WaitForMultipleObjects( num_active, active_handles, FALSE, INFINITE );
1239    }
1240        if ( waitcode != WAIT_FAILED )
1241    {
1242            if ( waitcode >= WAIT_ABANDONED_0
1243             && waitcode < WAIT_ABANDONED_0 + num_active )
1244            i = waitcode - WAIT_ABANDONED_0;
1245            else
1246            i = waitcode - WAIT_OBJECT_0;
1247       
1248        if ( check_process_exit(active_handles[i], status, 0, 0) == process_finished )
1249            return (int)active_handles[i];
1250        }
1251
1252FAILED:
1253        errno = GetLastError();
1254        return -1;
1255   
1256}
1257
1258# endif /* !__BORLANDC__ */
1259
1260# endif /* USE_EXECNT */
Note: See TracBrowser for help on using the repository browser.