]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | /* |
2 | * Copyright 1993, 1995 Christopher Seiwald. | |
3 | * | |
4 | * This file is part of Jam - see jam.c for Copyright information. | |
5 | */ | |
6 | ||
7 | /* This file is ALSO: | |
8 | * Copyright 2001-2004 David Abrahams. | |
9 | * Copyright 2007 Rene Rivera. | |
10 | * Distributed under the Boost Software License, Version 1.0. | |
11 | * (See accompanying file LICENSE_1_0.txt or copy at | |
12 | * http://www.boost.org/LICENSE_1_0.txt) | |
13 | */ | |
14 | ||
15 | /* | |
16 | * execnt.c - execute a shell command on Windows NT | |
17 | * | |
18 | * If $(JAMSHELL) is defined, uses that to formulate the actual command. The | |
19 | * default is: cmd.exe /Q/C | |
20 | * | |
21 | * In $(JAMSHELL), % expands to the command string and ! expands to the slot | |
22 | * number (starting at 1) for multiprocess (-j) invocations. If $(JAMSHELL) does | |
23 | * not include a %, it is tacked on as the last argument. | |
24 | * | |
25 | * Each $(JAMSHELL) placeholder must be specified as a separate individual | |
26 | * element in a jam variable value. | |
27 | * | |
28 | * Do not just set JAMSHELL to cmd.exe - it will not work! | |
29 | * | |
30 | * External routines: | |
31 | * exec_check() - preprocess and validate the command | |
32 | * exec_cmd() - launch an async command execution | |
33 | * exec_wait() - wait for any of the async command processes to terminate | |
34 | * | |
35 | * Internal routines: | |
36 | * filetime_to_seconds() - Windows FILETIME --> number of seconds conversion | |
37 | */ | |
38 | ||
39 | #include "jam.h" | |
40 | #include "output.h" | |
41 | #ifdef USE_EXECNT | |
42 | #include "execcmd.h" | |
43 | ||
44 | #include "lists.h" | |
45 | #include "output.h" | |
46 | #include "pathsys.h" | |
47 | #include "string.h" | |
48 | ||
49 | #include <assert.h> | |
50 | #include <ctype.h> | |
51 | #include <errno.h> | |
52 | #include <time.h> | |
53 | ||
54 | #define WIN32_LEAN_AND_MEAN | |
55 | #include <windows.h> | |
56 | #include <process.h> | |
57 | #include <tlhelp32.h> | |
58 | ||
59 | ||
60 | /* get the maximum shell command line length according to the OS */ | |
61 | static int maxline(); | |
62 | /* valid raw command string length */ | |
63 | static long raw_command_length( char const * command ); | |
64 | /* add two 64-bit unsigned numbers, h1l1 and h2l2 */ | |
65 | static FILETIME add_64( | |
66 | unsigned long h1, unsigned long l1, | |
67 | unsigned long h2, unsigned long l2 ); | |
68 | /* */ | |
69 | static FILETIME add_FILETIME( FILETIME t1, FILETIME t2 ); | |
70 | /* */ | |
71 | static FILETIME negate_FILETIME( FILETIME t ); | |
72 | /* record the timing info for the process */ | |
73 | static void record_times( HANDLE const, timing_info * const ); | |
74 | /* calc the current running time of an *active* process */ | |
75 | static double running_time( HANDLE const ); | |
76 | /* terminate the given process, after terminating all its children first */ | |
77 | static void kill_process_tree( DWORD const procesdId, HANDLE const ); | |
78 | /* waits for a command to complete or time out */ | |
79 | static int try_wait( int const timeoutMillis ); | |
80 | /* reads any pending output for running commands */ | |
81 | static void read_output(); | |
82 | /* checks if a command ran out of time, and kills it */ | |
83 | static int try_kill_one(); | |
84 | /* is the first process a parent (direct or indirect) to the second one */ | |
85 | static int is_parent_child( DWORD const parent, DWORD const child ); | |
86 | /* */ | |
87 | static void close_alert( PROCESS_INFORMATION const * const ); | |
88 | /* close any alerts hanging around */ | |
89 | static void close_alerts(); | |
90 | /* prepare a command file to be executed using an external shell */ | |
91 | static char const * prepare_command_file( string const * command, int slot ); | |
92 | /* invoke the actual external process using the given command line */ | |
93 | static void invoke_cmd( char const * const command, int const slot ); | |
94 | /* find a free slot in the running commands table */ | |
95 | static int get_free_cmdtab_slot(); | |
96 | /* put together the final command string we are to run */ | |
97 | static void string_new_from_argv( string * result, char const * const * argv ); | |
98 | /* frees and renews the given string */ | |
99 | static void string_renew( string * const ); | |
100 | /* reports the last failed Windows API related error message */ | |
101 | static void reportWindowsError( char const * const apiName, int slot ); | |
102 | /* closes a Windows HANDLE and resets its variable to 0. */ | |
103 | static void closeWinHandle( HANDLE * const handle ); | |
b32b8144 FG |
104 | /* Adds the job index to the list of currently active jobs. */ |
105 | static void register_wait( int job_id ); | |
7c673cae FG |
106 | |
107 | /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ | |
108 | ||
109 | /* CreateProcessA() Windows API places a limit of 32768 characters (bytes) on | |
110 | * the allowed command-line length, including a trailing Unicode (2-byte) | |
111 | * nul-terminator character. | |
112 | */ | |
113 | #define MAX_RAW_COMMAND_LENGTH 32766 | |
114 | ||
115 | /* We hold handles for pipes used to communicate with child processes in two | |
116 | * element arrays indexed as follows. | |
117 | */ | |
118 | #define EXECCMD_PIPE_READ 0 | |
119 | #define EXECCMD_PIPE_WRITE 1 | |
120 | ||
121 | static int intr_installed; | |
122 | ||
123 | ||
124 | /* The list of commands we run. */ | |
b32b8144 | 125 | static struct _cmdtab_t |
7c673cae FG |
126 | { |
127 | /* Temporary command file used to execute the action when needed. */ | |
128 | string command_file[ 1 ]; | |
129 | ||
130 | /* Pipes for communicating with the child process. Parent reads from (0), | |
131 | * child writes to (1). | |
132 | */ | |
133 | HANDLE pipe_out[ 2 ]; | |
134 | HANDLE pipe_err[ 2 ]; | |
135 | ||
136 | string buffer_out[ 1 ]; /* buffer to hold stdout, if any */ | |
137 | string buffer_err[ 1 ]; /* buffer to hold stderr, if any */ | |
138 | ||
139 | PROCESS_INFORMATION pi; /* running process information */ | |
140 | ||
b32b8144 FG |
141 | HANDLE wait_handle; |
142 | ||
11fdf7f2 TL |
143 | int flags; |
144 | ||
7c673cae FG |
145 | /* Function called when the command completes. */ |
146 | ExecCmdCallback func; | |
147 | ||
148 | /* Opaque data passed back to the 'func' callback. */ | |
149 | void * closure; | |
b32b8144 FG |
150 | } * cmdtab = NULL; |
151 | static int cmdtab_size = 0; | |
7c673cae | 152 | |
b32b8144 FG |
153 | /* A thread-safe single element queue. Used by the worker threads |
154 | * to signal the main thread that a process is completed. | |
155 | */ | |
156 | struct | |
157 | { | |
158 | int job_index; | |
159 | HANDLE read_okay; | |
160 | HANDLE write_okay; | |
161 | } process_queue; | |
7c673cae FG |
162 | |
163 | /* | |
164 | * Execution unit tests. | |
165 | */ | |
166 | ||
167 | void execnt_unit_test() | |
168 | { | |
169 | #if !defined( NDEBUG ) | |
170 | /* vc6 preprocessor is broken, so assert with these strings gets confused. | |
171 | * Use a table instead. | |
172 | */ | |
173 | { | |
174 | typedef struct test { char * command; int result; } test; | |
175 | test tests[] = { | |
176 | { "", 0 }, | |
177 | { " ", 0 }, | |
178 | { "x", 1 }, | |
179 | { "\nx", 1 }, | |
180 | { "x\n", 1 }, | |
181 | { "\nx\n", 1 }, | |
182 | { "\nx \n", 2 }, | |
183 | { "\nx \n ", 2 }, | |
184 | { " \n\t\t\v\r\r\n \t x \v \t\t\r\n\n\n \n\n\v\t", 8 }, | |
185 | { "x\ny", -1 }, | |
186 | { "x\n\n y", -1 }, | |
187 | { "echo x > foo.bar", -1 }, | |
188 | { "echo x < foo.bar", -1 }, | |
189 | { "echo x | foo.bar", -1 }, | |
190 | { "echo x \">\" foo.bar", 18 }, | |
191 | { "echo x '<' foo.bar", 18 }, | |
192 | { "echo x \"|\" foo.bar", 18 }, | |
193 | { "echo x \\\">\\\" foo.bar", -1 }, | |
194 | { "echo x \\\"<\\\" foo.bar", -1 }, | |
195 | { "echo x \\\"|\\\" foo.bar", -1 }, | |
196 | { "\"echo x > foo.bar\"", 18 }, | |
197 | { "echo x \"'\"<' foo.bar", -1 }, | |
198 | { "echo x \\\\\"<\\\\\" foo.bar", 22 }, | |
199 | { "echo x \\x\\\"<\\\\\" foo.bar", -1 }, | |
200 | { 0 } }; | |
201 | test const * t; | |
202 | for ( t = tests; t->command; ++t ) | |
203 | assert( raw_command_length( t->command ) == t->result ); | |
204 | } | |
205 | ||
206 | { | |
207 | int const length = maxline() + 9; | |
208 | char * const cmd = (char *)BJAM_MALLOC_ATOMIC( length + 1 ); | |
209 | memset( cmd, 'x', length ); | |
210 | cmd[ length ] = 0; | |
211 | assert( raw_command_length( cmd ) == length ); | |
212 | BJAM_FREE( cmd ); | |
213 | } | |
214 | #endif | |
215 | } | |
216 | ||
b32b8144 FG |
217 | /* |
218 | * exec_init() - global initialization | |
219 | */ | |
220 | void exec_init( void ) | |
221 | { | |
222 | if ( globs.jobs > cmdtab_size ) | |
223 | { | |
224 | cmdtab = BJAM_REALLOC( cmdtab, globs.jobs * sizeof( *cmdtab ) ); | |
225 | memset( cmdtab + cmdtab_size, 0, ( globs.jobs - cmdtab_size ) * sizeof( *cmdtab ) ); | |
226 | cmdtab_size = globs.jobs; | |
227 | } | |
228 | if ( globs.jobs > MAXIMUM_WAIT_OBJECTS && !process_queue.read_okay ) | |
229 | { | |
230 | process_queue.read_okay = CreateEvent( NULL, FALSE, FALSE, NULL ); | |
231 | process_queue.write_okay = CreateEvent( NULL, FALSE, TRUE, NULL ); | |
232 | } | |
233 | } | |
234 | ||
235 | /* | |
236 | * exec_done - free resources. | |
237 | */ | |
238 | void exec_done( void ) | |
239 | { | |
240 | if ( process_queue.read_okay ) | |
241 | { | |
242 | CloseHandle( process_queue.read_okay ); | |
243 | } | |
244 | if ( process_queue.write_okay ) | |
245 | { | |
246 | CloseHandle( process_queue.write_okay ); | |
247 | } | |
248 | BJAM_FREE( cmdtab ); | |
249 | } | |
7c673cae FG |
250 | |
251 | /* | |
252 | * exec_check() - preprocess and validate the command | |
253 | */ | |
254 | ||
255 | int exec_check | |
256 | ( | |
257 | string const * command, | |
258 | LIST * * pShell, | |
259 | int * error_length, | |
260 | int * error_max_length | |
261 | ) | |
262 | { | |
263 | /* Default shell does nothing when triggered with an empty or a | |
264 | * whitespace-only command so we simply skip running it in that case. We | |
265 | * still pass them on to non-default shells as we do not really know what | |
266 | * they are going to do with such commands. | |
267 | */ | |
268 | if ( list_empty( *pShell ) ) | |
269 | { | |
270 | char const * s = command->value; | |
271 | while ( isspace( *s ) ) ++s; | |
272 | if ( !*s ) | |
273 | return EXEC_CHECK_NOOP; | |
274 | } | |
275 | ||
276 | /* Check prerequisites for executing raw commands. */ | |
277 | if ( is_raw_command_request( *pShell ) ) | |
278 | { | |
279 | int const raw_cmd_length = raw_command_length( command->value ); | |
280 | if ( raw_cmd_length < 0 ) | |
281 | { | |
282 | /* Invalid characters detected - fallback to default shell. */ | |
283 | list_free( *pShell ); | |
284 | *pShell = L0; | |
285 | } | |
286 | else if ( raw_cmd_length > MAX_RAW_COMMAND_LENGTH ) | |
287 | { | |
288 | *error_length = raw_cmd_length; | |
289 | *error_max_length = MAX_RAW_COMMAND_LENGTH; | |
290 | return EXEC_CHECK_TOO_LONG; | |
291 | } | |
292 | else | |
293 | return raw_cmd_length ? EXEC_CHECK_OK : EXEC_CHECK_NOOP; | |
294 | } | |
295 | ||
296 | /* Now we know we are using an external shell. Note that there is no need to | |
297 | * check for too long command strings when using an external shell since we | |
298 | * use a command file and assume no one is going to set up a JAMSHELL format | |
299 | * string longer than a few hundred bytes at most which should be well under | |
300 | * the total command string limit. Should someone actually construct such a | |
301 | * JAMSHELL value it will get reported as an 'invalid parameter' | |
302 | * CreateProcessA() Windows API failure which seems like a good enough | |
303 | * result for such intentional mischief. | |
304 | */ | |
305 | ||
306 | /* Check for too long command lines. */ | |
307 | return check_cmd_for_too_long_lines( command->value, maxline(), | |
308 | error_length, error_max_length ); | |
309 | } | |
310 | ||
311 | ||
312 | /* | |
313 | * exec_cmd() - launch an async command execution | |
314 | * | |
315 | * We assume exec_check() already verified that the given command can have its | |
316 | * command string constructed as requested. | |
317 | */ | |
318 | ||
319 | void exec_cmd | |
320 | ( | |
321 | string const * cmd_orig, | |
11fdf7f2 | 322 | int flags, |
7c673cae FG |
323 | ExecCmdCallback func, |
324 | void * closure, | |
325 | LIST * shell | |
326 | ) | |
327 | { | |
328 | int const slot = get_free_cmdtab_slot(); | |
329 | int const is_raw_cmd = is_raw_command_request( shell ); | |
330 | string cmd_local[ 1 ]; | |
331 | ||
332 | /* Initialize default shell - anything more than /Q/C is non-portable. */ | |
333 | static LIST * default_shell; | |
334 | if ( !default_shell ) | |
335 | default_shell = list_new( object_new( "cmd.exe /Q/C" ) ); | |
336 | ||
337 | /* Specifying no shell means requesting the default shell. */ | |
338 | if ( list_empty( shell ) ) | |
339 | shell = default_shell; | |
340 | ||
341 | if ( DEBUG_EXECCMD ) | |
342 | if ( is_raw_cmd ) | |
343 | out_printf( "Executing raw command directly\n" ); | |
344 | else | |
345 | { | |
346 | out_printf( "Executing using a command file and the shell: " ); | |
347 | list_print( shell ); | |
348 | out_printf( "\n" ); | |
349 | } | |
350 | ||
351 | /* If we are running a raw command directly - trim its leading whitespaces | |
352 | * as well as any trailing all-whitespace lines but keep any trailing | |
353 | * whitespace in the final/only line containing something other than | |
354 | * whitespace). | |
355 | */ | |
356 | if ( is_raw_cmd ) | |
357 | { | |
358 | char const * start = cmd_orig->value; | |
359 | char const * p = cmd_orig->value + cmd_orig->size; | |
360 | char const * end = p; | |
361 | while ( isspace( *start ) ) ++start; | |
362 | while ( p > start && isspace( p[ -1 ] ) ) | |
363 | if ( *--p == '\n' ) | |
364 | end = p; | |
365 | string_new( cmd_local ); | |
366 | string_append_range( cmd_local, start, end ); | |
367 | assert( cmd_local->size == raw_command_length( cmd_orig->value ) ); | |
368 | } | |
369 | /* If we are not running a raw command directly, prepare a command file to | |
370 | * be executed using an external shell and the actual command string using | |
371 | * that command file. | |
372 | */ | |
373 | else | |
374 | { | |
375 | char const * const cmd_file = prepare_command_file( cmd_orig, slot ); | |
376 | char const * argv[ MAXARGC + 1 ]; /* +1 for NULL */ | |
377 | argv_from_shell( argv, shell, cmd_file, slot ); | |
378 | string_new_from_argv( cmd_local, argv ); | |
379 | } | |
380 | ||
381 | /* Catch interrupts whenever commands are running. */ | |
382 | if ( !intr_installed ) | |
383 | { | |
384 | intr_installed = 1; | |
385 | signal( SIGINT, onintr ); | |
386 | } | |
387 | ||
11fdf7f2 TL |
388 | cmdtab[ slot ].flags = flags; |
389 | ||
7c673cae FG |
390 | /* Save input data into the selected running commands table slot. */ |
391 | cmdtab[ slot ].func = func; | |
392 | cmdtab[ slot ].closure = closure; | |
393 | ||
394 | /* Invoke the actual external process using the constructed command line. */ | |
395 | invoke_cmd( cmd_local->value, slot ); | |
396 | ||
397 | /* Free our local command string copy. */ | |
398 | string_free( cmd_local ); | |
399 | } | |
400 | ||
401 | ||
402 | /* | |
403 | * exec_wait() - wait for any of the async command processes to terminate | |
404 | * | |
405 | * Wait and drive at most one execution completion, while processing the I/O for | |
406 | * all ongoing commands. | |
407 | */ | |
408 | ||
409 | void exec_wait() | |
410 | { | |
411 | int i = -1; | |
412 | int exit_reason; /* reason why a command completed */ | |
413 | ||
414 | /* Wait for a command to complete, while snarfing up any output. */ | |
415 | while ( 1 ) | |
416 | { | |
417 | /* Check for a complete command, briefly. */ | |
418 | i = try_wait( 500 ); | |
419 | /* Read in the output of all running commands. */ | |
420 | read_output(); | |
421 | /* Close out pending debug style dialogs. */ | |
422 | close_alerts(); | |
423 | /* Process the completed command we found. */ | |
424 | if ( i >= 0 ) { exit_reason = EXIT_OK; break; } | |
425 | /* Check if a command ran out of time. */ | |
426 | i = try_kill_one(); | |
427 | if ( i >= 0 ) { exit_reason = EXIT_TIMEOUT; break; } | |
428 | } | |
429 | ||
430 | /* We have a command... process it. */ | |
431 | { | |
432 | DWORD exit_code; | |
433 | timing_info time; | |
434 | int rstat; | |
435 | ||
436 | /* The time data for the command. */ | |
437 | record_times( cmdtab[ i ].pi.hProcess, &time ); | |
438 | ||
439 | /* Removed the used temporary command file. */ | |
440 | if ( cmdtab[ i ].command_file->size ) | |
441 | unlink( cmdtab[ i ].command_file->value ); | |
442 | ||
443 | /* Find out the process exit code. */ | |
444 | GetExitCodeProcess( cmdtab[ i ].pi.hProcess, &exit_code ); | |
445 | ||
446 | /* The dispossition of the command. */ | |
447 | if ( interrupted() ) | |
448 | rstat = EXEC_CMD_INTR; | |
449 | else if ( exit_code ) | |
450 | rstat = EXEC_CMD_FAIL; | |
451 | else | |
452 | rstat = EXEC_CMD_OK; | |
453 | ||
454 | /* Call the callback, may call back to jam rule land. */ | |
455 | (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time, | |
456 | cmdtab[ i ].buffer_out->value, cmdtab[ i ].buffer_err->value, | |
457 | exit_reason ); | |
458 | ||
459 | /* Clean up our child process tracking data. No need to clear the | |
460 | * temporary command file name as it gets reused. | |
461 | */ | |
462 | closeWinHandle( &cmdtab[ i ].pi.hProcess ); | |
463 | closeWinHandle( &cmdtab[ i ].pi.hThread ); | |
464 | closeWinHandle( &cmdtab[ i ].pipe_out[ EXECCMD_PIPE_READ ] ); | |
465 | closeWinHandle( &cmdtab[ i ].pipe_out[ EXECCMD_PIPE_WRITE ] ); | |
466 | closeWinHandle( &cmdtab[ i ].pipe_err[ EXECCMD_PIPE_READ ] ); | |
467 | closeWinHandle( &cmdtab[ i ].pipe_err[ EXECCMD_PIPE_WRITE ] ); | |
468 | string_renew( cmdtab[ i ].buffer_out ); | |
469 | string_renew( cmdtab[ i ].buffer_err ); | |
470 | } | |
471 | } | |
472 | ||
473 | ||
474 | /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ | |
475 | ||
476 | /* | |
477 | * Invoke the actual external process using the given command line. Track the | |
478 | * process in our running commands table. | |
479 | */ | |
480 | ||
481 | static void invoke_cmd( char const * const command, int const slot ) | |
482 | { | |
483 | SECURITY_ATTRIBUTES sa = { sizeof( SECURITY_ATTRIBUTES ), 0, 0 }; | |
484 | SECURITY_DESCRIPTOR sd; | |
485 | STARTUPINFO si = { sizeof( STARTUPINFO ), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
486 | 0, 0, 0, 0, 0, 0 }; | |
487 | ||
488 | /* Init the security data. */ | |
489 | InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION ); | |
490 | SetSecurityDescriptorDacl( &sd, TRUE, NULL, FALSE ); | |
491 | sa.lpSecurityDescriptor = &sd; | |
492 | sa.bInheritHandle = TRUE; | |
493 | ||
494 | /* Create output buffers. */ | |
495 | string_new( cmdtab[ slot ].buffer_out ); | |
496 | string_new( cmdtab[ slot ].buffer_err ); | |
497 | ||
498 | /* Create pipes for communicating with the child process. */ | |
499 | if ( !CreatePipe( &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_READ ], | |
500 | &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ], &sa, 0 ) ) | |
501 | { | |
502 | reportWindowsError( "CreatePipe", slot ); | |
503 | return; | |
504 | } | |
505 | if ( globs.pipe_action && !CreatePipe( &cmdtab[ slot ].pipe_err[ | |
506 | EXECCMD_PIPE_READ ], &cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_WRITE ], | |
507 | &sa, 0 ) ) | |
508 | { | |
509 | reportWindowsError( "CreatePipe", slot ); | |
510 | return; | |
511 | } | |
512 | ||
513 | /* Set handle inheritance off for the pipe ends the parent reads from. */ | |
514 | SetHandleInformation( cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_READ ], | |
515 | HANDLE_FLAG_INHERIT, 0 ); | |
516 | if ( globs.pipe_action ) | |
517 | SetHandleInformation( cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_READ ], | |
518 | HANDLE_FLAG_INHERIT, 0 ); | |
519 | ||
520 | /* Hide the child window, if any. */ | |
521 | si.dwFlags |= STARTF_USESHOWWINDOW; | |
522 | si.wShowWindow = SW_HIDE; | |
523 | ||
524 | /* Redirect the child's output streams to our pipes. */ | |
525 | si.dwFlags |= STARTF_USESTDHANDLES; | |
526 | si.hStdOutput = cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ]; | |
527 | si.hStdError = globs.pipe_action | |
528 | ? cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_WRITE ] | |
529 | : cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ]; | |
530 | ||
531 | /* Let the child inherit stdin, as some commands assume it is available. */ | |
532 | si.hStdInput = GetStdHandle( STD_INPUT_HANDLE ); | |
533 | ||
534 | if ( DEBUG_EXECCMD ) | |
535 | out_printf( "Command string for CreateProcessA(): '%s'\n", command ); | |
536 | ||
537 | /* Run the command by creating a sub-process for it. */ | |
538 | if ( !CreateProcessA( | |
539 | NULL , /* application name */ | |
540 | (char *)command , /* command line */ | |
541 | NULL , /* process attributes */ | |
542 | NULL , /* thread attributes */ | |
543 | TRUE , /* inherit handles */ | |
544 | CREATE_NEW_PROCESS_GROUP, /* create flags */ | |
545 | NULL , /* env vars, null inherits env */ | |
546 | NULL , /* current dir, null is our current dir */ | |
547 | &si , /* startup info */ | |
548 | &cmdtab[ slot ].pi ) ) /* child process info, if created */ | |
549 | { | |
550 | reportWindowsError( "CreateProcessA", slot ); | |
551 | return; | |
552 | } | |
b32b8144 FG |
553 | |
554 | register_wait( slot ); | |
7c673cae FG |
555 | } |
556 | ||
557 | ||
558 | /* | |
559 | * For more details on Windows cmd.exe shell command-line length limitations see | |
560 | * the following MSDN article: | |
561 | * http://support.microsoft.com/default.aspx?scid=kb;en-us;830473 | |
562 | */ | |
563 | ||
564 | static int raw_maxline() | |
565 | { | |
566 | OSVERSIONINFO os_info; | |
567 | os_info.dwOSVersionInfoSize = sizeof( os_info ); | |
568 | GetVersionEx( &os_info ); | |
569 | ||
570 | if ( os_info.dwMajorVersion >= 5 ) return 8191; /* XP */ | |
571 | if ( os_info.dwMajorVersion == 4 ) return 2047; /* NT 4.x */ | |
572 | return 996; /* NT 3.5.1 */ | |
573 | } | |
574 | ||
575 | static int maxline() | |
576 | { | |
b32b8144 | 577 | static int result; |
7c673cae FG |
578 | if ( !result ) result = raw_maxline(); |
579 | return result; | |
580 | } | |
581 | ||
582 | ||
583 | /* | |
584 | * Closes a Windows HANDLE and resets its variable to 0. | |
585 | */ | |
586 | ||
587 | static void closeWinHandle( HANDLE * const handle ) | |
588 | { | |
589 | if ( *handle ) | |
590 | { | |
591 | CloseHandle( *handle ); | |
592 | *handle = 0; | |
593 | } | |
594 | } | |
595 | ||
596 | ||
597 | /* | |
598 | * Frees and renews the given string. | |
599 | */ | |
600 | ||
601 | static void string_renew( string * const s ) | |
602 | { | |
603 | string_free( s ); | |
604 | string_new( s ); | |
605 | } | |
606 | ||
607 | ||
608 | /* | |
609 | * raw_command_length() - valid raw command string length | |
610 | * | |
611 | * Checks whether the given command may be executed as a raw command. If yes, | |
612 | * returns the corresponding command string length. If not, returns -1. | |
613 | * | |
614 | * Rules for constructing raw command strings: | |
615 | * - Command may not contain unquoted shell I/O redirection characters. | |
616 | * - May have at most one command line with non-whitespace content. | |
617 | * - Leading whitespace trimmed. | |
618 | * - Trailing all-whitespace lines trimmed. | |
619 | * - Trailing whitespace on the sole command line kept (may theoretically | |
620 | * affect the executed command). | |
621 | */ | |
622 | ||
623 | static long raw_command_length( char const * command ) | |
624 | { | |
625 | char const * p; | |
626 | char const * escape = 0; | |
627 | char inquote = 0; | |
628 | char const * newline = 0; | |
629 | ||
630 | /* Skip leading whitespace. */ | |
631 | while ( isspace( *command ) ) | |
632 | ++command; | |
633 | ||
634 | p = command; | |
635 | ||
636 | /* Look for newlines and unquoted I/O redirection. */ | |
637 | do | |
638 | { | |
639 | p += strcspn( p, "\n\"'<>|\\" ); | |
640 | switch ( *p ) | |
641 | { | |
642 | case '\n': | |
643 | /* If our command contains non-whitespace content split over | |
644 | * multiple lines we can not execute it directly. | |
645 | */ | |
646 | newline = p; | |
647 | while ( isspace( *++p ) ); | |
648 | if ( *p ) return -1; | |
649 | break; | |
650 | ||
651 | case '\\': | |
652 | escape = escape && escape == p - 1 ? 0 : p; | |
653 | ++p; | |
654 | break; | |
655 | ||
656 | case '"': | |
657 | case '\'': | |
658 | if ( escape && escape == p - 1 ) | |
659 | escape = 0; | |
660 | else if ( inquote == *p ) | |
661 | inquote = 0; | |
662 | else if ( !inquote ) | |
663 | inquote = *p; | |
664 | ++p; | |
665 | break; | |
666 | ||
667 | case '<': | |
668 | case '>': | |
669 | case '|': | |
670 | if ( !inquote ) | |
671 | return -1; | |
672 | ++p; | |
673 | break; | |
674 | } | |
675 | } | |
676 | while ( *p ); | |
677 | ||
678 | /* Return the number of characters the command will occupy. */ | |
679 | return ( newline ? newline : p ) - command; | |
680 | } | |
681 | ||
682 | ||
683 | /* 64-bit arithmetic helpers. */ | |
684 | ||
685 | /* Compute the carry bit from the addition of two 32-bit unsigned numbers. */ | |
686 | #define add_carry_bit( a, b ) ((((a) | (b)) >> 31) & (~((a) + (b)) >> 31) & 0x1) | |
687 | ||
688 | /* Compute the high 32 bits of the addition of two 64-bit unsigned numbers, h1l1 | |
689 | * and h2l2. | |
690 | */ | |
691 | #define add_64_hi( h1, l1, h2, l2 ) ((h1) + (h2) + add_carry_bit(l1, l2)) | |
692 | ||
693 | ||
694 | /* | |
695 | * Add two 64-bit unsigned numbers, h1l1 and h2l2. | |
696 | */ | |
697 | ||
698 | static FILETIME add_64 | |
699 | ( | |
700 | unsigned long h1, unsigned long l1, | |
701 | unsigned long h2, unsigned long l2 | |
702 | ) | |
703 | { | |
704 | FILETIME result; | |
705 | result.dwLowDateTime = l1 + l2; | |
706 | result.dwHighDateTime = add_64_hi( h1, l1, h2, l2 ); | |
707 | return result; | |
708 | } | |
709 | ||
710 | ||
711 | static FILETIME add_FILETIME( FILETIME t1, FILETIME t2 ) | |
712 | { | |
713 | return add_64( t1.dwHighDateTime, t1.dwLowDateTime, t2.dwHighDateTime, | |
714 | t2.dwLowDateTime ); | |
715 | } | |
716 | ||
717 | ||
718 | static FILETIME negate_FILETIME( FILETIME t ) | |
719 | { | |
720 | /* 2s complement negation */ | |
721 | return add_64( ~t.dwHighDateTime, ~t.dwLowDateTime, 0, 1 ); | |
722 | } | |
723 | ||
724 | ||
725 | /* | |
726 | * filetime_to_seconds() - Windows FILETIME --> number of seconds conversion | |
727 | */ | |
728 | ||
729 | static double filetime_to_seconds( FILETIME const ft ) | |
730 | { | |
731 | return ft.dwHighDateTime * ( (double)( 1UL << 31 ) * 2.0 * 1.0e-7 ) + | |
732 | ft.dwLowDateTime * 1.0e-7; | |
733 | } | |
734 | ||
735 | ||
736 | static void record_times( HANDLE const process, timing_info * const time ) | |
737 | { | |
738 | FILETIME creation; | |
739 | FILETIME exit; | |
740 | FILETIME kernel; | |
741 | FILETIME user; | |
742 | if ( GetProcessTimes( process, &creation, &exit, &kernel, &user ) ) | |
743 | { | |
744 | time->system = filetime_to_seconds( kernel ); | |
745 | time->user = filetime_to_seconds( user ); | |
746 | timestamp_from_filetime( &time->start, &creation ); | |
747 | timestamp_from_filetime( &time->end, &exit ); | |
748 | } | |
749 | } | |
750 | ||
751 | ||
752 | #define IO_BUFFER_SIZE ( 16 * 1024 ) | |
753 | ||
754 | static char ioBuffer[ IO_BUFFER_SIZE + 1 ]; | |
755 | ||
11fdf7f2 TL |
756 | #define FORWARD_PIPE_NONE 0 |
757 | #define FORWARD_PIPE_STDOUT 1 | |
758 | #define FORWARD_PIPE_STDERR 2 | |
7c673cae FG |
759 | |
760 | static void read_pipe | |
761 | ( | |
762 | HANDLE in, /* the pipe to read from */ | |
11fdf7f2 TL |
763 | string * out, |
764 | int forwarding_mode | |
7c673cae FG |
765 | ) |
766 | { | |
767 | DWORD bytesInBuffer = 0; | |
768 | DWORD bytesAvailable = 0; | |
769 | ||
770 | do | |
771 | { | |
772 | /* check if we have any data to read */ | |
773 | if ( !PeekNamedPipe( in, ioBuffer, IO_BUFFER_SIZE, &bytesInBuffer, | |
774 | &bytesAvailable, NULL ) ) | |
775 | bytesAvailable = 0; | |
776 | ||
777 | /* read in the available data */ | |
778 | if ( bytesAvailable > 0 ) | |
779 | { | |
780 | /* we only read in the available bytes, to avoid blocking */ | |
781 | if ( ReadFile( in, ioBuffer, bytesAvailable <= IO_BUFFER_SIZE ? | |
782 | bytesAvailable : IO_BUFFER_SIZE, &bytesInBuffer, NULL ) ) | |
783 | { | |
784 | if ( bytesInBuffer > 0 ) | |
785 | { | |
786 | /* Clean up some illegal chars. */ | |
787 | int i; | |
788 | for ( i = 0; i < bytesInBuffer; ++i ) | |
789 | { | |
790 | if ( ( (unsigned char)ioBuffer[ i ] < 1 ) ) | |
791 | ioBuffer[ i ] = '?'; | |
792 | } | |
793 | /* Null, terminate. */ | |
794 | ioBuffer[ bytesInBuffer ] = '\0'; | |
795 | /* Append to the output. */ | |
796 | string_append( out, ioBuffer ); | |
11fdf7f2 TL |
797 | /* Copy it to our output if appropriate */ |
798 | if ( forwarding_mode == FORWARD_PIPE_STDOUT ) | |
799 | out_data( ioBuffer ); | |
800 | else if ( forwarding_mode == FORWARD_PIPE_STDERR ) | |
801 | err_data( ioBuffer ); | |
7c673cae FG |
802 | /* Subtract what we read in. */ |
803 | bytesAvailable -= bytesInBuffer; | |
804 | } | |
805 | else | |
806 | { | |
807 | /* Likely read a error, bail out. */ | |
808 | bytesAvailable = 0; | |
809 | } | |
810 | } | |
811 | else | |
812 | { | |
813 | /* Definitely read a error, bail out. */ | |
814 | bytesAvailable = 0; | |
815 | } | |
816 | } | |
817 | } | |
818 | while ( bytesAvailable > 0 ); | |
819 | } | |
820 | ||
11fdf7f2 TL |
821 | #define EARLY_OUTPUT( cmd ) \ |
822 | ( ! ( cmd.flags & EXEC_CMD_QUIET ) ) | |
823 | ||
824 | #define FORWARD_STDOUT( c ) \ | |
825 | ( ( EARLY_OUTPUT( c ) && ( globs.pipe_action != 2 ) ) ? \ | |
826 | FORWARD_PIPE_STDOUT : FORWARD_PIPE_NONE ) | |
827 | #define FORWARD_STDERR( c ) \ | |
828 | ( ( EARLY_OUTPUT( c ) && ( globs.pipe_action & 2 ) ) ? \ | |
829 | FORWARD_PIPE_STDERR : FORWARD_PIPE_NONE ) | |
7c673cae FG |
830 | |
831 | static void read_output() | |
832 | { | |
833 | int i; | |
834 | for ( i = 0; i < globs.jobs; ++i ) | |
835 | if ( cmdtab[ i ].pi.hProcess ) | |
836 | { | |
837 | /* Read stdout data. */ | |
838 | if ( cmdtab[ i ].pipe_out[ EXECCMD_PIPE_READ ] ) | |
839 | read_pipe( cmdtab[ i ].pipe_out[ EXECCMD_PIPE_READ ], | |
11fdf7f2 | 840 | cmdtab[ i ].buffer_out, FORWARD_STDOUT( cmdtab[ i ] ) ); |
7c673cae FG |
841 | /* Read stderr data. */ |
842 | if ( cmdtab[ i ].pipe_err[ EXECCMD_PIPE_READ ] ) | |
843 | read_pipe( cmdtab[ i ].pipe_err[ EXECCMD_PIPE_READ ], | |
11fdf7f2 | 844 | cmdtab[ i ].buffer_err, FORWARD_STDERR( cmdtab[ i ] ) ); |
7c673cae FG |
845 | } |
846 | } | |
847 | ||
b32b8144 FG |
848 | static void CALLBACK try_wait_callback( void * data, BOOLEAN is_timeout ) |
849 | { | |
850 | struct _cmdtab_t * slot = ( struct _cmdtab_t * )data; | |
851 | WaitForSingleObject( process_queue.write_okay, INFINITE ); | |
852 | process_queue.job_index = slot - cmdtab; | |
853 | assert( !is_timeout ); | |
854 | SetEvent( process_queue.read_okay ); | |
855 | /* Okay. Non-blocking. */ | |
856 | UnregisterWait( slot->wait_handle ); | |
857 | } | |
858 | ||
859 | static int try_wait_impl( DWORD timeout ) | |
860 | { | |
861 | int job_index; | |
862 | int timed_out; | |
863 | int res = WaitForSingleObject( process_queue.read_okay, timeout ); | |
864 | if ( res != WAIT_OBJECT_0 ) | |
865 | return -1; | |
866 | job_index = process_queue.job_index; | |
867 | SetEvent( process_queue.write_okay ); | |
868 | return job_index; | |
869 | } | |
870 | ||
871 | static void register_wait( int job_id ) | |
872 | { | |
873 | if ( globs.jobs > MAXIMUM_WAIT_OBJECTS ) | |
874 | { | |
875 | HANDLE ignore; | |
876 | RegisterWaitForSingleObject( &cmdtab[ job_id ].wait_handle, | |
877 | cmdtab[ job_id ].pi.hProcess, | |
878 | &try_wait_callback, &cmdtab[ job_id ], INFINITE, | |
879 | WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE ); | |
880 | } | |
881 | } | |
7c673cae FG |
882 | |
883 | /* | |
884 | * Waits for a single child process command to complete, or the timeout, | |
885 | * whichever comes first. Returns the index of the completed command in the | |
886 | * cmdtab array, or -1. | |
887 | */ | |
888 | ||
7c673cae FG |
889 | static int try_wait( int const timeoutMillis ) |
890 | { | |
b32b8144 FG |
891 | if ( globs.jobs <= MAXIMUM_WAIT_OBJECTS ) |
892 | { | |
893 | int i; | |
894 | HANDLE active_handles[ MAXIMUM_WAIT_OBJECTS ]; | |
895 | int job_ids[ MAXIMUM_WAIT_OBJECTS ]; | |
896 | DWORD num_handles = 0; | |
897 | DWORD wait_api_result; | |
898 | for ( i = 0; i < globs.jobs; ++i ) | |
7c673cae | 899 | { |
b32b8144 | 900 | if( cmdtab[ i ].pi.hProcess ) |
7c673cae | 901 | { |
b32b8144 FG |
902 | job_ids[ num_handles ] = i; |
903 | active_handles[ num_handles ] = cmdtab[ i ].pi.hProcess; | |
904 | ++num_handles; | |
7c673cae | 905 | } |
7c673cae | 906 | } |
b32b8144 FG |
907 | wait_api_result = WaitForMultipleObjects( num_handles, active_handles, FALSE, timeoutMillis ); |
908 | if ( WAIT_OBJECT_0 <= wait_api_result && wait_api_result < WAIT_OBJECT_0 + globs.jobs ) | |
7c673cae | 909 | { |
b32b8144 | 910 | return job_ids[ wait_api_result - WAIT_OBJECT_0 ]; |
7c673cae FG |
911 | } |
912 | else | |
913 | { | |
b32b8144 | 914 | return -1; |
7c673cae FG |
915 | } |
916 | } | |
b32b8144 | 917 | else |
7c673cae | 918 | { |
b32b8144 | 919 | return try_wait_impl( timeoutMillis ); |
7c673cae FG |
920 | } |
921 | ||
7c673cae FG |
922 | } |
923 | ||
924 | ||
925 | static int try_kill_one() | |
926 | { | |
927 | /* Only need to check if a timeout was specified with the -l option. */ | |
928 | if ( globs.timeout > 0 ) | |
929 | { | |
930 | int i; | |
931 | for ( i = 0; i < globs.jobs; ++i ) | |
932 | if ( cmdtab[ i ].pi.hProcess ) | |
933 | { | |
934 | double const t = running_time( cmdtab[ i ].pi.hProcess ); | |
935 | if ( t > (double)globs.timeout ) | |
936 | { | |
937 | /* The job may have left an alert dialog around, try and get | |
938 | * rid of it before killing the job itself. | |
939 | */ | |
940 | close_alert( &cmdtab[ i ].pi ); | |
941 | /* We have a "runaway" job, kill it. */ | |
942 | kill_process_tree( cmdtab[ i ].pi.dwProcessId, | |
943 | cmdtab[ i ].pi.hProcess ); | |
944 | /* And return its running commands table slot. */ | |
945 | return i; | |
946 | } | |
947 | } | |
948 | } | |
949 | return -1; | |
950 | } | |
951 | ||
952 | ||
953 | static void close_alerts() | |
954 | { | |
955 | /* We only attempt this every 5 seconds or so, because it is not a cheap | |
956 | * operation, and we will catch the alerts eventually. This check uses | |
957 | * floats as some compilers define CLOCKS_PER_SEC as a float or double. | |
958 | */ | |
959 | if ( ( (float)clock() / (float)( CLOCKS_PER_SEC * 5 ) ) < ( 1.0 / 5.0 ) ) | |
960 | { | |
961 | int i; | |
962 | for ( i = 0; i < globs.jobs; ++i ) | |
963 | if ( cmdtab[ i ].pi.hProcess ) | |
964 | close_alert( &cmdtab[ i ].pi ); | |
965 | } | |
966 | } | |
967 | ||
968 | ||
969 | /* | |
970 | * Calc the current running time of an *active* process. | |
971 | */ | |
972 | ||
973 | static double running_time( HANDLE const process ) | |
974 | { | |
975 | FILETIME creation; | |
976 | FILETIME exit; | |
977 | FILETIME kernel; | |
978 | FILETIME user; | |
979 | if ( GetProcessTimes( process, &creation, &exit, &kernel, &user ) ) | |
980 | { | |
981 | /* Compute the elapsed time. */ | |
982 | FILETIME current; | |
983 | GetSystemTimeAsFileTime( ¤t ); | |
984 | return filetime_to_seconds( add_FILETIME( current, | |
985 | negate_FILETIME( creation ) ) ); | |
986 | } | |
987 | return 0.0; | |
988 | } | |
989 | ||
990 | ||
991 | /* | |
992 | * Not really optimal, or efficient, but it is easier this way, and it is not | |
993 | * like we are going to be killing thousands, or even tens of processes. | |
994 | */ | |
995 | ||
996 | static void kill_process_tree( DWORD const pid, HANDLE const process ) | |
997 | { | |
998 | HANDLE const process_snapshot_h = CreateToolhelp32Snapshot( | |
999 | TH32CS_SNAPPROCESS, 0 ); | |
1000 | if ( INVALID_HANDLE_VALUE != process_snapshot_h ) | |
1001 | { | |
1002 | BOOL ok = TRUE; | |
1003 | PROCESSENTRY32 pinfo; | |
1004 | pinfo.dwSize = sizeof( PROCESSENTRY32 ); | |
1005 | for ( | |
1006 | ok = Process32First( process_snapshot_h, &pinfo ); | |
1007 | ok == TRUE; | |
1008 | ok = Process32Next( process_snapshot_h, &pinfo ) ) | |
1009 | { | |
1010 | if ( pinfo.th32ParentProcessID == pid ) | |
1011 | { | |
1012 | /* Found a child, recurse to kill it and anything else below it. | |
1013 | */ | |
1014 | HANDLE const ph = OpenProcess( PROCESS_ALL_ACCESS, FALSE, | |
1015 | pinfo.th32ProcessID ); | |
1016 | if ( ph ) | |
1017 | { | |
1018 | kill_process_tree( pinfo.th32ProcessID, ph ); | |
1019 | CloseHandle( ph ); | |
1020 | } | |
1021 | } | |
1022 | } | |
1023 | CloseHandle( process_snapshot_h ); | |
1024 | } | |
1025 | /* Now that the children are all dead, kill the root. */ | |
1026 | TerminateProcess( process, -2 ); | |
1027 | } | |
1028 | ||
1029 | ||
1030 | static double creation_time( HANDLE const process ) | |
1031 | { | |
1032 | FILETIME creation; | |
1033 | FILETIME exit; | |
1034 | FILETIME kernel; | |
1035 | FILETIME user; | |
1036 | return GetProcessTimes( process, &creation, &exit, &kernel, &user ) | |
1037 | ? filetime_to_seconds( creation ) | |
1038 | : 0.0; | |
1039 | } | |
1040 | ||
1041 | ||
1042 | /* | |
1043 | * Recursive check if first process is parent (directly or indirectly) of the | |
1044 | * second one. Both processes are passed as process ids, not handles. Special | |
1045 | * return value 2 means that the second process is smss.exe and its parent | |
1046 | * process is System (first argument is ignored). | |
1047 | */ | |
1048 | ||
1049 | static int is_parent_child( DWORD const parent, DWORD const child ) | |
1050 | { | |
1051 | HANDLE process_snapshot_h = INVALID_HANDLE_VALUE; | |
1052 | ||
1053 | if ( !child ) | |
1054 | return 0; | |
1055 | if ( parent == child ) | |
1056 | return 1; | |
1057 | ||
1058 | process_snapshot_h = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); | |
1059 | if ( INVALID_HANDLE_VALUE != process_snapshot_h ) | |
1060 | { | |
1061 | BOOL ok = TRUE; | |
1062 | PROCESSENTRY32 pinfo; | |
1063 | pinfo.dwSize = sizeof( PROCESSENTRY32 ); | |
1064 | for ( | |
1065 | ok = Process32First( process_snapshot_h, &pinfo ); | |
1066 | ok == TRUE; | |
1067 | ok = Process32Next( process_snapshot_h, &pinfo ) ) | |
1068 | { | |
1069 | if ( pinfo.th32ProcessID == child ) | |
1070 | { | |
1071 | /* Unfortunately, process ids are not really unique. There might | |
1072 | * be spurious "parent and child" relationship match between two | |
1073 | * non-related processes if real parent process of a given | |
1074 | * process has exited (while child process kept running as an | |
1075 | * "orphan") and the process id of such parent process has been | |
1076 | * reused by internals of the operating system when creating | |
1077 | * another process. | |
1078 | * | |
1079 | * Thus an additional check is needed - process creation time. | |
1080 | * This check may fail (i.e. return 0) for system processes due | |
1081 | * to insufficient privileges, and that is OK. | |
1082 | */ | |
1083 | double tchild = 0.0; | |
1084 | double tparent = 0.0; | |
1085 | HANDLE const hchild = OpenProcess( PROCESS_QUERY_INFORMATION, | |
1086 | FALSE, pinfo.th32ProcessID ); | |
1087 | CloseHandle( process_snapshot_h ); | |
1088 | ||
1089 | /* csrss.exe may display message box like following: | |
1090 | * xyz.exe - Unable To Locate Component | |
1091 | * This application has failed to start because | |
1092 | * boost_foo-bar.dll was not found. Re-installing the | |
1093 | * application may fix the problem | |
1094 | * This actually happens when starting a test process that | |
1095 | * depends on a dynamic library which failed to build. We want | |
1096 | * to automatically close these message boxes even though | |
1097 | * csrss.exe is not our child process. We may depend on the fact | |
1098 | * that (in all current versions of Windows) csrss.exe is a | |
1099 | * direct child of the smss.exe process, which in turn is a | |
1100 | * direct child of the System process, which always has process | |
1101 | * id == 4. This check must be performed before comparing | |
1102 | * process creation times. | |
1103 | */ | |
1104 | if ( !stricmp( pinfo.szExeFile, "csrss.exe" ) && | |
1105 | is_parent_child( parent, pinfo.th32ParentProcessID ) == 2 ) | |
1106 | return 1; | |
1107 | if ( !stricmp( pinfo.szExeFile, "smss.exe" ) && | |
1108 | ( pinfo.th32ParentProcessID == 4 ) ) | |
1109 | return 2; | |
1110 | ||
1111 | if ( hchild ) | |
1112 | { | |
1113 | HANDLE hparent = OpenProcess( PROCESS_QUERY_INFORMATION, | |
1114 | FALSE, pinfo.th32ParentProcessID ); | |
1115 | if ( hparent ) | |
1116 | { | |
1117 | tchild = creation_time( hchild ); | |
1118 | tparent = creation_time( hparent ); | |
1119 | CloseHandle( hparent ); | |
1120 | } | |
1121 | CloseHandle( hchild ); | |
1122 | } | |
1123 | ||
1124 | /* Return 0 if one of the following is true: | |
1125 | * 1. we failed to read process creation time | |
1126 | * 2. child was created before alleged parent | |
1127 | */ | |
1128 | if ( ( tchild == 0.0 ) || ( tparent == 0.0 ) || | |
1129 | ( tchild < tparent ) ) | |
1130 | return 0; | |
1131 | ||
1132 | return is_parent_child( parent, pinfo.th32ParentProcessID ) & 1; | |
1133 | } | |
1134 | } | |
1135 | ||
1136 | CloseHandle( process_snapshot_h ); | |
1137 | } | |
1138 | ||
1139 | return 0; | |
1140 | } | |
1141 | ||
1142 | ||
1143 | /* | |
1144 | * Called by the OS for each topmost window. | |
1145 | */ | |
1146 | ||
1147 | BOOL CALLBACK close_alert_window_enum( HWND hwnd, LPARAM lParam ) | |
1148 | { | |
1149 | char buf[ 7 ] = { 0 }; | |
1150 | PROCESS_INFORMATION const * const pi = (PROCESS_INFORMATION *)lParam; | |
1151 | DWORD pid; | |
1152 | DWORD tid; | |
1153 | ||
1154 | /* We want to find and close any window that: | |
1155 | * 1. is visible and | |
1156 | * 2. is a dialog and | |
1157 | * 3. is displayed by any of our child processes | |
1158 | */ | |
1159 | if ( | |
1160 | /* We assume hidden windows do not require user interaction. */ | |
1161 | !IsWindowVisible( hwnd ) | |
1162 | /* Failed to read class name; presume it is not a dialog. */ | |
1163 | || !GetClassNameA( hwnd, buf, sizeof( buf ) ) | |
1164 | /* All Windows system dialogs use the same Window class name. */ | |
1165 | || strcmp( buf, "#32770" ) ) | |
1166 | return TRUE; | |
1167 | ||
1168 | /* GetWindowThreadProcessId() returns 0 on error, otherwise thread id of | |
1169 | * the window's message pump thread. | |
1170 | */ | |
1171 | tid = GetWindowThreadProcessId( hwnd, &pid ); | |
1172 | if ( !tid || !is_parent_child( pi->dwProcessId, pid ) ) | |
1173 | return TRUE; | |
1174 | ||
1175 | /* Ask real nice. */ | |
1176 | PostMessageA( hwnd, WM_CLOSE, 0, 0 ); | |
1177 | ||
1178 | /* Wait and see if it worked. If not, insist. */ | |
1179 | if ( WaitForSingleObject( pi->hProcess, 200 ) == WAIT_TIMEOUT ) | |
1180 | { | |
1181 | PostThreadMessageA( tid, WM_QUIT, 0, 0 ); | |
1182 | WaitForSingleObject( pi->hProcess, 300 ); | |
1183 | } | |
1184 | ||
1185 | /* Done, we do not want to check any other windows now. */ | |
1186 | return FALSE; | |
1187 | } | |
1188 | ||
1189 | ||
1190 | static void close_alert( PROCESS_INFORMATION const * const pi ) | |
1191 | { | |
1192 | EnumWindows( &close_alert_window_enum, (LPARAM)pi ); | |
1193 | } | |
1194 | ||
1195 | ||
1196 | /* | |
1197 | * Open a command file to store the command into for executing using an external | |
1198 | * shell. Returns a pointer to a FILE open for writing or 0 in case such a file | |
1199 | * could not be opened. The file name used is stored back in the corresponding | |
1200 | * running commands table slot. | |
1201 | * | |
1202 | * Expects the running commands table slot's command_file attribute to contain | |
1203 | * either a zeroed out string object or one prepared previously by this same | |
1204 | * function. | |
1205 | */ | |
1206 | ||
1207 | static FILE * open_command_file( int const slot ) | |
1208 | { | |
1209 | string * const command_file = cmdtab[ slot ].command_file; | |
1210 | ||
1211 | /* If the temporary command file name has not already been prepared for this | |
1212 | * slot number, prepare a new one containing a '##' place holder that will | |
1213 | * be changed later and needs to be located at a fixed distance from the | |
1214 | * end. | |
1215 | */ | |
1216 | if ( !command_file->value ) | |
1217 | { | |
1218 | DWORD const procID = GetCurrentProcessId(); | |
1219 | string const * const tmpdir = path_tmpdir(); | |
1220 | string_new( command_file ); | |
1221 | string_reserve( command_file, tmpdir->size + 64 ); | |
1222 | command_file->size = sprintf( command_file->value, | |
1223 | "%s\\jam%d-%02d-##.bat", tmpdir->value, procID, slot ); | |
1224 | } | |
1225 | ||
1226 | /* For some reason opening a command file can fail intermittently. But doing | |
1227 | * some retries works. Most likely this is due to a previously existing file | |
1228 | * of the same name that happens to still be opened by an active virus | |
1229 | * scanner. Originally pointed out and fixed by Bronek Kozicki. | |
1230 | * | |
1231 | * We first try to open several differently named files to avoid having to | |
1232 | * wait idly if not absolutely necessary. Our temporary command file names | |
1233 | * contain a fixed position place holder we use for generating different | |
1234 | * file names. | |
1235 | */ | |
1236 | { | |
1237 | char * const index1 = command_file->value + command_file->size - 6; | |
1238 | char * const index2 = index1 + 1; | |
1239 | int waits_remaining; | |
1240 | assert( command_file->value < index1 ); | |
1241 | assert( index2 + 1 < command_file->value + command_file->size ); | |
1242 | assert( index2[ 1 ] == '.' ); | |
1243 | for ( waits_remaining = 3; ; --waits_remaining ) | |
1244 | { | |
1245 | int index; | |
1246 | for ( index = 0; index != 20; ++index ) | |
1247 | { | |
1248 | FILE * f; | |
1249 | *index1 = '0' + index / 10; | |
1250 | *index2 = '0' + index % 10; | |
1251 | f = fopen( command_file->value, "w" ); | |
1252 | if ( f ) return f; | |
1253 | } | |
1254 | if ( !waits_remaining ) break; | |
1255 | Sleep( 250 ); | |
1256 | } | |
1257 | } | |
1258 | ||
1259 | return 0; | |
1260 | } | |
1261 | ||
1262 | ||
1263 | /* | |
1264 | * Prepare a command file to be executed using an external shell. | |
1265 | */ | |
1266 | ||
1267 | static char const * prepare_command_file( string const * command, int slot ) | |
1268 | { | |
1269 | FILE * const f = open_command_file( slot ); | |
1270 | if ( !f ) | |
1271 | { | |
1272 | err_printf( "failed to write command file!\n" ); | |
1273 | exit( EXITBAD ); | |
1274 | } | |
1275 | fputs( command->value, f ); | |
1276 | fclose( f ); | |
1277 | return cmdtab[ slot ].command_file->value; | |
1278 | } | |
1279 | ||
1280 | ||
1281 | /* | |
1282 | * Find a free slot in the running commands table. | |
1283 | */ | |
1284 | ||
1285 | static int get_free_cmdtab_slot() | |
1286 | { | |
1287 | int slot; | |
b32b8144 | 1288 | for ( slot = 0; slot < globs.jobs; ++slot ) |
7c673cae FG |
1289 | if ( !cmdtab[ slot ].pi.hProcess ) |
1290 | return slot; | |
1291 | err_printf( "no slots for child!\n" ); | |
1292 | exit( EXITBAD ); | |
1293 | } | |
1294 | ||
1295 | ||
1296 | /* | |
1297 | * Put together the final command string we are to run. | |
1298 | */ | |
1299 | ||
1300 | static void string_new_from_argv( string * result, char const * const * argv ) | |
1301 | { | |
1302 | assert( argv ); | |
1303 | assert( argv[ 0 ] ); | |
1304 | string_copy( result, *(argv++) ); | |
1305 | while ( *argv ) | |
1306 | { | |
1307 | string_push_back( result, ' ' ); | |
11fdf7f2 | 1308 | string_push_back( result, '"' ); |
7c673cae | 1309 | string_append( result, *(argv++) ); |
11fdf7f2 | 1310 | string_push_back( result, '"' ); |
7c673cae FG |
1311 | } |
1312 | } | |
1313 | ||
1314 | ||
1315 | /* | |
1316 | * Reports the last failed Windows API related error message. | |
1317 | */ | |
1318 | ||
1319 | static void reportWindowsError( char const * const apiName, int slot ) | |
1320 | { | |
1321 | char * errorMessage; | |
1322 | char buf[24]; | |
1323 | string * err_buf; | |
1324 | timing_info time; | |
1325 | DWORD const errorCode = GetLastError(); | |
1326 | DWORD apiResult = FormatMessageA( | |
1327 | FORMAT_MESSAGE_ALLOCATE_BUFFER | /* __in DWORD dwFlags */ | |
1328 | FORMAT_MESSAGE_FROM_SYSTEM | | |
1329 | FORMAT_MESSAGE_IGNORE_INSERTS, | |
1330 | NULL, /* __in_opt LPCVOID lpSource */ | |
1331 | errorCode, /* __in DWORD dwMessageId */ | |
1332 | 0, /* __in DWORD dwLanguageId */ | |
1333 | (LPSTR)&errorMessage, /* __out LPTSTR lpBuffer */ | |
1334 | 0, /* __in DWORD nSize */ | |
1335 | 0 ); /* __in_opt va_list * Arguments */ | |
1336 | ||
1337 | /* Build a message as if the process had written to stderr. */ | |
1338 | if ( globs.pipe_action ) | |
1339 | err_buf = cmdtab[ slot ].buffer_err; | |
1340 | else | |
1341 | err_buf = cmdtab[ slot ].buffer_out; | |
1342 | string_append( err_buf, apiName ); | |
1343 | string_append( err_buf, "() Windows API failed: " ); | |
1344 | sprintf( buf, "%d", errorCode ); | |
1345 | string_append( err_buf, buf ); | |
1346 | ||
1347 | if ( !apiResult ) | |
1348 | string_append( err_buf, ".\n" ); | |
1349 | else | |
1350 | { | |
1351 | string_append( err_buf, " - " ); | |
1352 | string_append( err_buf, errorMessage ); | |
1353 | /* Make sure that the buffer is terminated with a newline */ | |
1354 | if( err_buf->value[ err_buf->size - 1 ] != '\n' ) | |
1355 | string_push_back( err_buf, '\n' ); | |
1356 | LocalFree( errorMessage ); | |
1357 | } | |
1358 | ||
1359 | /* Since the process didn't actually start, use a blank timing_info. */ | |
1360 | time.system = 0; | |
1361 | time.user = 0; | |
1362 | timestamp_current( &time.start ); | |
1363 | timestamp_current( &time.end ); | |
1364 | ||
1365 | /* Invoke the callback with a failure status. */ | |
1366 | (*cmdtab[ slot ].func)( cmdtab[ slot ].closure, EXEC_CMD_FAIL, &time, | |
1367 | cmdtab[ slot ].buffer_out->value, cmdtab[ slot ].buffer_err->value, | |
1368 | EXIT_OK ); | |
1369 | ||
1370 | /* Clean up any handles that were opened. */ | |
1371 | closeWinHandle( &cmdtab[ slot ].pi.hProcess ); | |
1372 | closeWinHandle( &cmdtab[ slot ].pi.hThread ); | |
1373 | closeWinHandle( &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_READ ] ); | |
1374 | closeWinHandle( &cmdtab[ slot ].pipe_out[ EXECCMD_PIPE_WRITE ] ); | |
1375 | closeWinHandle( &cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_READ ] ); | |
1376 | closeWinHandle( &cmdtab[ slot ].pipe_err[ EXECCMD_PIPE_WRITE ] ); | |
1377 | string_renew( cmdtab[ slot ].buffer_out ); | |
1378 | string_renew( cmdtab[ slot ].buffer_err ); | |
1379 | } | |
1380 | ||
1381 | ||
1382 | #endif /* USE_EXECNT */ |