blob: 76420451802ddb8469b723e84416cc8d815bb860 [file] [log] [blame]
/*
* 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 <errno.h>
#include <assert.h>
#include <ctype.h>
#include <time.h>
#include <math.h>
#ifdef USE_EXECNT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <process.h>
#include <tlhelp32.h>
/*
* 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( &current );
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 */