📄 execnt.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># 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: * 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( *(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 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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -