mirror of
https://github.com/boostorg/build.git
synced 2026-02-15 00:52:16 +00:00
1213 lines
37 KiB
C
1213 lines
37 KiB
C
/*
|
|
* 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>
|
|
|
|
# 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:
|
|
* execcmd() - launch an async command execution
|
|
* execwait() - 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
|
|
{
|
|
/* buffer to hold action */
|
|
string action;
|
|
/* buffer to hold target */
|
|
string target;
|
|
/* buffer to hold command being invoked */
|
|
string command;
|
|
/* the temporary batch file of the action, when needed */
|
|
char *tempfile_bat;
|
|
/* the pipes for the child process, parent reads from (0),
|
|
child writes to (1) */
|
|
HANDLE pipe_out[2];
|
|
HANDLE pipe_err[2];
|
|
/* buffers to hold stdout and stderr, if any */
|
|
string buffer_out;
|
|
string buffer_err;
|
|
/* running process info */
|
|
PROCESS_INFORMATION pi;
|
|
/* when comand completes, the result value */
|
|
DWORD exitcode;
|
|
/* function called when the command completes */
|
|
void (*func)( void *closure, int status, timing_info*, char *, char * );
|
|
void *closure;
|
|
/* when command completes, the reason it completed */
|
|
int exit_reason;
|
|
} 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
|
|
}
|
|
|
|
/* execcmd() - launch an async command execution */
|
|
void execcmd(
|
|
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 )
|
|
{
|
|
const char *tempdir = path_tmpdir();
|
|
DWORD procID = GetCurrentProcessId();
|
|
|
|
/* SVA - allocate 64 other 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( 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
|
|
fails 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 fix 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];
|
|
}
|
|
|
|
/* Save the operation for execwait() 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 comman we run */
|
|
{
|
|
char ** argp = argv;
|
|
string_new(&cmd);
|
|
string_copy(&cmd,*(argp++));
|
|
while( *argp != 0 )
|
|
{
|
|
string_push_back(&cmd,' ');
|
|
string_append(&cmd,*(argp++));
|
|
}
|
|
}
|
|
|
|
/* create the 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 /* the child process info, if created */
|
|
)
|
|
)
|
|
{
|
|
perror( "CreateProcess" );
|
|
exit( EXITBAD );
|
|
}
|
|
|
|
/* clean up temporary stuff */
|
|
string_free(&cmd);
|
|
}
|
|
|
|
/* Wait until we're under the limit of concurrent commands. */
|
|
/* Don't trust globs.jobs alone. */
|
|
|
|
while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs )
|
|
if( !execwait() )
|
|
break;
|
|
|
|
if (argv != argv_static)
|
|
{
|
|
free_argv(argv);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* execwait()
|
|
- wait and drive at most one execution completion
|
|
* waits for one command to complete, while processing the io
|
|
for all ongoing commands.
|
|
*/
|
|
int execwait()
|
|
{
|
|
int i = -1;
|
|
|
|
/* Handle naive make1() which doesn't know if cmds are running. */
|
|
|
|
if( !cmdsrunning )
|
|
return 0;
|
|
|
|
/* wait for a command to complete, while snarfing up any output */
|
|
do
|
|
{
|
|
/* read in the output of all running commands */
|
|
read_output();
|
|
/* close out pending debug style dialogs */
|
|
close_alerts();
|
|
/* check for a complete command, briefly */
|
|
if ( i < 0 ) i = try_wait(500);
|
|
/* 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;
|
|
}
|
|
|
|
/* the dispossition of the command */
|
|
if( intr )
|
|
rstat = EXEC_CMD_INTR;
|
|
else if( cmdtab[i].exitcode != 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].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].exitcode = 0;
|
|
cmdtab[i].exit_reason = EXIT_OK;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
|
|
static void free_argv( char** args )
|
|
{
|
|
BJAM_FREE( args[0] );
|
|
BJAM_FREE( args );
|
|
}
|
|
|
|
int maxline()
|
|
{
|
|
OSVERSIONINFO os_info;
|
|
os_info.dwOSVersionInfoSize = sizeof(os_info);
|
|
GetVersionEx(&os_info);
|
|
|
|
return (os_info.dwMajorVersion == 3)
|
|
? 996 /* NT 3.5.1 */
|
|
: 2047 /* NT >= 4.x */
|
|
;
|
|
}
|
|
|
|
/* 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's 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( const char* 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
|
|
* don't need to worry about preserving escaped quotes here.
|
|
* Spaces can't 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 + t.dwLowDateTime * 1.0e-7;
|
|
}
|
|
|
|
static void record_times(HANDLE process, timing_info* time)
|
|
{
|
|
FILETIME creation, exit, kernel, user;
|
|
|
|
if (GetProcessTimes(process, &creation, &exit, &kernel, &user))
|
|
{
|
|
/* Compute the elapsed time */
|
|
#if 0 /* We don't know how to get this number on Unix */
|
|
time->elapsed = filetime_seconds(
|
|
add_FILETIME( exit, negate_FILETIME(creation) )
|
|
);
|
|
#endif
|
|
time->system = filetime_seconds(kernel);
|
|
time->user = filetime_seconds(user);
|
|
}
|
|
|
|
/* CloseHandle((HANDLE)pid); */
|
|
}
|
|
|
|
#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 non-ascii chars */
|
|
int i;
|
|
for ( i = 0; i < bytesInBuffer; ++i )
|
|
{
|
|
if ((unsigned char)ioBuffer[i] < 1 ||
|
|
(unsigned char)ioBuffer[i] > 127 )
|
|
{
|
|
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 is first. returns the index of the completed
|
|
command, or -1. */
|
|
static int try_wait(int timeoutMillis)
|
|
{
|
|
int i, num_active, waiting;
|
|
HANDLE active_handles[MAXJOBS];
|
|
int active_procs[MAXJOBS];
|
|
|
|
for ( waiting = 1; waiting; )
|
|
{
|
|
/* find the first completed child process */
|
|
for ( num_active = 0, i = 0; i < globs.jobs; ++i )
|
|
{
|
|
/* if we have an already dead process, return it. */
|
|
cmdtab[i].exitcode = 0;
|
|
if ( GetExitCodeProcess( cmdtab[i].pi.hProcess, &cmdtab[i].exitcode ) )
|
|
{
|
|
if ( STILL_ACTIVE != cmdtab[i].exitcode )
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
/* it's running, add it to the list to watch for */
|
|
active_handles[num_active] = cmdtab[i].pi.hProcess;
|
|
active_procs[num_active] = i;
|
|
num_active += 1;
|
|
}
|
|
|
|
/* wait for a child to complete, or for our timeout window to expire */
|
|
if ( waiting )
|
|
{
|
|
WaitForMultipleObjects( num_active, active_handles, FALSE, timeoutMillis );
|
|
waiting = 0;
|
|
}
|
|
}
|
|
|
|
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 as complete, with the failure code */
|
|
GetExitCodeProcess( cmdtab[i].pi.hProcess, &cmdtab[i].exitcode );
|
|
/* mark it 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's
|
|
not a cheap operation, and we'll 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, exit, kernel, user, current;
|
|
if (GetProcessTimes(process, &creation, &exit, &kernel, &user))
|
|
{
|
|
/* Compute the elapsed time */
|
|
GetSystemTimeAsFileTime(¤t);
|
|
{
|
|
double delta = filetime_seconds(
|
|
add_FILETIME( current, negate_FILETIME(creation) )
|
|
);
|
|
return delta;
|
|
}
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
/* it's 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;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* not really optimal, or efficient, but it's easier this way, and it's 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);
|
|
TRUE == ok;
|
|
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, exit, kernel, user, current;
|
|
if (GetProcessTimes(process, &creation, &exit, &kernel, &user))
|
|
{
|
|
return filetime_seconds(creation);
|
|
}
|
|
return 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 (ie. return 0) for system
|
|
processes due to insufficient privileges, and that's 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 smss.exe process, which in turn is directly child of
|
|
System process, which always has process id == 4 .
|
|
This check must be performed before comparison of process
|
|
creation time */
|
|
if (stricmp(pinfo.szExeFile, "csrss.exe") == 0
|
|
&& is_parent_child(parent, pinfo.th32ParentProcessID) == 2)
|
|
{
|
|
return 1;
|
|
}
|
|
else if (stricmp(pinfo.szExeFile, "smss.exe") == 0
|
|
&& pinfo.th32ParentProcessID == 4)
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
if (hchild != 0)
|
|
{
|
|
HANDLE hparent = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ParentProcessID);
|
|
if (hparent != 0)
|
|
{
|
|
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's not a dialog */
|
|
|
|
if (strcmp(buf, "#32770") != 0)
|
|
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 cannot 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 */
|