summaryrefslogtreecommitdiff
path: root/jam-files/engine/execnt.c
diff options
context:
space:
mode:
authorChris Dyer <cdyer@cab.ark.cs.cmu.edu>2012-10-02 00:19:43 -0400
committerChris Dyer <cdyer@cab.ark.cs.cmu.edu>2012-10-02 00:19:43 -0400
commit925087356b853e2099c1b60d8b757d7aa02121a9 (patch)
tree579925c5c9d3da51f43018a5c6d1c4dfbb72b089 /jam-files/engine/execnt.c
parentea79e535d69f6854d01c62e3752971fb6730d8e7 (diff)
cdec cleanup, remove bayesian stuff, parsing stuff
Diffstat (limited to 'jam-files/engine/execnt.c')
-rw-r--r--jam-files/engine/execnt.c1296
1 files changed, 0 insertions, 1296 deletions
diff --git a/jam-files/engine/execnt.c b/jam-files/engine/execnt.c
deleted file mode 100644
index 76420451..00000000
--- a/jam-files/engine/execnt.c
+++ /dev/null
@@ -1,1296 +0,0 @@
-/*
- * 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 */