diff options
author | Kenneth Heafield <github@kheafield.com> | 2012-05-12 14:01:52 -0400 |
---|---|---|
committer | Kenneth Heafield <github@kheafield.com> | 2012-05-12 14:01:52 -0400 |
commit | 3faecf9a00512dcbc8712c4bca9adae72fb64410 (patch) | |
tree | 9761b50d12f81a675fb7cbc663ceebad15079f78 /jam-files/engine/execnt.c | |
parent | c806a8fff63043f63773874986301f2822a2b552 (diff) |
Give in and copy bjam into cdec source code
Diffstat (limited to 'jam-files/engine/execnt.c')
-rw-r--r-- | jam-files/engine/execnt.c | 1296 |
1 files changed, 1296 insertions, 0 deletions
diff --git a/jam-files/engine/execnt.c b/jam-files/engine/execnt.c new file mode 100644 index 00000000..76420451 --- /dev/null +++ b/jam-files/engine/execnt.c @@ -0,0 +1,1296 @@ +/* + * 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( ¤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 */ |