|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "jam.h" |
|
#ifdef USE_EXECNT |
|
#include "execcmd.h" |
|
|
|
#include "lists.h" |
|
#include "output.h" |
|
#include "pathsys.h" |
|
#include "string.h" |
|
|
|
#include <assert.h> |
|
#include <ctype.h> |
|
#include <errno.h> |
|
#include <time.h> |
|
|
|
#define WIN32_LEAN_AND_MEAN |
|
#include <windows.h> |
|
#include <process.h> |
|
#include <tlhelp32.h> |
|
|
|
|
|
|
|
static int maxline(); |
|
|
|
static long raw_command_length( char const * command ); |
|
|
|
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 ); |
|
|
|
static void record_times( HANDLE const, timing_info * const ); |
|
|
|
static double running_time( HANDLE const ); |
|
|
|
static void kill_process_tree( DWORD const procesdId, HANDLE const ); |
|
|
|
static int try_wait( int const timeoutMillis ); |
|
|
|
static void read_output(); |
|
|
|
static int try_kill_one(); |
|
|
|
static int is_parent_child( DWORD const parent, DWORD const child ); |
|
|
|
static void close_alert( PROCESS_INFORMATION const * const ); |
|
|
|
static void close_alerts(); |
|
|
|
static char const * prepare_command_file( string const * command, int slot ); |
|
|
|
static void invoke_cmd( char const * const command, int const slot ); |
|
|
|
static int get_free_cmdtab_slot(); |
|
|
|
static void string_new_from_argv( string * result, char const * const * argv ); |
|
|
|
static void string_renew( string * const ); |
|
|
|
static void reportWindowsError( char const * const apiName ); |
|
|
|
static void closeWinHandle( HANDLE * const handle ); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define MAX_RAW_COMMAND_LENGTH 32766 |
|
|
|
|
|
|
|
|
|
#define EXECCMD_PIPE_READ 0 |
|
#define EXECCMD_PIPE_WRITE 1 |
|
|
|
static int intr_installed; |
|
|
|
|
|
|
|
static struct |
|
{ |
|
|
|
string command_file[ 1 ]; |
|
|
|
|
|
|
|
|
|
HANDLE pipe_out[ 2 ]; |
|
HANDLE pipe_err[ 2 ]; |
|
|
|
string buffer_out[ 1 ]; |
|
string buffer_err[ 1 ]; |
|
|
|
PROCESS_INFORMATION pi; |
|
|
|
|
|
ExecCmdCallback func; |
|
|
|
|
|
void * closure; |
|
} |
|
cmdtab[ MAXJOBS ] = { { 0 } }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
void execnt_unit_test() |
|
{ |
|
#if !defined( NDEBUG ) |
|
|
|
|
|
|
|
{ |
|
typedef struct test { char * command; int result; } test; |
|
test tests[] = { |
|
{ "", 0 }, |
|
{ " ", 0 }, |
|
{ "x", 1 }, |
|
{ "\nx", 1 }, |
|
{ "x\n", 1 }, |
|
{ "\nx\n", 1 }, |
|
{ "\nx \n", 2 }, |
|
{ "\nx \n ", 2 }, |
|
{ " \n\t\t\v\r\r\n \t x \v \t\t\r\n\n\n \n\n\v\t", 8 }, |
|
{ "x\ny", -1 }, |
|
{ "x\n\n y", -1 }, |
|
{ "echo x > foo.bar", -1 }, |
|
{ "echo x < foo.bar", -1 }, |
|
{ "echo x | foo.bar", -1 }, |
|
{ "echo x \">\" foo.bar", 18 }, |
|
{ "echo x '<' foo.bar", 18 }, |
|
{ "echo x \"|\" foo.bar", 18 }, |
|
{ "echo x \\\">\\\" foo.bar", -1 }, |
|
{ "echo x \\\"<\\\" foo.bar", -1 }, |
|
{ "echo x \\\"|\\\" foo.bar", -1 }, |
|
{ "\"echo x > foo.bar\"", 18 }, |
|
{ "echo x \"'\"<' foo.bar", -1 }, |
|
{ "echo x \\\\\"<\\\\\" foo.bar", 22 }, |
|
{ "echo x \\x\\\"<\\\\\" foo.bar", -1 }, |
|
{ 0 } }; |
|
test const * t; |
|
for ( t = tests; t->command; ++t ) |
|
assert( raw_command_length( t->command ) == t->result ); |
|
} |
|
|
|
{ |
|
int const length = maxline() + 9; |
|
char * const cmd = (char *)BJAM_MALLOC_ATOMIC( length + 1 ); |
|
memset( cmd, 'x', length ); |
|
cmd[ length ] = 0; |
|
assert( raw_command_length( cmd ) == length ); |
|
BJAM_FREE( cmd ); |
|
} |
|
#endif |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
int exec_check |
|
( |
|
string const * command, |
|
LIST * * pShell, |
|
int * error_length, |
|
int * error_max_length |
|
) |
|
{ |
|
|
|
|
|
|
|
|
|
|
|
if ( list_empty( *pShell ) ) |
|
{ |
|
char const * s = command->value; |
|
while ( isspace( *s ) ) ++s; |
|
if ( !*s ) |
|
return EXEC_CHECK_NOOP; |
|
} |
|
|
|
|
|
if ( is_raw_command_request( *pShell ) ) |
|
{ |
|
int const raw_cmd_length = raw_command_length( command->value ); |
|
if ( raw_cmd_length < 0 ) |
|
{ |
|
|
|
list_free( *pShell ); |
|
*pShell = L0; |
|
} |
|
else if ( raw_cmd_length > MAX_RAW_COMMAND_LENGTH ) |
|
{ |
|
*error_length = raw_cmd_length; |
|
*error_max_length = MAX_RAW_COMMAND_LENGTH; |
|
return EXEC_CHECK_TOO_LONG; |
|
} |
|
else |
|
return raw_cmd_length ? EXEC_CHECK_OK : EXEC_CHECK_NOOP; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return check_cmd_for_too_long_lines( command->value, maxline(), |
|
error_length, error_max_length ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void exec_cmd |
|
( |
|
string const * cmd_orig, |
|
ExecCmdCallback func, |
|
void * closure, |
|
LIST * shell |
|
) |
|
{ |
|
int const slot = get_free_cmdtab_slot(); |
|
int const is_raw_cmd = is_raw_command_request( shell ); |
|
string cmd_local[ 1 ]; |
|
|
|
|
|
static LIST * default_shell; |
|
if ( !default_shell ) |
|
default_shell = list_new( object_new( "cmd.exe /Q/C" ) ); |
|
|
|
|
|
if ( list_empty( shell ) ) |
|
shell = default_shell; |
|
|
|
if ( DEBUG_EXECCMD ) |
|
if ( is_raw_cmd ) |
|
printf( "Executing raw command directly\n" ); |
|
else |
|
{ |
|
printf( "Executing using a command file and the shell: " ); |
|
list_print( shell ); |
|
printf( "\n" ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
if ( is_raw_cmd ) |
|
{ |
|
char const * start = cmd_orig->value; |
|
char const * p = cmd_orig->value + cmd_orig->size; |
|
char const * end = p; |
|
while ( isspace( *start ) ) ++start; |
|
while ( p > start && isspace( p[ -1 ] ) ) |
|
if ( *--p == '\n' ) |
|
end = p; |
|
string_new( cmd_local ); |
|
string_append_range( cmd_local, start, end ); |
|
assert( cmd_local->size == raw_command_length( cmd_orig->value ) ); |
|
} |
|
|
|
|
|
|
|
|
|
else |
|
{ |
|
char const * const cmd_file = prepare_command_file( cmd_orig, slot ); |
|
char const * argv[ MAXARGC + 1 ]; |
|
argv_from_shell( argv, shell, cmd_file, slot ); |
|
string_new_from_argv( cmd_local, argv ); |
|
} |
|
|
|
|
|
if ( !intr_installed ) |
|
{ |
|
intr_installed = 1; |
|
signal( SIGINT, onintr ); |
|
} |
|
|
|
|
|
cmdtab[ slot ].func = func; |
|
cmdtab[ slot ].closure = closure; |
|
|
|
|
|
invoke_cmd( cmd_local->value, slot ); |
|
|
|
|
|
string_free( cmd_local ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void exec_wait() |
|
{ |
|
int i = -1; |
|
int exit_reason; |
|
|
|
|
|
while ( 1 ) |
|
{ |
|
|
|
i = try_wait( 500 ); |
|
|
|
read_output(); |
|
|
|
close_alerts(); |
|
|
|
if ( i >= 0 ) { exit_reason = EXIT_OK; break; } |
|
|
|
i = try_kill_one(); |
|
if ( i >= 0 ) { exit_reason = EXIT_TIMEOUT; break; } |
|
} |
|
|
|
|
|
{ |
|
DWORD exit_code; |
|
timing_info time; |
|
int rstat; |
|
|
|
|
|
record_times( cmdtab[ i ].pi.hProcess, &time ); |
|
|
|
|
|
if ( cmdtab[ i ].command_file->size ) |
|
unlink( cmdtab[ i ].command_file->value ); |
|
|
|
|
|
GetExitCodeProcess( cmdtab[ i ].pi.hProcess, &exit_code ); |
|
|
|
|
|
if ( interrupted() ) |
|
rstat = EXEC_CMD_INTR; |
|
else if ( exit_code ) |
|
rstat = EXEC_CMD_FAIL; |
|
else |
|
rstat = EXEC_CMD_OK; |
|
|
|
|
|
(*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time, |
|
cmdtab[ i ].buffer_out->value, cmdtab[ i ].buffer_err->value, |
|
exit_reason ); |
|
|
|
|
|
|
|
|
|
closeWinHandle( &cmdtab[ i ].pi.hProcess ); |
|
closeWinHandle( &cmdtab[ i ].pi.hThread ); |
|
closeWinHandle( &cmdtab[ i ].pipe_out[ EXECCMD_PIPE_READ ] ); |
|
closeWinHandle( &cmdtab[ i ].pipe_out[ EXECCMD_PIPE_WRITE ] ); |
|
closeWinHandle( &cmdtab[ i ].pipe_err[ EXECCMD_PIPE_READ ] ); |
|
closeWinHandle( &cmdtab[ i ].pipe_err[ EXECCMD_PIPE_WRITE ] ); |
|
string_renew( cmdtab[ i ].buffer_out ); |
|
string_renew( cmdtab[ i ].buffer_err ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void invoke_cmd( char const * const command, int const slot ) |
|
{ |
|
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 }; |
|
|
|
|
|
InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION ); |
|
SetSecurityDescriptorDacl( &sd, TRUE, NULL, FALSE ); |
|
sa.lpSecurityDescriptor = &sd; |
|
sa.bInheritHandle = TRUE; |
|
|
|
|
|
if ( !CreatePipe( &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_READ ], |
|
&cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ], &sa, 0 ) ) |
|
{ |
|
reportWindowsError( "CreatePipe" ); |
|
exit( EXITBAD ); |
|
} |
|
if ( globs.pipe_action && !CreatePipe( &cmdtab[ slot ].pipe_err[ |
|
EXECCMD_PIPE_READ ], &cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_WRITE ], |
|
&sa, 0 ) ) |
|
{ |
|
reportWindowsError( "CreatePipe" ); |
|
exit( EXITBAD ); |
|
} |
|
|
|
|
|
SetHandleInformation( cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_READ ], |
|
HANDLE_FLAG_INHERIT, 0 ); |
|
if ( globs.pipe_action ) |
|
SetHandleInformation( cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_READ ], |
|
HANDLE_FLAG_INHERIT, 0 ); |
|
|
|
|
|
si.dwFlags |= STARTF_USESHOWWINDOW; |
|
si.wShowWindow = SW_HIDE; |
|
|
|
|
|
si.dwFlags |= STARTF_USESTDHANDLES; |
|
si.hStdOutput = cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ]; |
|
si.hStdError = globs.pipe_action |
|
? cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_WRITE ] |
|
: cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ]; |
|
|
|
|
|
si.hStdInput = GetStdHandle( STD_INPUT_HANDLE ); |
|
|
|
|
|
string_new( cmdtab[ slot ].buffer_out ); |
|
string_new( cmdtab[ slot ].buffer_err ); |
|
|
|
if ( DEBUG_EXECCMD ) |
|
printf( "Command string for CreateProcessA(): '%s'\n", command ); |
|
|
|
|
|
if ( !CreateProcessA( |
|
NULL , |
|
(char *)command , |
|
NULL , |
|
NULL , |
|
TRUE , |
|
CREATE_NEW_PROCESS_GROUP, |
|
NULL , |
|
NULL , |
|
&si , |
|
&cmdtab[ slot ].pi ) ) |
|
{ |
|
reportWindowsError( "CreateProcessA" ); |
|
exit( EXITBAD ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int raw_maxline() |
|
{ |
|
OSVERSIONINFO os_info; |
|
os_info.dwOSVersionInfoSize = sizeof( os_info ); |
|
GetVersionEx( &os_info ); |
|
|
|
if ( os_info.dwMajorVersion >= 5 ) return 8191; |
|
if ( os_info.dwMajorVersion == 4 ) return 2047; |
|
return 996; |
|
} |
|
|
|
static int maxline() |
|
{ |
|
static result; |
|
if ( !result ) result = raw_maxline(); |
|
return result; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static void closeWinHandle( HANDLE * const handle ) |
|
{ |
|
if ( *handle ) |
|
{ |
|
CloseHandle( *handle ); |
|
*handle = 0; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static void string_renew( string * const s ) |
|
{ |
|
string_free( s ); |
|
string_new( s ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static long raw_command_length( char const * command ) |
|
{ |
|
char const * p; |
|
char const * escape = 0; |
|
char inquote = 0; |
|
char const * newline = 0; |
|
|
|
|
|
while ( isspace( *command ) ) |
|
++command; |
|
|
|
p = command; |
|
|
|
|
|
do |
|
{ |
|
p += strcspn( p, "\n\"'<>|\\" ); |
|
switch ( *p ) |
|
{ |
|
case '\n': |
|
|
|
|
|
|
|
newline = p; |
|
while ( isspace( *++p ) ); |
|
if ( *p ) return -1; |
|
break; |
|
|
|
case '\\': |
|
escape = escape && escape == p - 1 ? 0 : p; |
|
++p; |
|
break; |
|
|
|
case '"': |
|
case '\'': |
|
if ( escape && escape == p - 1 ) |
|
escape = 0; |
|
else if ( inquote == *p ) |
|
inquote = 0; |
|
else if ( !inquote ) |
|
inquote = *p; |
|
++p; |
|
break; |
|
|
|
case '<': |
|
case '>': |
|
case '|': |
|
if ( !inquote ) |
|
return -1; |
|
++p; |
|
break; |
|
} |
|
} |
|
while ( *p ); |
|
|
|
|
|
return ( newline ? newline : p ) - command; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
#define add_carry_bit( a, b ) ((((a) | (b)) >> 31) & (~((a) + (b)) >> 31) & 0x1) |
|
|
|
|
|
|
|
|
|
#define add_64_hi( h1, l1, h2, l2 ) ((h1) + (h2) + add_carry_bit(l1, l2)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
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 ) |
|
{ |
|
|
|
return add_64( ~t.dwHighDateTime, ~t.dwLowDateTime, 0, 1 ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static double filetime_to_seconds( FILETIME const ft ) |
|
{ |
|
return ft.dwHighDateTime * ( (double)( 1UL << 31 ) * 2.0 * 1.0e-7 ) + |
|
ft.dwLowDateTime * 1.0e-7; |
|
} |
|
|
|
|
|
static void record_times( HANDLE const process, timing_info * const time ) |
|
{ |
|
FILETIME creation; |
|
FILETIME exit; |
|
FILETIME kernel; |
|
FILETIME user; |
|
if ( GetProcessTimes( process, &creation, &exit, &kernel, &user ) ) |
|
{ |
|
time->system = filetime_to_seconds( kernel ); |
|
time->user = filetime_to_seconds( user ); |
|
timestamp_from_filetime( &time->start, &creation ); |
|
timestamp_from_filetime( &time->end, &exit ); |
|
} |
|
} |
|
|
|
|
|
#define IO_BUFFER_SIZE ( 16 * 1024 ) |
|
|
|
static char ioBuffer[ IO_BUFFER_SIZE + 1 ]; |
|
|
|
|
|
static void read_pipe |
|
( |
|
HANDLE in, |
|
string * out |
|
) |
|
{ |
|
DWORD bytesInBuffer = 0; |
|
DWORD bytesAvailable = 0; |
|
|
|
do |
|
{ |
|
|
|
if ( !PeekNamedPipe( in, ioBuffer, IO_BUFFER_SIZE, &bytesInBuffer, |
|
&bytesAvailable, NULL ) ) |
|
bytesAvailable = 0; |
|
|
|
|
|
if ( bytesAvailable > 0 ) |
|
{ |
|
|
|
if ( ReadFile( in, ioBuffer, bytesAvailable <= IO_BUFFER_SIZE ? |
|
bytesAvailable : IO_BUFFER_SIZE, &bytesInBuffer, NULL ) ) |
|
{ |
|
if ( bytesInBuffer > 0 ) |
|
{ |
|
|
|
int i; |
|
for ( i = 0; i < bytesInBuffer; ++i ) |
|
{ |
|
if ( ( (unsigned char)ioBuffer[ i ] < 1 ) ) |
|
ioBuffer[ i ] = '?'; |
|
} |
|
|
|
ioBuffer[ bytesInBuffer ] = '\0'; |
|
|
|
string_append( out, ioBuffer ); |
|
|
|
bytesAvailable -= bytesInBuffer; |
|
} |
|
else |
|
{ |
|
|
|
bytesAvailable = 0; |
|
} |
|
} |
|
else |
|
{ |
|
|
|
bytesAvailable = 0; |
|
} |
|
} |
|
} |
|
while ( bytesAvailable > 0 ); |
|
} |
|
|
|
|
|
static void read_output() |
|
{ |
|
int i; |
|
for ( i = 0; i < globs.jobs; ++i ) |
|
if ( cmdtab[ i ].pi.hProcess ) |
|
{ |
|
|
|
if ( cmdtab[ i ].pipe_out[ EXECCMD_PIPE_READ ] ) |
|
read_pipe( cmdtab[ i ].pipe_out[ EXECCMD_PIPE_READ ], |
|
cmdtab[ i ].buffer_out ); |
|
|
|
if ( cmdtab[ i ].pipe_err[ EXECCMD_PIPE_READ ] ) |
|
read_pipe( cmdtab[ i ].pipe_err[ EXECCMD_PIPE_READ ], |
|
cmdtab[ i ].buffer_err ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int try_wait( int const timeoutMillis ) |
|
{ |
|
int i; |
|
int num_active; |
|
int wait_api_result; |
|
HANDLE active_handles[ MAXJOBS ]; |
|
int active_procs[ MAXJOBS ]; |
|
|
|
|
|
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_api_result = WaitForMultipleObjects( num_active, active_handles, |
|
FALSE, timeoutMillis ); |
|
if ( ( WAIT_OBJECT_0 <= wait_api_result ) && |
|
( wait_api_result < WAIT_OBJECT_0 + num_active ) ) |
|
{ |
|
|
|
return active_procs[ wait_api_result - WAIT_OBJECT_0 ]; |
|
} |
|
|
|
|
|
return -1; |
|
} |
|
|
|
|
|
static int try_kill_one() |
|
{ |
|
|
|
if ( globs.timeout > 0 ) |
|
{ |
|
int i; |
|
for ( i = 0; i < globs.jobs; ++i ) |
|
if ( cmdtab[ i ].pi.hProcess ) |
|
{ |
|
double const t = running_time( cmdtab[ i ].pi.hProcess ); |
|
if ( t > (double)globs.timeout ) |
|
{ |
|
|
|
|
|
|
|
close_alert( &cmdtab[ i ].pi ); |
|
|
|
kill_process_tree( cmdtab[ i ].pi.dwProcessId, |
|
cmdtab[ i ].pi.hProcess ); |
|
|
|
return i; |
|
} |
|
} |
|
} |
|
return -1; |
|
} |
|
|
|
|
|
static void close_alerts() |
|
{ |
|
|
|
|
|
|
|
|
|
if ( ( (float)clock() / (float)( CLOCKS_PER_SEC * 5 ) ) < ( 1.0 / 5.0 ) ) |
|
{ |
|
int i; |
|
for ( i = 0; i < globs.jobs; ++i ) |
|
if ( cmdtab[ i ].pi.hProcess ) |
|
close_alert( &cmdtab[ i ].pi ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static double running_time( HANDLE const process ) |
|
{ |
|
FILETIME creation; |
|
FILETIME exit; |
|
FILETIME kernel; |
|
FILETIME user; |
|
if ( GetProcessTimes( process, &creation, &exit, &kernel, &user ) ) |
|
{ |
|
|
|
FILETIME current; |
|
GetSystemTimeAsFileTime( ¤t ); |
|
return filetime_to_seconds( add_FILETIME( current, |
|
negate_FILETIME( creation ) ) ); |
|
} |
|
return 0.0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void kill_process_tree( DWORD const pid, HANDLE const process ) |
|
{ |
|
HANDLE const 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 ) |
|
{ |
|
|
|
|
|
HANDLE const ph = OpenProcess( PROCESS_ALL_ACCESS, FALSE, |
|
pinfo.th32ProcessID ); |
|
if ( ph ) |
|
{ |
|
kill_process_tree( pinfo.th32ProcessID, ph ); |
|
CloseHandle( ph ); |
|
} |
|
} |
|
} |
|
CloseHandle( process_snapshot_h ); |
|
} |
|
|
|
TerminateProcess( process, -2 ); |
|
} |
|
|
|
|
|
static double creation_time( HANDLE const process ) |
|
{ |
|
FILETIME creation; |
|
FILETIME exit; |
|
FILETIME kernel; |
|
FILETIME user; |
|
return GetProcessTimes( process, &creation, &exit, &kernel, &user ) |
|
? filetime_to_seconds( creation ) |
|
: 0.0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int is_parent_child( DWORD const parent, DWORD const 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 ) |
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
double tchild = 0.0; |
|
double tparent = 0.0; |
|
HANDLE const hchild = OpenProcess( PROCESS_QUERY_INFORMATION, |
|
FALSE, pinfo.th32ProcessID ); |
|
CloseHandle( process_snapshot_h ); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
BOOL CALLBACK close_alert_window_enum( HWND hwnd, LPARAM lParam ) |
|
{ |
|
char buf[ 7 ] = { 0 }; |
|
PROCESS_INFORMATION const * const pi = (PROCESS_INFORMATION *)lParam; |
|
DWORD pid; |
|
DWORD tid; |
|
|
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
!IsWindowVisible( hwnd ) |
|
|
|
|| !GetClassNameA( hwnd, buf, sizeof( buf ) ) |
|
|
|
|| strcmp( buf, "#32770" ) ) |
|
return TRUE; |
|
|
|
|
|
|
|
|
|
tid = GetWindowThreadProcessId( hwnd, &pid ); |
|
if ( !tid || !is_parent_child( pi->dwProcessId, pid ) ) |
|
return TRUE; |
|
|
|
|
|
PostMessageA( hwnd, WM_CLOSE, 0, 0 ); |
|
|
|
|
|
if ( WaitForSingleObject( pi->hProcess, 200 ) == WAIT_TIMEOUT ) |
|
{ |
|
PostThreadMessageA( tid, WM_QUIT, 0, 0 ); |
|
WaitForSingleObject( pi->hProcess, 300 ); |
|
} |
|
|
|
|
|
return FALSE; |
|
} |
|
|
|
|
|
static void close_alert( PROCESS_INFORMATION const * const pi ) |
|
{ |
|
EnumWindows( &close_alert_window_enum, (LPARAM)pi ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static FILE * open_command_file( int const slot ) |
|
{ |
|
string * const command_file = cmdtab[ slot ].command_file; |
|
|
|
|
|
|
|
|
|
|
|
|
|
if ( !command_file->value ) |
|
{ |
|
DWORD const procID = GetCurrentProcessId(); |
|
string const * const tmpdir = path_tmpdir(); |
|
string_new( command_file ); |
|
string_reserve( command_file, tmpdir->size + 64 ); |
|
command_file->size = sprintf( command_file->value, |
|
"%s\\jam%d-%02d-##.bat", tmpdir->value, procID, slot ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
|
char * const index1 = command_file->value + command_file->size - 6; |
|
char * const index2 = index1 + 1; |
|
int waits_remaining; |
|
assert( command_file->value < index1 ); |
|
assert( index2 + 1 < command_file->value + command_file->size ); |
|
assert( index2[ 1 ] == '.' ); |
|
for ( waits_remaining = 3; ; --waits_remaining ) |
|
{ |
|
int index; |
|
for ( index = 0; index != 20; ++index ) |
|
{ |
|
FILE * f; |
|
*index1 = '0' + index / 10; |
|
*index2 = '0' + index % 10; |
|
f = fopen( command_file->value, "w" ); |
|
if ( f ) return f; |
|
} |
|
if ( !waits_remaining ) break; |
|
Sleep( 250 ); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static char const * prepare_command_file( string const * command, int slot ) |
|
{ |
|
FILE * const f = open_command_file( slot ); |
|
if ( !f ) |
|
{ |
|
printf( "failed to write command file!\n" ); |
|
exit( EXITBAD ); |
|
} |
|
fputs( command->value, f ); |
|
fclose( f ); |
|
return cmdtab[ slot ].command_file->value; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static int get_free_cmdtab_slot() |
|
{ |
|
int slot; |
|
for ( slot = 0; slot < MAXJOBS; ++slot ) |
|
if ( !cmdtab[ slot ].pi.hProcess ) |
|
return slot; |
|
printf( "no slots for child!\n" ); |
|
exit( EXITBAD ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static void string_new_from_argv( string * result, char const * const * argv ) |
|
{ |
|
assert( argv ); |
|
assert( argv[ 0 ] ); |
|
string_copy( result, *(argv++) ); |
|
while ( *argv ) |
|
{ |
|
string_push_back( result, ' ' ); |
|
string_append( result, *(argv++) ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static void reportWindowsError( char const * const apiName ) |
|
{ |
|
char * errorMessage; |
|
DWORD const errorCode = GetLastError(); |
|
DWORD apiResult = FormatMessageA( |
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | |
|
FORMAT_MESSAGE_FROM_SYSTEM | |
|
FORMAT_MESSAGE_IGNORE_INSERTS, |
|
NULL, |
|
errorCode, |
|
0, |
|
(LPSTR)&errorMessage, |
|
0, |
|
0 ); |
|
if ( !apiResult ) |
|
printf( "%s() Windows API failed: %d.\n", apiName, errorCode ); |
|
else |
|
{ |
|
printf( "%s() Windows API failed: %d - %s\n", apiName, errorCode, |
|
errorMessage ); |
|
LocalFree( errorMessage ); |
|
} |
|
} |
|
|
|
|
|
#endif |
|
|