/* * Copyright 1993, 1995 Christopher Seiwald. * * This file is part of Jam - see jam.c for Copyright information. */ /* This file is ALSO: * Copyright 2001-2004 David Abrahams. * Copyright 2007 Rene Rivera. * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) */ #include "jam.h" #include "lists.h" #include "execcmd.h" #include "pathsys.h" #include "string.h" #include "output.h" #include #include #include #include #include #ifdef USE_EXECNT #define WIN32_LEAN_AND_MEAN #include #include #include /* * execnt.c - execute a shell command on Windows NT * * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp(). * The default is: * * /bin/sh -c % [ on UNIX/AmigaOS ] * cmd.exe /c % [ on Windows NT ] * * Each word must be an individual element in a jam variable value. * * In $(JAMSHELL), % expands to the command string and ! expands to * the slot number (starting at 1) for multiprocess (-j) invocations. * If $(JAMSHELL) doesn't include a %, it is tacked on as the last * argument. * * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work! * * External routines: * exec_cmd() - launch an async command execution. * exec_wait() - wait and drive at most one execution completion. * * Internal routines: * onintr() - bump intr to note command interruption. * * 04/08/94 (seiwald) - Coherent/386 support added. * 05/04/94 (seiwald) - async multiprocess interface * 01/22/95 (seiwald) - $(JAMSHELL) support * 06/02/97 (gsar) - full async multiprocess support for Win32 */ /* get the maximum command line length according to the OS */ int maxline(); /* delete and argv list */ static void free_argv(char**); /* Convert a command string into arguments for spawnvp. */ static char** string_to_args(const char*); /* bump intr to note command interruption */ static void onintr(int); /* If the command is suitable for execution via spawnvp */ long can_spawn(char*); /* Add two 64-bit unsigned numbers, h1l1 and h2l2 */ static FILETIME add_64( unsigned long h1, unsigned long l1, unsigned long h2, unsigned long l2); static FILETIME add_FILETIME(FILETIME t1, FILETIME t2); static FILETIME negate_FILETIME(FILETIME t); /* Convert a FILETIME to a number of seconds */ static double filetime_seconds(FILETIME t); /* record the timing info for the process */ static void record_times(HANDLE, timing_info*); /* calc the current running time of an *active* process */ static double running_time(HANDLE); /* */ DWORD get_process_id(HANDLE); /* terminate the given process, after terminating all its children */ static void kill_process_tree(DWORD, HANDLE); /* waits for a command to complete or for the given timeout, whichever is first */ static int try_wait(int timeoutMillis); /* reads any pending output for running commands */ static void read_output(); /* checks if a command ran out of time, and kills it */ static int try_kill_one(); /* */ static double creation_time(HANDLE); /* Recursive check if first process is parent (directly or indirectly) of the second one. */ static int is_parent_child(DWORD, DWORD); /* */ static void close_alert(HANDLE); /* close any alerts hanging around */ static void close_alerts(); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ static int intr = 0; static int cmdsrunning = 0; static void (* istat)( int ); /* The list of commands we run. */ static struct { string action; /* buffer to hold action */ string target; /* buffer to hold target */ string command; /* buffer to hold command being invoked */ /* Temporary batch file used to execute the action when needed. */ char * tempfile_bat; /* Pipes for communicating with the child process. Parent reads from (0), * child writes to (1). */ HANDLE pipe_out[ 2 ]; HANDLE pipe_err[ 2 ]; string buffer_out; /* buffer to hold stdout, if any */ string buffer_err; /* buffer to hold stderr, if any */ PROCESS_INFORMATION pi; /* running process information */ DWORD exit_code; /* executed command's exit code */ int exit_reason; /* reason why a command completed */ /* Function called when the command completes. */ void (* func)( void * closure, int status, timing_info *, char *, char * ); /* Opaque data passed back to the 'func' callback called when the command * completes. */ void * closure; } cmdtab[ MAXJOBS ] = { { 0 } }; /* * Execution unit tests. */ void execnt_unit_test() { #if !defined( NDEBUG ) /* vc6 preprocessor is broken, so assert with these strings gets confused. * Use a table instead. */ typedef struct test { char * command; int result; } test; test tests[] = { { "x", 0 }, { "x\n ", 0 }, { "x\ny", 1 }, { "x\n\n y", 1 }, { "echo x > foo.bar", 1 }, { "echo x < foo.bar", 1 }, { "echo x \">\" foo.bar", 0 }, { "echo x \"<\" foo.bar", 0 }, { "echo x \\\">\\\" foo.bar", 1 }, { "echo x \\\"<\\\" foo.bar", 1 } }; int i; for ( i = 0; i < sizeof( tests ) / sizeof( *tests ); ++i ) assert( !can_spawn( tests[ i ].command ) == tests[ i ].result ); { char * long_command = BJAM_MALLOC_ATOMIC( MAXLINE + 10 ); assert( long_command != 0 ); memset( long_command, 'x', MAXLINE + 9 ); long_command[ MAXLINE + 9 ] = 0; assert( can_spawn( long_command ) == MAXLINE + 9 ); BJAM_FREE( long_command ); } { /* Work around vc6 bug; it doesn't like escaped string * literals inside assert */ char * * argv = string_to_args(" \"g++\" -c -I\"Foobar\"" ); char const expected[] = "-c -I\"Foobar\""; assert( !strcmp( argv[ 0 ], "g++" ) ); assert( !strcmp( argv[ 1 ], expected ) ); free_argv( argv ); } #endif } /* * exec_cmd() - launch an async command execution. */ void exec_cmd ( char * command, void (* func)( void * closure, int status, timing_info *, char * invoked_command, char * command_output ), void * closure, LIST * shell, char * action, char * target ) { int slot; int raw_cmd = 0 ; char * argv_static[ MAXARGC + 1 ]; /* +1 for NULL */ char * * argv = argv_static; char * p; char * command_orig = command; /* Check to see if we need to hack around the line-length limitation. Look * for a JAMSHELL setting of "%", indicating that the command should be * invoked directly. */ if ( shell && !strcmp( shell->string, "%" ) && !list_next( shell ) ) { raw_cmd = 1; shell = 0; } /* Find a slot in the running commands table for this one. */ for ( slot = 0; slot < MAXJOBS; ++slot ) if ( !cmdtab[ slot ].pi.hProcess ) break; if ( slot == MAXJOBS ) { printf( "no slots for child!\n" ); exit( EXITBAD ); } /* Compute the name of a temp batch file, for possible use. */ if ( !cmdtab[ slot ].tempfile_bat ) { char const * tempdir = path_tmpdir(); DWORD procID = GetCurrentProcessId(); /* SVA - allocate 64 bytes extra just to be safe. */ cmdtab[ slot ].tempfile_bat = BJAM_MALLOC_ATOMIC( strlen( tempdir ) + 64 ); sprintf( cmdtab[ slot ].tempfile_bat, "%s\\jam%d-%02d.bat", tempdir, procID, slot ); } /* Trim leading, -ending- white space */ while ( *( command + 1 ) && isspace( *command ) ) ++command; /* Write to .BAT file unless the line would be too long and it meets the * other spawnability criteria. */ if ( raw_cmd && ( can_spawn( command ) >= MAXLINE ) ) { if ( DEBUG_EXECCMD ) printf("Executing raw command directly\n"); } else { FILE * f = 0; int tries = 0; raw_cmd = 0; /* Write command to bat file. For some reason this open can fail * intermitently. But doing some retries works. Most likely this is due * to a previously existing file of the same name that happens to be * opened by an active virus scanner. Pointed out and fixed by Bronek * Kozicki. */ for ( ; !f && ( tries < 4 ); ++tries ) { f = fopen( cmdtab[ slot ].tempfile_bat, "w" ); if ( !f && ( tries < 4 ) ) Sleep( 250 ); } if ( !f ) { printf( "failed to write command file!\n" ); exit( EXITBAD ); } fputs( command, f ); fclose( f ); command = cmdtab[ slot ].tempfile_bat; if ( DEBUG_EXECCMD ) { if ( shell ) printf( "using user-specified shell: %s", shell->string ); else printf( "Executing through .bat file\n" ); } } /* Formulate argv; If shell was defined, be prepared for % and ! subs. * Otherwise, use stock cmd.exe. */ if ( shell ) { int i; char jobno[ 4 ]; int gotpercent = 0; sprintf( jobno, "%d", slot + 1 ); for ( i = 0; shell && ( i < MAXARGC ); ++i, shell = list_next( shell ) ) { switch ( shell->string[ 0 ] ) { case '%': argv[ i ] = command; ++gotpercent; break; case '!': argv[ i ] = jobno; break; default : argv[ i ] = shell->string; } if ( DEBUG_EXECCMD ) printf( "argv[%d] = '%s'\n", i, argv[ i ] ); } if ( !gotpercent ) argv[ i++ ] = command; argv[ i ] = 0; } else if ( raw_cmd ) { argv = string_to_args( command ); } else { argv[ 0 ] = "cmd.exe"; argv[ 1 ] = "/Q/C"; /* anything more is non-portable */ argv[ 2 ] = command; argv[ 3 ] = 0; } /* Catch interrupts whenever commands are running. */ if ( !cmdsrunning++ ) istat = signal( SIGINT, onintr ); /* Start the command. */ { SECURITY_ATTRIBUTES sa = { sizeof( SECURITY_ATTRIBUTES ), 0, 0 }; SECURITY_DESCRIPTOR sd; STARTUPINFO si = { sizeof( STARTUPINFO ), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; string cmd; /* Init the security data. */ InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION ); SetSecurityDescriptorDacl( &sd, TRUE, NULL, FALSE ); sa.lpSecurityDescriptor = &sd; sa.bInheritHandle = TRUE; /* Create the stdout, which is also the merged out + err, pipe. */ if ( !CreatePipe( &cmdtab[ slot ].pipe_out[ 0 ], &cmdtab[ slot ].pipe_out[ 1 ], &sa, 0 ) ) { perror( "CreatePipe" ); exit( EXITBAD ); } /* Create the stdout, which is also the merged out+err, pipe. */ if ( globs.pipe_action == 2 ) { if ( !CreatePipe( &cmdtab[ slot ].pipe_err[ 0 ], &cmdtab[ slot ].pipe_err[ 1 ], &sa, 0 ) ) { perror( "CreatePipe" ); exit( EXITBAD ); } } /* Set handle inheritance off for the pipe ends the parent reads from. */ SetHandleInformation( cmdtab[ slot ].pipe_out[ 0 ], HANDLE_FLAG_INHERIT, 0 ); if ( globs.pipe_action == 2 ) SetHandleInformation( cmdtab[ slot ].pipe_err[ 0 ], HANDLE_FLAG_INHERIT, 0 ); /* Hide the child window, if any. */ si.dwFlags |= STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; /* Set the child outputs to the pipes. */ si.dwFlags |= STARTF_USESTDHANDLES; si.hStdOutput = cmdtab[ slot ].pipe_out[ 1 ]; if ( globs.pipe_action == 2 ) { /* Pipe stderr to the action error output. */ si.hStdError = cmdtab[ slot ].pipe_err[ 1 ]; } else if ( globs.pipe_action == 1 ) { /* Pipe stderr to the console error output. */ si.hStdError = GetStdHandle( STD_ERROR_HANDLE ); } else { /* Pipe stderr to the action merged output. */ si.hStdError = cmdtab[ slot ].pipe_out[ 1 ]; } /* Let the child inherit stdin, as some commands assume it's available. */ si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); /* Save the operation for exec_wait() to find. */ cmdtab[ slot ].func = func; cmdtab[ slot ].closure = closure; if ( action && target ) { string_copy( &cmdtab[ slot ].action, action ); string_copy( &cmdtab[ slot ].target, target ); } else { string_free( &cmdtab[ slot ].action ); string_new ( &cmdtab[ slot ].action ); string_free( &cmdtab[ slot ].target ); string_new ( &cmdtab[ slot ].target ); } string_copy( &cmdtab[ slot ].command, command_orig ); /* Put together the command we run. */ { char * * argp = argv; string_new( &cmd ); string_copy( &cmd, *(argp++) ); while ( *argp ) { string_push_back( &cmd, ' ' ); string_append( &cmd, *(argp++) ); } } /* Create output buffers. */ string_new( &cmdtab[ slot ].buffer_out ); string_new( &cmdtab[ slot ].buffer_err ); /* Run the command by creating a sub-process for it. */ if ( ! CreateProcess( NULL , /* application name */ cmd.value , /* command line */ NULL , /* process attributes */ NULL , /* thread attributes */ TRUE , /* inherit handles */ CREATE_NEW_PROCESS_GROUP, /* create flags */ NULL , /* env vars, null inherits env */ NULL , /* current dir, null is our */ /* current dir */ &si , /* startup info */ &cmdtab[ slot ].pi /* child process info, if created */ ) ) { perror( "CreateProcess" ); exit( EXITBAD ); } /* Clean up temporary stuff. */ string_free( &cmd ); } /* Wait until we are under the limit of concurrent commands. Do not trust * globs.jobs alone. */ while ( ( cmdsrunning >= MAXJOBS ) || ( cmdsrunning >= globs.jobs ) ) if ( !exec_wait() ) break; if ( argv != argv_static ) free_argv( argv ); } /* * exec_wait() * * wait and drive at most one execution completion. * * waits for one command to complete, while processing the i/o for all * ongoing commands. * * Returns 0 if called when there were no more commands being executed or 1 * otherwise. */ int exec_wait() { int i = -1; /* Handle naive make1() which does not know if cmds are running. */ if ( !cmdsrunning ) return 0; /* Wait for a command to complete, while snarfing up any output. */ do { /* Check for a complete command, briefly. */ i = try_wait(500); /* Read in the output of all running commands. */ read_output(); /* Close out pending debug style dialogs. */ close_alerts(); /* Check if a command ran out of time. */ if ( i < 0 ) i = try_kill_one(); } while ( i < 0 ); /* We have a command... process it. */ --cmdsrunning; { timing_info time; int rstat; /* The time data for the command. */ record_times( cmdtab[ i ].pi.hProcess, &time ); /* Clear the temp file. */ if ( cmdtab[ i ].tempfile_bat ) { unlink( cmdtab[ i ].tempfile_bat ); BJAM_FREE( cmdtab[ i ].tempfile_bat ); cmdtab[ i ].tempfile_bat = NULL; } /* Find out the process exit code. */ GetExitCodeProcess( cmdtab[ i ].pi.hProcess, &cmdtab[ i ].exit_code ); /* The dispossition of the command. */ if ( intr ) rstat = EXEC_CMD_INTR; else if ( cmdtab[ i ].exit_code != 0 ) rstat = EXEC_CMD_FAIL; else rstat = EXEC_CMD_OK; /* Output the action block. */ out_action( cmdtab[ i ].action.size > 0 ? cmdtab[ i ].action.value : 0, cmdtab[ i ].target.size > 0 ? cmdtab[ i ].target.value : 0, cmdtab[ i ].command.size > 0 ? cmdtab[ i ].command.value : 0, cmdtab[ i ].buffer_out.size > 0 ? cmdtab[ i ].buffer_out.value : 0, cmdtab[ i ].buffer_err.size > 0 ? cmdtab[ i ].buffer_err.value : 0, cmdtab[ i ].exit_reason ); /* Call the callback, may call back to jam rule land. Assume -p0 in * effect so only pass buffer containing merged output. */ (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time, cmdtab[ i ].command.value, cmdtab[ i ].buffer_out.value ); /* Clean up the command data, process, etc. */ string_free( &cmdtab[ i ].action ); string_new( &cmdtab[ i ].action ); string_free( &cmdtab[ i ].target ); string_new( &cmdtab[ i ].target ); string_free( &cmdtab[ i ].command ); string_new( &cmdtab[ i ].command ); if ( cmdtab[ i ].pi.hProcess ) { CloseHandle( cmdtab[ i ].pi.hProcess ); cmdtab[ i ].pi.hProcess = 0; } if ( cmdtab[ i ].pi.hThread ) { CloseHandle( cmdtab[ i ].pi.hThread ); cmdtab[ i ].pi.hThread = 0; } if ( cmdtab[ i ].pipe_out[ 0 ] ) { CloseHandle( cmdtab[ i ].pipe_out[ 0 ] ); cmdtab[ i ].pipe_out[ 0 ] = 0; } if ( cmdtab[ i ].pipe_out[ 1 ] ) { CloseHandle( cmdtab[ i ].pipe_out[ 1 ] ); cmdtab[ i ].pipe_out[ 1 ] = 0; } if ( cmdtab[ i ].pipe_err[ 0 ] ) { CloseHandle( cmdtab[ i ].pipe_err[ 0 ] ); cmdtab[ i ].pipe_err[ 0 ] = 0; } if ( cmdtab[ i ].pipe_err[ 1 ] ) { CloseHandle( cmdtab[ i ].pipe_err[ 1 ] ); cmdtab[ i ].pipe_err[ 1 ] = 0; } string_free( &cmdtab[ i ].buffer_out ); string_new( &cmdtab[ i ].buffer_out ); string_free( &cmdtab[ i ].buffer_err ); string_new( &cmdtab[ i ].buffer_err ); cmdtab[ i ].exit_code = 0; cmdtab[ i ].exit_reason = EXIT_OK; } return 1; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ static void free_argv( char * * args ) { BJAM_FREE( args[ 0 ] ); BJAM_FREE( args ); } /* * For more details on Windows cmd.exe shell command-line length limitations see * the following MSDN article: * http://support.microsoft.com/default.aspx?scid=kb;en-us;830473 */ int maxline() { OSVERSIONINFO os_info; os_info.dwOSVersionInfoSize = sizeof( os_info ); GetVersionEx( &os_info ); if ( os_info.dwMajorVersion >= 5 ) return 8191; /* XP > */ if ( os_info.dwMajorVersion == 4 ) return 2047; /* NT 4.x */ return 996; /* NT 3.5.1 */ } /* * Convert a command string into arguments for spawnvp(). The original code, * inherited from ftjam, tried to break up every argument on the command-line, * dealing with quotes, but that is really a waste of time on Win32, at least. * It turns out that all you need to do is get the raw path to the executable in * the first argument to spawnvp(), and you can pass all the rest of the * command-line arguments to spawnvp() in one, un-processed string. * * New strategy: break the string in at most one place. */ static char * * string_to_args( char const * string ) { int src_len; int in_quote; char * line; char const * src; char * dst; char * * argv; /* Drop leading and trailing whitespace if any. */ while ( isspace( *string ) ) ++string; src_len = strlen( string ); while ( ( src_len > 0 ) && isspace( string[ src_len - 1 ] ) ) --src_len; /* Copy the input string into a buffer we can modify. */ line = (char *)BJAM_MALLOC_ATOMIC( src_len + 1 ); if ( !line ) return 0; /* Allocate the argv array. * element 0: stores the path to the executable * element 1: stores the command-line arguments to the executable * element 2: NULL terminator */ argv = (char * *)BJAM_MALLOC( 3 * sizeof( char * ) ); if ( !argv ) { BJAM_FREE( line ); return 0; } /* Strip quotes from the first command-line argument and find where it ends. * Quotes are illegal in Win32 pathnames, so we do not need to worry about * preserving escaped quotes here. Spaces can not be escaped in Win32, only * enclosed in quotes, so removing backslash escapes is also a non-issue. */ in_quote = 0; for ( src = string, dst = line ; *src; ++src ) { if ( *src == '"' ) in_quote = !in_quote; else if ( !in_quote && isspace( *src ) ) break; else *dst++ = *src; } *dst++ = 0; argv[ 0 ] = line; /* Skip whitespace in src. */ while ( isspace( *src ) ) ++src; argv[ 1 ] = dst; /* Copy the rest of the arguments verbatim. */ src_len -= src - string; /* Use strncat() because it appends a trailing nul. */ *dst = 0; strncat( dst, src, src_len ); argv[ 2 ] = 0; return argv; } static void onintr( int disp ) { ++intr; printf( "...interrupted\n" ); } /* * can_spawn() - If the command is suitable for execution via spawnvp(), return * a number >= the number of characters it would occupy on the command-line. * Otherwise, return zero. */ long can_spawn( char * command ) { char * p; char inquote = 0; /* Move to the first non-whitespace. */ command += strspn( command, " \t" ); p = command; /* Look for newlines and unquoted i/o redirection. */ do { p += strcspn( p, "'\n\"<>|" ); switch ( *p ) { case '\n': /* Skip over any following spaces. */ while ( isspace( *p ) ) ++p; /* Must use a .bat file if there is anything significant following * the newline. */ if ( *p ) return 0; break; case '"': case '\'': if ( ( p > command ) && ( p[ -1 ] != '\\' ) ) { if ( inquote == *p ) inquote = 0; else if ( inquote == 0 ) inquote = *p; } ++p; break; case '<': case '>': case '|': if ( !inquote ) return 0; ++p; break; } } while ( *p ); /* Return the number of characters the command will occupy. */ return p - command; } /* 64-bit arithmetic helpers. */ /* Compute the carry bit from the addition of two 32-bit unsigned numbers. */ #define add_carry_bit( a, b ) ( (((a) | (b)) >> 31) & (~((a) + (b)) >> 31) & 0x1 ) /* Compute the high 32 bits of the addition of two 64-bit unsigned numbers, h1l1 and h2l2. */ #define add_64_hi( h1, l1, h2, l2 ) ((h1) + (h2) + add_carry_bit(l1, l2)) /* * Add two 64-bit unsigned numbers, h1l1 and h2l2. */ static FILETIME add_64 ( unsigned long h1, unsigned long l1, unsigned long h2, unsigned long l2 ) { FILETIME result; result.dwLowDateTime = l1 + l2; result.dwHighDateTime = add_64_hi( h1, l1, h2, l2 ); return result; } static FILETIME add_FILETIME( FILETIME t1, FILETIME t2 ) { return add_64( t1.dwHighDateTime, t1.dwLowDateTime, t2.dwHighDateTime, t2.dwLowDateTime ); } static FILETIME negate_FILETIME( FILETIME t ) { /* 2s complement negation */ return add_64( ~t.dwHighDateTime, ~t.dwLowDateTime, 0, 1 ); } /* * Convert a FILETIME to a number of seconds. */ static double filetime_seconds( FILETIME t ) { return t.dwHighDateTime * ( (double)( 1UL << 31 ) * 2.0 * 1.0e-7 ) + t.dwLowDateTime * 1.0e-7; } /* * What should be a simple conversion, turns out to be horribly complicated by * the defficiencies of MSVC and the Win32 API. */ static time_t filetime_dt( FILETIME t_utc ) { static int calc_time_diff = 1; static double time_diff; if ( calc_time_diff ) { struct tm t0_; FILETIME f0_local; FILETIME f0_; SYSTEMTIME s0_; GetSystemTime( &s0_ ); t0_.tm_year = s0_.wYear-1900; t0_.tm_mon = s0_.wMonth-1; t0_.tm_wday = s0_.wDayOfWeek; t0_.tm_mday = s0_.wDay; t0_.tm_hour = s0_.wHour; t0_.tm_min = s0_.wMinute; t0_.tm_sec = s0_.wSecond; t0_.tm_isdst = 0; SystemTimeToFileTime( &s0_, &f0_local ); LocalFileTimeToFileTime( &f0_local, &f0_ ); time_diff = filetime_seconds( f0_ ) - (double)mktime( &t0_ ); calc_time_diff = 0; } return ceil( filetime_seconds( t_utc ) - time_diff ); } static void record_times( HANDLE process, timing_info * time ) { FILETIME creation; FILETIME exit; FILETIME kernel; FILETIME user; if ( GetProcessTimes( process, &creation, &exit, &kernel, &user ) ) { time->system = filetime_seconds( kernel ); time->user = filetime_seconds( user ); time->start = filetime_dt ( creation ); time->end = filetime_dt ( exit ); } } #define IO_BUFFER_SIZE ( 16 * 1024 ) static char ioBuffer[ IO_BUFFER_SIZE + 1 ]; static void read_pipe ( HANDLE in, /* the pipe to read from */ string * out ) { DWORD bytesInBuffer = 0; DWORD bytesAvailable = 0; do { /* check if we have any data to read */ if ( !PeekNamedPipe( in, ioBuffer, IO_BUFFER_SIZE, &bytesInBuffer, &bytesAvailable, NULL ) ) bytesAvailable = 0; /* read in the available data */ if ( bytesAvailable > 0 ) { /* we only read in the available bytes, to avoid blocking */ if ( ReadFile( in, ioBuffer, bytesAvailable <= IO_BUFFER_SIZE ? bytesAvailable : IO_BUFFER_SIZE, &bytesInBuffer, NULL ) ) { if ( bytesInBuffer > 0 ) { /* Clean up some illegal chars. */ int i; for ( i = 0; i < bytesInBuffer; ++i ) { if ( ( (unsigned char)ioBuffer[ i ] < 1 ) ) ioBuffer[ i ] = '?'; } /* Null, terminate. */ ioBuffer[ bytesInBuffer ] = '\0'; /* Append to the output. */ string_append( out, ioBuffer ); /* Subtract what we read in. */ bytesAvailable -= bytesInBuffer; } else { /* Likely read a error, bail out. */ bytesAvailable = 0; } } else { /* Definitely read a error, bail out. */ bytesAvailable = 0; } } } while ( bytesAvailable > 0 ); } static void read_output() { int i; for ( i = 0; i < globs.jobs && i < MAXJOBS; ++i ) { /* Read stdout data. */ if ( cmdtab[ i ].pipe_out[ 0 ] ) read_pipe( cmdtab[ i ].pipe_out[ 0 ], & cmdtab[ i ].buffer_out ); /* Read stderr data. */ if ( cmdtab[ i ].pipe_err[ 0 ] ) read_pipe( cmdtab[ i ].pipe_err[ 0 ], & cmdtab[ i ].buffer_err ); } } /* * Waits for a single child process command to complete, or the timeout, * whichever comes first. Returns the index of the completed command in the * cmdtab array, or -1. */ static int try_wait( int timeoutMillis ) { int i; int num_active; int wait_api_result; HANDLE active_handles[ MAXJOBS ]; int active_procs[ MAXJOBS ]; /* Prepare a list of all active processes to wait for. */ for ( num_active = 0, i = 0; i < globs.jobs; ++i ) { if ( cmdtab[ i ].pi.hProcess ) { active_handles[ num_active ] = cmdtab[ i ].pi.hProcess; active_procs[ num_active ] = i; ++num_active; } } /* Wait for a child to complete, or for our timeout window to expire. */ wait_api_result = WaitForMultipleObjects( num_active, active_handles, FALSE, timeoutMillis ); if ( ( WAIT_OBJECT_0 <= wait_api_result ) && ( wait_api_result < WAIT_OBJECT_0 + num_active ) ) { /* Rerminated process detected - return its index. */ return active_procs[ wait_api_result - WAIT_OBJECT_0 ]; } /* Timeout. */ return -1; } static int try_kill_one() { /* Only need to check if a timeout was specified with the -l option. */ if ( globs.timeout > 0 ) { int i; for ( i = 0; i < globs.jobs; ++i ) { double t = running_time( cmdtab[ i ].pi.hProcess ); if ( t > (double)globs.timeout ) { /* The job may have left an alert dialog around, try and get rid * of it before killing */ close_alert( cmdtab[ i ].pi.hProcess ); /* We have a "runaway" job, kill it. */ kill_process_tree( 0, cmdtab[ i ].pi.hProcess ); /* And return it marked as a timeout. */ cmdtab[ i ].exit_reason = EXIT_TIMEOUT; return i; } } } return -1; } static void close_alerts() { /* We only attempt this every 5 seconds, or so, because it is not a cheap * operation, and we will catch the alerts eventually. This check uses * floats as some compilers define CLOCKS_PER_SEC as a float or double. */ if ( ( (float)clock() / (float)( CLOCKS_PER_SEC * 5 ) ) < ( 1.0 / 5.0 ) ) { int i; for ( i = 0; i < globs.jobs; ++i ) close_alert( cmdtab[ i ].pi.hProcess ); } } /* * Calc the current running time of an *active* process. */ static double running_time( HANDLE process ) { FILETIME creation; FILETIME exit; FILETIME kernel; FILETIME user; FILETIME current; if ( GetProcessTimes( process, &creation, &exit, &kernel, &user ) ) { /* Compute the elapsed time. */ GetSystemTimeAsFileTime( ¤t ); return filetime_seconds( add_FILETIME( current, negate_FILETIME( creation ) ) ); } return 0.0; } /* It is just stupidly silly that one has to do this. */ typedef struct PROCESS_BASIC_INFORMATION__ { LONG ExitStatus; PVOID PebBaseAddress; ULONG AffinityMask; LONG BasePriority; ULONG UniqueProcessId; ULONG InheritedFromUniqueProcessId; } PROCESS_BASIC_INFORMATION_; typedef LONG (__stdcall * NtQueryInformationProcess__)( HANDLE ProcessHandle, LONG ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength); static NtQueryInformationProcess__ NtQueryInformationProcess_ = NULL; static HMODULE NTDLL_ = NULL; DWORD get_process_id( HANDLE process ) { PROCESS_BASIC_INFORMATION_ pinfo; if ( !NtQueryInformationProcess_ ) { if ( ! NTDLL_ ) NTDLL_ = GetModuleHandleA( "ntdll" ); if ( NTDLL_ ) NtQueryInformationProcess_ = (NtQueryInformationProcess__)GetProcAddress( NTDLL_, "NtQueryInformationProcess" ); } if ( NtQueryInformationProcess_ ) { LONG r = (*NtQueryInformationProcess_)( process, /* ProcessBasicInformation == */ 0, &pinfo, sizeof( PROCESS_BASIC_INFORMATION_ ), NULL ); return pinfo.UniqueProcessId; } return 0; } /* * Not really optimal, or efficient, but it is easier this way, and it is not * like we are going to be killing thousands, or even tens of processes. */ static void kill_process_tree( DWORD pid, HANDLE process ) { HANDLE process_snapshot_h = INVALID_HANDLE_VALUE; if ( !pid ) pid = get_process_id( process ); process_snapshot_h = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); if ( INVALID_HANDLE_VALUE != process_snapshot_h ) { BOOL ok = TRUE; PROCESSENTRY32 pinfo; pinfo.dwSize = sizeof( PROCESSENTRY32 ); for ( ok = Process32First( process_snapshot_h, &pinfo ); ok == TRUE; ok = Process32Next( process_snapshot_h, &pinfo ) ) { if ( pinfo.th32ParentProcessID == pid ) { /* Found a child, recurse to kill it and anything else below it. */ HANDLE ph = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pinfo.th32ProcessID ); if ( NULL != ph ) { kill_process_tree( pinfo.th32ProcessID, ph ); CloseHandle( ph ); } } } CloseHandle( process_snapshot_h ); } /* Now that the children are all dead, kill the root. */ TerminateProcess( process, -2 ); } static double creation_time( HANDLE process ) { FILETIME creation; FILETIME exit; FILETIME kernel; FILETIME user; FILETIME current; return GetProcessTimes( process, &creation, &exit, &kernel, &user ) ? filetime_seconds( creation ) : 0.0; } /* * Recursive check if first process is parent (directly or indirectly) of the * second one. Both processes are passed as process ids, not handles. Special * return value 2 means that the second process is smss.exe and its parent * process is System (first argument is ignored). */ static int is_parent_child( DWORD parent, DWORD child ) { HANDLE process_snapshot_h = INVALID_HANDLE_VALUE; if ( !child ) return 0; if ( parent == child ) return 1; process_snapshot_h = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); if ( INVALID_HANDLE_VALUE != process_snapshot_h ) { BOOL ok = TRUE; PROCESSENTRY32 pinfo; pinfo.dwSize = sizeof( PROCESSENTRY32 ); for ( ok = Process32First( process_snapshot_h, &pinfo ); ok == TRUE; ok = Process32Next( process_snapshot_h, &pinfo ) ) { if ( pinfo.th32ProcessID == child ) { /* Unfortunately, process ids are not really unique. There might * be spurious "parent and child" relationship match between two * non-related processes if real parent process of a given * process has exited (while child process kept running as an * "orphan") and the process id of such parent process has been * reused by internals of the operating system when creating * another process. * * Thus additional check is needed - process creation time. This * check may fail (i.e. return 0) for system processes due to * insufficient privileges, and that is OK. */ double tchild = 0.0; double tparent = 0.0; HANDLE hchild = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ProcessID ); CloseHandle( process_snapshot_h ); /* csrss.exe may display message box like following: * xyz.exe - Unable To Locate Component * This application has failed to start because * boost_foo-bar.dll was not found. Re-installing the * application may fix the problem * This actually happens when starting test process that depends * on a dynamic library which failed to build. We want to * automatically close these message boxes even though csrss.exe * is not our child process. We may depend on the fact that (in * all current versions of Windows) csrss.exe is directly child * of the smss.exe process, which in turn is directly child of * the System process, which always has process id == 4. This * check must be performed before comparison of process creation * times. */ if ( !stricmp( pinfo.szExeFile, "csrss.exe" ) && ( is_parent_child( parent, pinfo.th32ParentProcessID ) == 2 ) ) return 1; if ( !stricmp( pinfo.szExeFile, "smss.exe" ) && ( pinfo.th32ParentProcessID == 4 ) ) return 2; if ( hchild ) { HANDLE hparent = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ParentProcessID ); if ( hparent ) { tchild = creation_time( hchild ); tparent = creation_time( hparent ); CloseHandle( hparent ); } CloseHandle( hchild ); } /* Return 0 if one of the following is true: * 1. we failed to read process creation time * 2. child was created before alleged parent */ if ( ( tchild == 0.0 ) || ( tparent == 0.0 ) || ( tchild < tparent ) ) return 0; return is_parent_child( parent, pinfo.th32ParentProcessID ) & 1; } } CloseHandle( process_snapshot_h ); } return 0; } typedef struct PROCESS_HANDLE_ID { HANDLE h; DWORD pid; } PROCESS_HANDLE_ID; /* * This function is called by the operating system for each topmost window. */ BOOL CALLBACK close_alert_window_enum( HWND hwnd, LPARAM lParam ) { char buf[ 7 ] = { 0 }; PROCESS_HANDLE_ID p = *( (PROCESS_HANDLE_ID *)lParam ); DWORD pid = 0; DWORD tid = 0; /* We want to find and close any window that: * 1. is visible and * 2. is a dialog and * 3. is displayed by any of our child processes */ if ( !IsWindowVisible( hwnd ) ) return TRUE; if ( !GetClassNameA( hwnd, buf, sizeof( buf ) ) ) return TRUE; /* Failed to read class name; presume it is not a dialog. */ if ( strcmp( buf, "#32770" ) ) return TRUE; /* Not a dialog */ /* GetWindowThreadProcessId() returns 0 on error, otherwise thread id of * window message pump thread. */ tid = GetWindowThreadProcessId( hwnd, &pid ); if ( tid && is_parent_child( p.pid, pid ) ) { /* Ask really nice. */ PostMessageA( hwnd, WM_CLOSE, 0, 0 ); /* Now wait and see if it worked. If not, insist. */ if ( WaitForSingleObject( p.h, 200 ) == WAIT_TIMEOUT ) { PostThreadMessageA( tid, WM_QUIT, 0, 0 ); WaitForSingleObject( p.h, 300 ); } /* Done, we do not want to check any other window now. */ return FALSE; } return TRUE; } static void close_alert( HANDLE process ) { DWORD pid = get_process_id( process ); /* If process already exited or we just can not get its process id, do not * go any further. */ if ( pid ) { PROCESS_HANDLE_ID p; p.h = process; p.pid = pid; EnumWindows( &close_alert_window_enum, (LPARAM)&p ); } } #endif /* USE_EXECNT */