summaryrefslogtreecommitdiff
path: root/execnt.c
diff options
context:
space:
mode:
Diffstat (limited to 'execnt.c')
-rw-r--r--execnt.c1296
1 files changed, 1296 insertions, 0 deletions
diff --git a/execnt.c b/execnt.c
new file mode 100644
index 0000000..7642045
--- /dev/null
+++ b/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( &current );
+ return filetime_seconds( add_FILETIME( current,
+ negate_FILETIME( creation ) ) );
+ }
+ return 0.0;
+}
+
+
+/* It is just stupidly silly that one has to do this. */
+typedef struct PROCESS_BASIC_INFORMATION__
+{
+ LONG ExitStatus;
+ PVOID PebBaseAddress;
+ ULONG AffinityMask;
+ LONG BasePriority;
+ ULONG UniqueProcessId;
+ ULONG InheritedFromUniqueProcessId;
+} PROCESS_BASIC_INFORMATION_;
+typedef LONG (__stdcall * NtQueryInformationProcess__)(
+ HANDLE ProcessHandle,
+ LONG ProcessInformationClass,
+ PVOID ProcessInformation,
+ ULONG ProcessInformationLength,
+ PULONG ReturnLength);
+static NtQueryInformationProcess__ NtQueryInformationProcess_ = NULL;
+static HMODULE NTDLL_ = NULL;
+DWORD get_process_id( HANDLE process )
+{
+ PROCESS_BASIC_INFORMATION_ pinfo;
+ if ( !NtQueryInformationProcess_ )
+ {
+ if ( ! NTDLL_ )
+ NTDLL_ = GetModuleHandleA( "ntdll" );
+ if ( NTDLL_ )
+ NtQueryInformationProcess_
+ = (NtQueryInformationProcess__)GetProcAddress( NTDLL_, "NtQueryInformationProcess" );
+ }
+ if ( NtQueryInformationProcess_ )
+ {
+ LONG r = (*NtQueryInformationProcess_)( process,
+ /* ProcessBasicInformation == */ 0, &pinfo,
+ sizeof( PROCESS_BASIC_INFORMATION_ ), NULL );
+ return pinfo.UniqueProcessId;
+ }
+ return 0;
+}
+
+
+/*
+ * Not really optimal, or efficient, but it is easier this way, and it is not
+ * like we are going to be killing thousands, or even tens of processes.
+ */
+
+static void kill_process_tree( DWORD pid, HANDLE process )
+{
+ HANDLE process_snapshot_h = INVALID_HANDLE_VALUE;
+ if ( !pid )
+ pid = get_process_id( process );
+ process_snapshot_h = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
+
+ if ( INVALID_HANDLE_VALUE != process_snapshot_h )
+ {
+ BOOL ok = TRUE;
+ PROCESSENTRY32 pinfo;
+ pinfo.dwSize = sizeof( PROCESSENTRY32 );
+ for (
+ ok = Process32First( process_snapshot_h, &pinfo );
+ ok == TRUE;
+ ok = Process32Next( process_snapshot_h, &pinfo ) )
+ {
+ if ( pinfo.th32ParentProcessID == pid )
+ {
+ /* Found a child, recurse to kill it and anything else below it.
+ */
+ HANDLE ph = OpenProcess( PROCESS_ALL_ACCESS, FALSE,
+ pinfo.th32ProcessID );
+ if ( NULL != ph )
+ {
+ kill_process_tree( pinfo.th32ProcessID, ph );
+ CloseHandle( ph );
+ }
+ }
+ }
+ CloseHandle( process_snapshot_h );
+ }
+ /* Now that the children are all dead, kill the root. */
+ TerminateProcess( process, -2 );
+}
+
+
+static double creation_time( HANDLE process )
+{
+ FILETIME creation;
+ FILETIME exit;
+ FILETIME kernel;
+ FILETIME user;
+ FILETIME current;
+ return GetProcessTimes( process, &creation, &exit, &kernel, &user )
+ ? filetime_seconds( creation )
+ : 0.0;
+}
+
+
+/*
+ * Recursive check if first process is parent (directly or indirectly) of the
+ * second one. Both processes are passed as process ids, not handles. Special
+ * return value 2 means that the second process is smss.exe and its parent
+ * process is System (first argument is ignored).
+ */
+
+static int is_parent_child( DWORD parent, DWORD child )
+{
+ HANDLE process_snapshot_h = INVALID_HANDLE_VALUE;
+
+ if ( !child )
+ return 0;
+ if ( parent == child )
+ return 1;
+
+ process_snapshot_h = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
+ if ( INVALID_HANDLE_VALUE != process_snapshot_h )
+ {
+ BOOL ok = TRUE;
+ PROCESSENTRY32 pinfo;
+ pinfo.dwSize = sizeof( PROCESSENTRY32 );
+ for (
+ ok = Process32First( process_snapshot_h, &pinfo );
+ ok == TRUE;
+ ok = Process32Next( process_snapshot_h, &pinfo ) )
+ {
+ if ( pinfo.th32ProcessID == child )
+ {
+ /* Unfortunately, process ids are not really unique. There might
+ * be spurious "parent and child" relationship match between two
+ * non-related processes if real parent process of a given
+ * process has exited (while child process kept running as an
+ * "orphan") and the process id of such parent process has been
+ * reused by internals of the operating system when creating
+ * another process.
+ *
+ * Thus additional check is needed - process creation time. This
+ * check may fail (i.e. return 0) for system processes due to
+ * insufficient privileges, and that is OK.
+ */
+ double tchild = 0.0;
+ double tparent = 0.0;
+ HANDLE hchild = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ProcessID );
+ CloseHandle( process_snapshot_h );
+
+ /* csrss.exe may display message box like following:
+ * xyz.exe - Unable To Locate Component
+ * This application has failed to start because
+ * boost_foo-bar.dll was not found. Re-installing the
+ * application may fix the problem
+ * This actually happens when starting test process that depends
+ * on a dynamic library which failed to build. We want to
+ * automatically close these message boxes even though csrss.exe
+ * is not our child process. We may depend on the fact that (in
+ * all current versions of Windows) csrss.exe is directly child
+ * of the smss.exe process, which in turn is directly child of
+ * the System process, which always has process id == 4. This
+ * check must be performed before comparison of process creation
+ * times.
+ */
+ if ( !stricmp( pinfo.szExeFile, "csrss.exe" ) &&
+ ( is_parent_child( parent, pinfo.th32ParentProcessID ) == 2 ) )
+ return 1;
+ if ( !stricmp( pinfo.szExeFile, "smss.exe" ) &&
+ ( pinfo.th32ParentProcessID == 4 ) )
+ return 2;
+
+ if ( hchild )
+ {
+ HANDLE hparent = OpenProcess( PROCESS_QUERY_INFORMATION,
+ FALSE, pinfo.th32ParentProcessID );
+ if ( hparent )
+ {
+ tchild = creation_time( hchild );
+ tparent = creation_time( hparent );
+ CloseHandle( hparent );
+ }
+ CloseHandle( hchild );
+ }
+
+ /* Return 0 if one of the following is true:
+ * 1. we failed to read process creation time
+ * 2. child was created before alleged parent
+ */
+ if ( ( tchild == 0.0 ) || ( tparent == 0.0 ) ||
+ ( tchild < tparent ) )
+ return 0;
+
+ return is_parent_child( parent, pinfo.th32ParentProcessID ) & 1;
+ }
+ }
+
+ CloseHandle( process_snapshot_h );
+ }
+
+ return 0;
+}
+
+typedef struct PROCESS_HANDLE_ID { HANDLE h; DWORD pid; } PROCESS_HANDLE_ID;
+
+
+/*
+ * This function is called by the operating system for each topmost window.
+ */
+
+BOOL CALLBACK close_alert_window_enum( HWND hwnd, LPARAM lParam )
+{
+ char buf[ 7 ] = { 0 };
+ PROCESS_HANDLE_ID p = *( (PROCESS_HANDLE_ID *)lParam );
+ DWORD pid = 0;
+ DWORD tid = 0;
+
+ /* We want to find and close any window that:
+ * 1. is visible and
+ * 2. is a dialog and
+ * 3. is displayed by any of our child processes
+ */
+ if ( !IsWindowVisible( hwnd ) )
+ return TRUE;
+
+ if ( !GetClassNameA( hwnd, buf, sizeof( buf ) ) )
+ return TRUE; /* Failed to read class name; presume it is not a dialog. */
+
+ if ( strcmp( buf, "#32770" ) )
+ return TRUE; /* Not a dialog */
+
+ /* GetWindowThreadProcessId() returns 0 on error, otherwise thread id of
+ * window message pump thread.
+ */
+ tid = GetWindowThreadProcessId( hwnd, &pid );
+
+ if ( tid && is_parent_child( p.pid, pid ) )
+ {
+ /* Ask really nice. */
+ PostMessageA( hwnd, WM_CLOSE, 0, 0 );
+ /* Now wait and see if it worked. If not, insist. */
+ if ( WaitForSingleObject( p.h, 200 ) == WAIT_TIMEOUT )
+ {
+ PostThreadMessageA( tid, WM_QUIT, 0, 0 );
+ WaitForSingleObject( p.h, 300 );
+ }
+
+ /* Done, we do not want to check any other window now. */
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static void close_alert( HANDLE process )
+{
+ DWORD pid = get_process_id( process );
+ /* If process already exited or we just can not get its process id, do not
+ * go any further.
+ */
+ if ( pid )
+ {
+ PROCESS_HANDLE_ID p;
+ p.h = process;
+ p.pid = pid;
+ EnumWindows( &close_alert_window_enum, (LPARAM)&p );
+ }
+}
+
+#endif /* USE_EXECNT */