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 | * Distributed under the Boost Software License, Version 1.0. |
---|
10 | * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) |
---|
11 | */ |
---|
12 | |
---|
13 | # include "jam.h" |
---|
14 | # include "lists.h" |
---|
15 | # include "execcmd.h" |
---|
16 | # include "pathsys.h" |
---|
17 | # include "debug.h" |
---|
18 | # include <errno.h> |
---|
19 | # include <assert.h> |
---|
20 | # include <ctype.h> |
---|
21 | # include <time.h> |
---|
22 | |
---|
23 | # ifdef USE_EXECNT |
---|
24 | |
---|
25 | # define WIN32_LEAN_AND_MEAN |
---|
26 | # include <windows.h> /* do the ugly deed */ |
---|
27 | # include <process.h> |
---|
28 | # if !defined( __BORLANDC__ ) |
---|
29 | # include <tlhelp32.h> |
---|
30 | # endif |
---|
31 | |
---|
32 | # if !defined( __BORLANDC__ ) && !defined( OS_OS2 ) |
---|
33 | # define wait my_wait |
---|
34 | static int my_wait( int *status ); |
---|
35 | # endif |
---|
36 | |
---|
37 | /* |
---|
38 | * execnt.c - execute a shell command on Windows NT and Windows 95/98 |
---|
39 | * |
---|
40 | * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp(). |
---|
41 | * The default is: |
---|
42 | * |
---|
43 | * /bin/sh -c % [ on UNIX/AmigaOS ] |
---|
44 | * cmd.exe /c % [ on Windows NT ] |
---|
45 | * |
---|
46 | * Each word must be an individual element in a jam variable value. |
---|
47 | * |
---|
48 | * In $(JAMSHELL), % expands to the command string and ! expands to |
---|
49 | * the slot number (starting at 1) for multiprocess (-j) invocations. |
---|
50 | * If $(JAMSHELL) doesn't include a %, it is tacked on as the last |
---|
51 | * argument. |
---|
52 | * |
---|
53 | * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work! |
---|
54 | * |
---|
55 | * External routines: |
---|
56 | * execcmd() - launch an async command execution |
---|
57 | * execwait() - wait and drive at most one execution completion |
---|
58 | * |
---|
59 | * Internal routines: |
---|
60 | * onintr() - bump intr to note command interruption |
---|
61 | * |
---|
62 | * 04/08/94 (seiwald) - Coherent/386 support added. |
---|
63 | * 05/04/94 (seiwald) - async multiprocess interface |
---|
64 | * 01/22/95 (seiwald) - $(JAMSHELL) support |
---|
65 | * 06/02/97 (gsar) - full async multiprocess support for Win32 |
---|
66 | */ |
---|
67 | |
---|
68 | static int intr = 0; |
---|
69 | static int cmdsrunning = 0; |
---|
70 | static void (*istat)( int ); |
---|
71 | |
---|
72 | static int is_nt_351 = 0; |
---|
73 | static int is_win95 = 1; |
---|
74 | static int is_win95_defined = 0; |
---|
75 | |
---|
76 | |
---|
77 | static struct |
---|
78 | { |
---|
79 | int pid; /* on win32, a real process handle */ |
---|
80 | void (*func)( void *closure, int status, timing_info* ); |
---|
81 | void *closure; |
---|
82 | char *tempfile; |
---|
83 | |
---|
84 | } cmdtab[ MAXJOBS ] = {{0}}; |
---|
85 | |
---|
86 | |
---|
87 | static void |
---|
88 | set_is_win95( void ) |
---|
89 | { |
---|
90 | OSVERSIONINFO os_info; |
---|
91 | |
---|
92 | os_info.dwOSVersionInfoSize = sizeof(os_info); |
---|
93 | os_info.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; |
---|
94 | GetVersionEx( &os_info ); |
---|
95 | |
---|
96 | is_win95 = (os_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); |
---|
97 | is_win95_defined = 1; |
---|
98 | |
---|
99 | /* now, test wether we're running Windows 3.51 */ |
---|
100 | /* this is later used to limit the system call command length */ |
---|
101 | if (os_info.dwPlatformId == VER_PLATFORM_WIN32_NT) |
---|
102 | is_nt_351 = os_info.dwMajorVersion == 3; |
---|
103 | } |
---|
104 | |
---|
105 | int maxline() |
---|
106 | { |
---|
107 | if (!is_win95_defined) |
---|
108 | set_is_win95(); |
---|
109 | |
---|
110 | /* Set the maximum command line length according to the OS */ |
---|
111 | return is_nt_351 ? 996 |
---|
112 | : is_win95 ? 1023 |
---|
113 | : 2047; |
---|
114 | } |
---|
115 | |
---|
116 | static void |
---|
117 | free_argv( char** args ) |
---|
118 | { |
---|
119 | free( args[0] ); |
---|
120 | free( args ); |
---|
121 | } |
---|
122 | |
---|
123 | /* Convert a command string into arguments for spawnvp. The original |
---|
124 | * code, inherited from ftjam, tried to break up every argument on the |
---|
125 | * command-line, dealing with quotes, but that's really a waste of |
---|
126 | * time on Win32, at least. It turns out that all you need to do is |
---|
127 | * get the raw path to the executable in the first argument to |
---|
128 | * spawnvp, and you can pass all the rest of the command-line |
---|
129 | * arguments to spawnvp in one, un-processed string. |
---|
130 | * |
---|
131 | * New strategy: break the string in at most one place. |
---|
132 | */ |
---|
133 | static char** |
---|
134 | string_to_args( const char* string ) |
---|
135 | { |
---|
136 | int src_len; |
---|
137 | int in_quote; |
---|
138 | char* line; |
---|
139 | char const* src; |
---|
140 | char* dst; |
---|
141 | char** argv; |
---|
142 | |
---|
143 | /* drop leading and trailing whitespace if any */ |
---|
144 | while (isspace(*string)) |
---|
145 | ++string; |
---|
146 | |
---|
147 | src_len = strlen( string ); |
---|
148 | while ( src_len > 0 && isspace( string[src_len - 1] ) ) |
---|
149 | --src_len; |
---|
150 | |
---|
151 | /* Copy the input string into a buffer we can modify |
---|
152 | */ |
---|
153 | line = (char*)malloc( src_len+1 ); |
---|
154 | if (!line) |
---|
155 | return 0; |
---|
156 | |
---|
157 | if ( DEBUG_PROFILE ) |
---|
158 | profile_memory( src_len+1 ); |
---|
159 | |
---|
160 | /* allocate the argv array. |
---|
161 | * element 0: stores the path to the executable |
---|
162 | * element 1: stores the command-line arguments to the executable |
---|
163 | * element 2: NULL terminator |
---|
164 | */ |
---|
165 | argv = (char**)malloc( 3 * sizeof(char*) ); |
---|
166 | if (!argv) |
---|
167 | { |
---|
168 | free( line ); |
---|
169 | return 0; |
---|
170 | } |
---|
171 | |
---|
172 | if ( DEBUG_PROFILE ) |
---|
173 | profile_memory( 3 * sizeof(char*) ); |
---|
174 | |
---|
175 | /* Strip quotes from the first command-line argument and find |
---|
176 | * where it ends. Quotes are illegal in Win32 pathnames, so we |
---|
177 | * don't need to worry about preserving escaped quotes here. |
---|
178 | * Spaces can't be escaped in Win32, only enclosed in quotes, so |
---|
179 | * removing backslash escapes is also a non-issue. |
---|
180 | */ |
---|
181 | in_quote = 0; |
---|
182 | for ( src = string, dst = line ; *src; src++ ) |
---|
183 | { |
---|
184 | if (*src == '"') |
---|
185 | in_quote = !in_quote; |
---|
186 | else if (!in_quote && isspace(*src)) |
---|
187 | break; |
---|
188 | else |
---|
189 | *dst++ = *src; |
---|
190 | } |
---|
191 | *dst++ = 0; |
---|
192 | argv[0] = line; |
---|
193 | |
---|
194 | /* skip whitespace in src */ |
---|
195 | while (isspace(*src)) |
---|
196 | ++src; |
---|
197 | |
---|
198 | argv[1] = dst; |
---|
199 | |
---|
200 | /* Copy the rest of the arguments verbatim */ |
---|
201 | |
---|
202 | src_len -= src - string; |
---|
203 | |
---|
204 | /* Use strncat because it appends a trailing nul */ |
---|
205 | *dst = 0; |
---|
206 | strncat(dst, src, src_len); |
---|
207 | |
---|
208 | argv[2] = 0; |
---|
209 | |
---|
210 | return argv; |
---|
211 | } |
---|
212 | |
---|
213 | |
---|
214 | |
---|
215 | /* process a "del" or "erase" command under Windows 95/98 */ |
---|
216 | static int |
---|
217 | process_del( char* command ) |
---|
218 | { |
---|
219 | char** arg; |
---|
220 | char* p = command, *q; |
---|
221 | int wildcard = 0, result = 0; |
---|
222 | |
---|
223 | /* first of all, skip the command itself */ |
---|
224 | if ( p[0] == 'd' ) |
---|
225 | p += 3; /* assumes "del..;" */ |
---|
226 | else if ( p[0] == 'e' ) |
---|
227 | p += 5; /* assumes "erase.." */ |
---|
228 | else |
---|
229 | return 1; /* invalid command */ |
---|
230 | |
---|
231 | /* process all targets independently */ |
---|
232 | for (;;) |
---|
233 | { |
---|
234 | /* skip leading spaces */ |
---|
235 | while ( *p && isspace(*p) ) |
---|
236 | p++; |
---|
237 | |
---|
238 | /* exit if we encounter an end of string */ |
---|
239 | if (!*p) |
---|
240 | return 0; |
---|
241 | |
---|
242 | /* ignore toggles/flags */ |
---|
243 | while (*p == '/') |
---|
244 | { |
---|
245 | p++; |
---|
246 | while ( *p && isalnum(*p) ) |
---|
247 | p++; |
---|
248 | while (*p && isspace(*p) ) |
---|
249 | ++p; |
---|
250 | } |
---|
251 | |
---|
252 | |
---|
253 | { |
---|
254 | int in_quote = 0; |
---|
255 | int wildcard = 0; |
---|
256 | int go_on = 1; |
---|
257 | |
---|
258 | q = p; |
---|
259 | while (go_on) |
---|
260 | { |
---|
261 | switch (*p) |
---|
262 | { |
---|
263 | case '"': |
---|
264 | in_quote = !in_quote; |
---|
265 | break; |
---|
266 | |
---|
267 | case '?': |
---|
268 | case '*': |
---|
269 | if (!in_quote) |
---|
270 | wildcard = 1; |
---|
271 | break; |
---|
272 | |
---|
273 | case '\0': |
---|
274 | if (in_quote) |
---|
275 | return 1; |
---|
276 | /* fall-through */ |
---|
277 | |
---|
278 | case ' ': |
---|
279 | case '\t': |
---|
280 | if (!in_quote) |
---|
281 | { |
---|
282 | int len = p - q; |
---|
283 | int result; |
---|
284 | char* line; |
---|
285 | |
---|
286 | /* q..p-1 contains the delete argument */ |
---|
287 | if ( len <= 0 ) |
---|
288 | return 1; |
---|
289 | |
---|
290 | line = (char*)malloc( len+4+1 ); |
---|
291 | if (!line) |
---|
292 | return 1; |
---|
293 | if ( DEBUG_PROFILE ) |
---|
294 | profile_memory( len+4+1 ); |
---|
295 | |
---|
296 | strncpy( line, "del ", 4 ); |
---|
297 | strncpy( line+4, q, len ); |
---|
298 | line[len+4] = '\0'; |
---|
299 | |
---|
300 | if ( wildcard ) |
---|
301 | result = system( line ); |
---|
302 | else |
---|
303 | result = !DeleteFile( line+4 ); |
---|
304 | |
---|
305 | free( line ); |
---|
306 | if (result) |
---|
307 | return 1; |
---|
308 | |
---|
309 | go_on = 0; |
---|
310 | } |
---|
311 | |
---|
312 | default: |
---|
313 | ; |
---|
314 | } |
---|
315 | p++; |
---|
316 | } /* while (go_on) */ |
---|
317 | } |
---|
318 | } |
---|
319 | } |
---|
320 | |
---|
321 | |
---|
322 | /* |
---|
323 | * onintr() - bump intr to note command interruption |
---|
324 | */ |
---|
325 | |
---|
326 | void |
---|
327 | onintr( int disp ) |
---|
328 | { |
---|
329 | intr++; |
---|
330 | printf( "...interrupted\n" ); |
---|
331 | } |
---|
332 | |
---|
333 | /* |
---|
334 | * can_spawn() - If the command is suitable for execution via spawnvp, |
---|
335 | * return a number >= the number of characters it would occupy on the |
---|
336 | * command-line. Otherwise, return zero. |
---|
337 | */ |
---|
338 | long can_spawn(char* command) |
---|
339 | { |
---|
340 | char *p; |
---|
341 | |
---|
342 | char inquote = 0; |
---|
343 | |
---|
344 | /* Move to the first non-whitespace */ |
---|
345 | command += strspn( command, " \t" ); |
---|
346 | |
---|
347 | p = command; |
---|
348 | |
---|
349 | /* Look for newlines and unquoted i/o redirection */ |
---|
350 | do |
---|
351 | { |
---|
352 | p += strcspn( p, "'\n\"<>|" ); |
---|
353 | |
---|
354 | switch (*p) |
---|
355 | { |
---|
356 | case '\n': |
---|
357 | /* skip over any following spaces */ |
---|
358 | while( isspace( *p ) ) |
---|
359 | ++p; |
---|
360 | /* Must use a .bat file if there is anything significant |
---|
361 | * following the newline |
---|
362 | */ |
---|
363 | if (*p) |
---|
364 | return 0; |
---|
365 | break; |
---|
366 | |
---|
367 | case '"': |
---|
368 | case '\'': |
---|
369 | if (p > command && p[-1] != '\\') |
---|
370 | { |
---|
371 | if (inquote == *p) |
---|
372 | inquote = 0; |
---|
373 | else if (inquote == 0) |
---|
374 | inquote = *p; |
---|
375 | } |
---|
376 | |
---|
377 | ++p; |
---|
378 | break; |
---|
379 | |
---|
380 | case '<': |
---|
381 | case '>': |
---|
382 | case '|': |
---|
383 | if (!inquote) |
---|
384 | return 0; |
---|
385 | ++p; |
---|
386 | break; |
---|
387 | } |
---|
388 | } |
---|
389 | while (*p); |
---|
390 | |
---|
391 | /* Return the number of characters the command will occupy |
---|
392 | */ |
---|
393 | return p - command; |
---|
394 | } |
---|
395 | |
---|
396 | void execnt_unit_test() |
---|
397 | { |
---|
398 | #if !defined(NDEBUG) |
---|
399 | /* vc6 preprocessor is broken, so assert with these strings gets |
---|
400 | * confused. Use a table instead. |
---|
401 | */ |
---|
402 | typedef struct test { char* command; int result; } test; |
---|
403 | test tests[] = { |
---|
404 | { "x", 0 }, |
---|
405 | { "x\n ", 0 }, |
---|
406 | { "x\ny", 1 }, |
---|
407 | { "x\n\n y", 1 }, |
---|
408 | { "echo x > foo.bar", 1 }, |
---|
409 | { "echo x < foo.bar", 1 }, |
---|
410 | { "echo x \">\" foo.bar", 0 }, |
---|
411 | { "echo x \"<\" foo.bar", 0 }, |
---|
412 | { "echo x \\\">\\\" foo.bar", 1 }, |
---|
413 | { "echo x \\\"<\\\" foo.bar", 1 } |
---|
414 | }; |
---|
415 | int i; |
---|
416 | for ( i = 0; i < sizeof(tests)/sizeof(*tests); ++i) |
---|
417 | { |
---|
418 | assert( !can_spawn( tests[i].command ) == tests[i].result ); |
---|
419 | } |
---|
420 | |
---|
421 | { |
---|
422 | char* long_command = malloc(MAXLINE + 10); |
---|
423 | assert( long_command != 0 ); |
---|
424 | memset( long_command, 'x', MAXLINE + 9 ); |
---|
425 | long_command[MAXLINE + 9] = 0; |
---|
426 | assert( can_spawn( long_command ) == MAXLINE + 9); |
---|
427 | free( long_command ); |
---|
428 | } |
---|
429 | |
---|
430 | { |
---|
431 | /* Work around vc6 bug; it doesn't like escaped string |
---|
432 | * literals inside assert |
---|
433 | */ |
---|
434 | char** argv = string_to_args("\"g++\" -c -I\"Foobar\""); |
---|
435 | char const expected[] = "-c -I\"Foobar\""; |
---|
436 | |
---|
437 | assert(!strcmp(argv[0], "g++")); |
---|
438 | assert(!strcmp(argv[1], expected)); |
---|
439 | free_argv(argv); |
---|
440 | } |
---|
441 | #endif |
---|
442 | } |
---|
443 | |
---|
444 | /* 64-bit arithmetic helpers */ |
---|
445 | |
---|
446 | /* Compute the carry bit from the addition of two 32-bit unsigned numbers */ |
---|
447 | #define add_carry_bit(a, b) ( (((a) | (b)) >> 31) & (~((a) + (b)) >> 31) & 0x1 ) |
---|
448 | |
---|
449 | /* Compute the high 32 bits of the addition of two 64-bit unsigned numbers, h1l1 and h2l2 */ |
---|
450 | #define add_64_hi(h1, l1, h2, l2) ((h1) + (h2) + add_carry_bit(l1, l2)) |
---|
451 | |
---|
452 | /* Add two 64-bit unsigned numbers, h1l1 and h2l2 */ |
---|
453 | static FILETIME add_64( |
---|
454 | unsigned long h1, unsigned long l1, |
---|
455 | unsigned long h2, unsigned long l2) |
---|
456 | { |
---|
457 | FILETIME result; |
---|
458 | result.dwLowDateTime = l1 + l2; |
---|
459 | result.dwHighDateTime = add_64_hi(h1, l1, h2, l2); |
---|
460 | |
---|
461 | return result; |
---|
462 | } |
---|
463 | |
---|
464 | static FILETIME add_FILETIME(FILETIME t1, FILETIME t2) |
---|
465 | { |
---|
466 | return add_64( |
---|
467 | t1.dwHighDateTime, t1.dwLowDateTime |
---|
468 | , t2.dwHighDateTime, t2.dwLowDateTime); |
---|
469 | } |
---|
470 | static FILETIME negate_FILETIME(FILETIME t) |
---|
471 | { |
---|
472 | /* 2s complement negation */ |
---|
473 | return add_64(~t.dwHighDateTime, ~t.dwLowDateTime, 0, 1); |
---|
474 | } |
---|
475 | |
---|
476 | /* COnvert a FILETIME to a number of seconds */ |
---|
477 | static double filetime_seconds(FILETIME t) |
---|
478 | { |
---|
479 | return t.dwHighDateTime * (double)(1UL << 31) * 2 + t.dwLowDateTime * 1.0e-7; |
---|
480 | } |
---|
481 | |
---|
482 | static void |
---|
483 | record_times(int pid, timing_info* time) |
---|
484 | { |
---|
485 | FILETIME creation, exit, kernel, user; |
---|
486 | if (GetProcessTimes((HANDLE)pid, &creation, &exit, &kernel, &user)) |
---|
487 | { |
---|
488 | /* Compute the elapsed time */ |
---|
489 | #if 0 /* We don't know how to get this number this on Unix */ |
---|
490 | time->elapsed = filetime_seconds( |
---|
491 | add_FILETIME( exit, negate_FILETIME(creation) ) |
---|
492 | ); |
---|
493 | #endif |
---|
494 | |
---|
495 | time->system = filetime_seconds(kernel); |
---|
496 | time->user = filetime_seconds(user); |
---|
497 | } |
---|
498 | |
---|
499 | CloseHandle((HANDLE)pid); |
---|
500 | } |
---|
501 | |
---|
502 | |
---|
503 | /* |
---|
504 | * execcmd() - launch an async command execution |
---|
505 | */ |
---|
506 | |
---|
507 | void |
---|
508 | execcmd( |
---|
509 | char *string, |
---|
510 | void (*func)( void *closure, int status, timing_info* ), |
---|
511 | void *closure, |
---|
512 | LIST *shell ) |
---|
513 | { |
---|
514 | int pid; |
---|
515 | int slot; |
---|
516 | int raw_cmd = 0 ; |
---|
517 | char *argv_static[ MAXARGC + 1 ]; /* +1 for NULL */ |
---|
518 | char **argv = argv_static; |
---|
519 | char *p; |
---|
520 | |
---|
521 | /* Check to see if we need to hack around the line-length limitation. */ |
---|
522 | /* Look for a JAMSHELL setting of "%", indicating that the command |
---|
523 | * should be invoked directly */ |
---|
524 | if ( shell && !strcmp(shell->string,"%") && !list_next(shell) ) |
---|
525 | { |
---|
526 | raw_cmd = 1; |
---|
527 | shell = 0; |
---|
528 | } |
---|
529 | |
---|
530 | if ( !is_win95_defined ) |
---|
531 | set_is_win95(); |
---|
532 | |
---|
533 | /* Find a slot in the running commands table for this one. */ |
---|
534 | if ( is_win95 ) |
---|
535 | { |
---|
536 | /* only synchronous spans are supported on Windows 95/98 */ |
---|
537 | slot = 0; |
---|
538 | } |
---|
539 | else |
---|
540 | { |
---|
541 | for( slot = 0; slot < MAXJOBS; slot++ ) |
---|
542 | if( !cmdtab[ slot ].pid ) |
---|
543 | break; |
---|
544 | } |
---|
545 | if( slot == MAXJOBS ) |
---|
546 | { |
---|
547 | printf( "no slots for child!\n" ); |
---|
548 | exit( EXITBAD ); |
---|
549 | } |
---|
550 | |
---|
551 | if( !cmdtab[ slot ].tempfile ) |
---|
552 | { |
---|
553 | const char *tempdir = path_tmpdir(); |
---|
554 | DWORD procID = GetCurrentProcessId(); |
---|
555 | |
---|
556 | /* SVA - allocate 64 other just to be safe */ |
---|
557 | cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 64 ); |
---|
558 | if ( DEBUG_PROFILE ) |
---|
559 | profile_memory( strlen( tempdir ) + 64 ); |
---|
560 | |
---|
561 | sprintf( cmdtab[ slot ].tempfile, "%s\\jam%d-%02d.bat", |
---|
562 | tempdir, procID, slot ); |
---|
563 | } |
---|
564 | |
---|
565 | /* Trim leading, ending white space */ |
---|
566 | |
---|
567 | while( isspace( *string ) ) |
---|
568 | ++string; |
---|
569 | |
---|
570 | /* Write to .BAT file unless the line would be too long and it |
---|
571 | * meets the other spawnability criteria. |
---|
572 | */ |
---|
573 | if( raw_cmd && can_spawn( string ) >= MAXLINE ) |
---|
574 | { |
---|
575 | if( DEBUG_EXECCMD ) |
---|
576 | printf("Executing raw command directly\n"); |
---|
577 | } |
---|
578 | else |
---|
579 | { |
---|
580 | FILE *f = 0; |
---|
581 | int tries = 0; |
---|
582 | raw_cmd = 0; |
---|
583 | |
---|
584 | /* Write command to bat file. For some reason this open can |
---|
585 | fails intermitently. But doing some retries works. Most likely |
---|
586 | this is due to a previously existing file of the same name that |
---|
587 | happens to be opened by an active virus scanner. Pointed out, |
---|
588 | and fix by Bronek Kozicki. */ |
---|
589 | for (; !f && tries < 4; ++tries) |
---|
590 | { |
---|
591 | f = fopen( cmdtab[ slot ].tempfile, "w" ); |
---|
592 | if ( !f && tries < 4 ) Sleep( 250 ); |
---|
593 | } |
---|
594 | if (!f) |
---|
595 | { |
---|
596 | printf( "failed to write command file!\n" ); |
---|
597 | exit( EXITBAD ); |
---|
598 | } |
---|
599 | fputs( string, f ); |
---|
600 | fclose( f ); |
---|
601 | |
---|
602 | string = cmdtab[ slot ].tempfile; |
---|
603 | |
---|
604 | if( DEBUG_EXECCMD ) |
---|
605 | { |
---|
606 | if (shell) |
---|
607 | printf("using user-specified shell: %s", shell->string); |
---|
608 | else |
---|
609 | printf("Executing through .bat file\n"); |
---|
610 | } |
---|
611 | } |
---|
612 | |
---|
613 | /* Forumulate argv */ |
---|
614 | /* If shell was defined, be prepared for % and ! subs. */ |
---|
615 | /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */ |
---|
616 | |
---|
617 | if( shell ) |
---|
618 | { |
---|
619 | int i; |
---|
620 | char jobno[4]; |
---|
621 | int gotpercent = 0; |
---|
622 | |
---|
623 | sprintf( jobno, "%d", slot + 1 ); |
---|
624 | |
---|
625 | for( i = 0; shell && i < MAXARGC; i++, shell = list_next( shell ) ) |
---|
626 | { |
---|
627 | switch( shell->string[0] ) |
---|
628 | { |
---|
629 | case '%': argv[i] = string; gotpercent++; break; |
---|
630 | case '!': argv[i] = jobno; break; |
---|
631 | default: argv[i] = shell->string; |
---|
632 | } |
---|
633 | if( DEBUG_EXECCMD ) |
---|
634 | printf( "argv[%d] = '%s'\n", i, argv[i] ); |
---|
635 | } |
---|
636 | |
---|
637 | if( !gotpercent ) |
---|
638 | argv[i++] = string; |
---|
639 | |
---|
640 | argv[i] = 0; |
---|
641 | } |
---|
642 | else if (raw_cmd) |
---|
643 | { |
---|
644 | argv = string_to_args(string); |
---|
645 | } |
---|
646 | else |
---|
647 | { |
---|
648 | /* don't worry, this is ignored on Win95/98, see later.. */ |
---|
649 | argv[0] = "cmd.exe"; |
---|
650 | argv[1] = "/Q/C"; /* anything more is non-portable */ |
---|
651 | argv[2] = string; |
---|
652 | argv[3] = 0; |
---|
653 | } |
---|
654 | |
---|
655 | /* Catch interrupts whenever commands are running. */ |
---|
656 | |
---|
657 | if( !cmdsrunning++ ) |
---|
658 | istat = signal( SIGINT, onintr ); |
---|
659 | |
---|
660 | /* Start the command */ |
---|
661 | |
---|
662 | /* on Win95, we only do a synchronous call */ |
---|
663 | if ( is_win95 ) |
---|
664 | { |
---|
665 | static const char* hard_coded[] = |
---|
666 | { |
---|
667 | "del", "erase", "copy", "mkdir", "rmdir", "cls", "dir", |
---|
668 | "ren", "rename", "move", 0 |
---|
669 | }; |
---|
670 | |
---|
671 | const char** keyword; |
---|
672 | int len, spawn = 1; |
---|
673 | int result; |
---|
674 | timing_info time = {0,0}; |
---|
675 | |
---|
676 | for ( keyword = hard_coded; keyword[0]; keyword++ ) |
---|
677 | { |
---|
678 | len = strlen( keyword[0] ); |
---|
679 | if ( strnicmp( string, keyword[0], len ) == 0 && |
---|
680 | !isalnum(string[len]) ) |
---|
681 | { |
---|
682 | /* this is one of the hard coded symbols, use 'system' to run */ |
---|
683 | /* them.. except for "del"/"erase" */ |
---|
684 | if ( keyword - hard_coded < 2 ) |
---|
685 | result = process_del( string ); |
---|
686 | else |
---|
687 | result = system( string ); |
---|
688 | |
---|
689 | spawn = 0; |
---|
690 | break; |
---|
691 | } |
---|
692 | } |
---|
693 | |
---|
694 | if (spawn) |
---|
695 | { |
---|
696 | char** args; |
---|
697 | |
---|
698 | /* convert the string into an array of arguments */ |
---|
699 | /* we need to take care of double quotes !! */ |
---|
700 | args = string_to_args( string ); |
---|
701 | if ( args ) |
---|
702 | { |
---|
703 | #if 0 |
---|
704 | char** arg; |
---|
705 | fprintf( stderr, "%s: ", args[0] ); |
---|
706 | arg = args+1; |
---|
707 | while ( arg[0] ) |
---|
708 | { |
---|
709 | fprintf( stderr, " {%s}", arg[0] ); |
---|
710 | arg++; |
---|
711 | } |
---|
712 | fprintf( stderr, "\n" ); |
---|
713 | #endif |
---|
714 | result = spawnvp( P_WAIT, args[0], args ); |
---|
715 | record_times(result, &time); |
---|
716 | free_argv( args ); |
---|
717 | } |
---|
718 | else |
---|
719 | result = 1; |
---|
720 | } |
---|
721 | func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK, &time ); |
---|
722 | return; |
---|
723 | } |
---|
724 | |
---|
725 | if( DEBUG_EXECCMD ) |
---|
726 | { |
---|
727 | char **argp = argv; |
---|
728 | |
---|
729 | printf("Executing command"); |
---|
730 | while(*argp != 0) |
---|
731 | { |
---|
732 | printf(" [%s]", *argp); |
---|
733 | argp++; |
---|
734 | } |
---|
735 | printf("\n"); |
---|
736 | } |
---|
737 | |
---|
738 | /* the rest is for Windows NT only */ |
---|
739 | /* spawn doesn't like quotes around the command name */ |
---|
740 | if ( argv[0][0] == '"') |
---|
741 | { |
---|
742 | int l = strlen(argv[0]); |
---|
743 | |
---|
744 | /* Clobber any closing quote, shortening the string by one |
---|
745 | * element */ |
---|
746 | if (argv[0][l-1] == '"') |
---|
747 | argv[0][l-1] = '\0'; |
---|
748 | |
---|
749 | /* Move everything *including* the original terminating zero |
---|
750 | * back one place in memory, covering up the opening quote */ |
---|
751 | memmove(argv[0],argv[0]+1,l); |
---|
752 | } |
---|
753 | if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 ) |
---|
754 | { |
---|
755 | perror( "spawn" ); |
---|
756 | exit( EXITBAD ); |
---|
757 | } |
---|
758 | /* Save the operation for execwait() to find. */ |
---|
759 | |
---|
760 | cmdtab[ slot ].pid = pid; |
---|
761 | cmdtab[ slot ].func = func; |
---|
762 | cmdtab[ slot ].closure = closure; |
---|
763 | |
---|
764 | /* Wait until we're under the limit of concurrent commands. */ |
---|
765 | /* Don't trust globs.jobs alone. */ |
---|
766 | |
---|
767 | while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs ) |
---|
768 | if( !execwait() ) |
---|
769 | break; |
---|
770 | |
---|
771 | if (argv != argv_static) |
---|
772 | { |
---|
773 | free_argv(argv); |
---|
774 | } |
---|
775 | } |
---|
776 | |
---|
777 | /* |
---|
778 | * execwait() - wait and drive at most one execution completion |
---|
779 | */ |
---|
780 | |
---|
781 | int |
---|
782 | execwait() |
---|
783 | { |
---|
784 | int i; |
---|
785 | int status, w; |
---|
786 | int rstat; |
---|
787 | timing_info time; |
---|
788 | |
---|
789 | /* Handle naive make1() which doesn't know if cmds are running. */ |
---|
790 | |
---|
791 | if( !cmdsrunning ) |
---|
792 | return 0; |
---|
793 | |
---|
794 | if ( is_win95 ) |
---|
795 | return 0; |
---|
796 | |
---|
797 | /* Pick up process pid and status */ |
---|
798 | |
---|
799 | while( ( w = wait( &status ) ) == -1 && errno == EINTR ) |
---|
800 | ; |
---|
801 | |
---|
802 | if( w == -1 ) |
---|
803 | { |
---|
804 | printf( "child process(es) lost!\n" ); |
---|
805 | perror("wait"); |
---|
806 | exit( EXITBAD ); |
---|
807 | } |
---|
808 | |
---|
809 | /* Find the process in the cmdtab. */ |
---|
810 | |
---|
811 | for( i = 0; i < MAXJOBS; i++ ) |
---|
812 | if( w == cmdtab[ i ].pid ) |
---|
813 | break; |
---|
814 | |
---|
815 | if( i == MAXJOBS ) |
---|
816 | { |
---|
817 | printf( "waif child found!\n" ); |
---|
818 | exit( EXITBAD ); |
---|
819 | } |
---|
820 | |
---|
821 | record_times(cmdtab[i].pid, &time); |
---|
822 | |
---|
823 | /* Clear the temp file */ |
---|
824 | if ( cmdtab[i].tempfile ) |
---|
825 | unlink( cmdtab[ i ].tempfile ); |
---|
826 | |
---|
827 | /* Drive the completion */ |
---|
828 | |
---|
829 | if( !--cmdsrunning ) |
---|
830 | signal( SIGINT, istat ); |
---|
831 | |
---|
832 | if( intr ) |
---|
833 | rstat = EXEC_CMD_INTR; |
---|
834 | else if( w == -1 || status != 0 ) |
---|
835 | rstat = EXEC_CMD_FAIL; |
---|
836 | else |
---|
837 | rstat = EXEC_CMD_OK; |
---|
838 | |
---|
839 | cmdtab[ i ].pid = 0; |
---|
840 | /* SVA don't leak temp files */ |
---|
841 | if(cmdtab[i].tempfile != NULL) |
---|
842 | { |
---|
843 | free(cmdtab[i].tempfile); |
---|
844 | cmdtab[i].tempfile = NULL; |
---|
845 | } |
---|
846 | (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time ); |
---|
847 | |
---|
848 | return 1; |
---|
849 | } |
---|
850 | |
---|
851 | # if !defined( __BORLANDC__ ) |
---|
852 | |
---|
853 | /* The possible result codes from check_process_exit, below */ |
---|
854 | typedef enum { process_error, process_active, process_finished } process_state; |
---|
855 | |
---|
856 | /* Helper for my_wait() below. Checks to see whether the process has |
---|
857 | * exited and if so, records timing information. |
---|
858 | */ |
---|
859 | static process_state |
---|
860 | check_process_exit( |
---|
861 | HANDLE process /* The process we're looking at */ |
---|
862 | |
---|
863 | , int* status /* Storage for the finished process' exit |
---|
864 | * code. If the process is still active |
---|
865 | * this location is left untouched. */ |
---|
866 | |
---|
867 | , HANDLE* active_handles /* Storage for the process handle if it is |
---|
868 | * found to be still active, or NULL. The |
---|
869 | * process is treated as though it is |
---|
870 | * complete. */ |
---|
871 | |
---|
872 | , int* num_active /* The current length of active_handles */ |
---|
873 | ) |
---|
874 | { |
---|
875 | DWORD exitcode; |
---|
876 | process_state result; |
---|
877 | |
---|
878 | /* Try to get the process exit code */ |
---|
879 | if (!GetExitCodeProcess(process, &exitcode)) |
---|
880 | { |
---|
881 | result = process_error; /* signal an error */ |
---|
882 | } |
---|
883 | else if ( |
---|
884 | exitcode == STILL_ACTIVE /* If the process is still active */ |
---|
885 | && active_handles != 0 /* and we've been passed a place to buffer it */ |
---|
886 | ) |
---|
887 | { |
---|
888 | active_handles[(*num_active)++] = process; /* push it onto the active stack */ |
---|
889 | result = process_active; |
---|
890 | } |
---|
891 | else |
---|
892 | { |
---|
893 | *status = (int)((exitcode & 0xff) << 8); |
---|
894 | result = process_finished; |
---|
895 | } |
---|
896 | |
---|
897 | return result; |
---|
898 | } |
---|
899 | |
---|
900 | static double |
---|
901 | running_time(HANDLE process) |
---|
902 | { |
---|
903 | FILETIME creation, exit, kernel, user, current; |
---|
904 | if (GetProcessTimes(process, &creation, &exit, &kernel, &user)) |
---|
905 | { |
---|
906 | /* Compute the elapsed time */ |
---|
907 | GetSystemTimeAsFileTime(¤t); |
---|
908 | { |
---|
909 | double delta = filetime_seconds( |
---|
910 | add_FILETIME( current, negate_FILETIME(creation) ) |
---|
911 | ); |
---|
912 | return delta; |
---|
913 | } |
---|
914 | } |
---|
915 | return 0.0; |
---|
916 | } |
---|
917 | |
---|
918 | static double |
---|
919 | creation_time(HANDLE process) |
---|
920 | { |
---|
921 | FILETIME creation, exit, kernel, user, current; |
---|
922 | if (GetProcessTimes(process, &creation, &exit, &kernel, &user)) |
---|
923 | { |
---|
924 | return filetime_seconds(creation); |
---|
925 | } |
---|
926 | return 0.0; |
---|
927 | } |
---|
928 | |
---|
929 | /* it's just stupidly silly that one has to do this! */ |
---|
930 | typedef struct PROCESS_BASIC_INFORMATION__ { |
---|
931 | LONG ExitStatus; |
---|
932 | PVOID PebBaseAddress; |
---|
933 | ULONG AffinityMask; |
---|
934 | LONG BasePriority; |
---|
935 | ULONG UniqueProcessId; |
---|
936 | ULONG InheritedFromUniqueProcessId; |
---|
937 | } PROCESS_BASIC_INFORMATION_; |
---|
938 | typedef LONG (__stdcall * NtQueryInformationProcess__)( |
---|
939 | HANDLE ProcessHandle, |
---|
940 | LONG ProcessInformationClass, |
---|
941 | PVOID ProcessInformation, |
---|
942 | ULONG ProcessInformationLength, |
---|
943 | PULONG ReturnLength); |
---|
944 | static NtQueryInformationProcess__ NtQueryInformationProcess_ = NULL; |
---|
945 | static HMODULE NTDLL_ = NULL; |
---|
946 | DWORD get_process_id(HANDLE process) |
---|
947 | { |
---|
948 | PROCESS_BASIC_INFORMATION_ pinfo; |
---|
949 | if ( ! NtQueryInformationProcess_ ) |
---|
950 | { |
---|
951 | if ( ! NTDLL_ ) |
---|
952 | { |
---|
953 | NTDLL_ = GetModuleHandleA("ntdll"); |
---|
954 | } |
---|
955 | if ( NTDLL_ ) |
---|
956 | { |
---|
957 | NtQueryInformationProcess_ |
---|
958 | = (NtQueryInformationProcess__)GetProcAddress( NTDLL_,"NtQueryInformationProcess" ); |
---|
959 | } |
---|
960 | } |
---|
961 | if ( NtQueryInformationProcess_ ) |
---|
962 | { |
---|
963 | LONG r = (*NtQueryInformationProcess_)( |
---|
964 | process,/* ProcessBasicInformation == */ 0,&pinfo,sizeof(PROCESS_BASIC_INFORMATION_),NULL); |
---|
965 | return pinfo.UniqueProcessId; |
---|
966 | } |
---|
967 | else |
---|
968 | { |
---|
969 | return 0; |
---|
970 | } |
---|
971 | } |
---|
972 | |
---|
973 | /* not really optimal, or efficient, but it's easier this way, and it's not |
---|
974 | like we are going to be killing thousands, or even tens or processes. */ |
---|
975 | static void |
---|
976 | kill_all(DWORD pid, HANDLE process) |
---|
977 | { |
---|
978 | HANDLE process_snapshot_h = INVALID_HANDLE_VALUE; |
---|
979 | if ( !pid ) |
---|
980 | { |
---|
981 | pid = get_process_id(process); |
---|
982 | } |
---|
983 | process_snapshot_h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); |
---|
984 | |
---|
985 | if (INVALID_HANDLE_VALUE != process_snapshot_h) |
---|
986 | { |
---|
987 | BOOL ok = TRUE; |
---|
988 | PROCESSENTRY32 pinfo; |
---|
989 | pinfo.dwSize = sizeof(PROCESSENTRY32); |
---|
990 | for ( |
---|
991 | ok = Process32First(process_snapshot_h,&pinfo); |
---|
992 | TRUE == ok; |
---|
993 | ok = Process32Next(process_snapshot_h,&pinfo) ) |
---|
994 | { |
---|
995 | if (pinfo.th32ParentProcessID == pid) |
---|
996 | { |
---|
997 | /* found a child, recurse to kill it and anything else below it */ |
---|
998 | HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pinfo.th32ProcessID); |
---|
999 | if (NULL != ph) |
---|
1000 | { |
---|
1001 | kill_all(pinfo.th32ProcessID,ph); |
---|
1002 | CloseHandle(ph); |
---|
1003 | } |
---|
1004 | } |
---|
1005 | } |
---|
1006 | CloseHandle(process_snapshot_h); |
---|
1007 | } |
---|
1008 | /* now that the children are all dead, kill the root */ |
---|
1009 | TerminateProcess(process,-2); |
---|
1010 | } |
---|
1011 | |
---|
1012 | /* Recursive check if first process is parent (directly or indirectly) of |
---|
1013 | the second one. Both processes are passed as process ids, not handles. |
---|
1014 | Special return value 2 means that the second process is smss.exe and its |
---|
1015 | parent process is System (first argument is ignored) */ |
---|
1016 | static int |
---|
1017 | is_parent_child(DWORD parent, DWORD child) |
---|
1018 | { |
---|
1019 | HANDLE process_snapshot_h = INVALID_HANDLE_VALUE; |
---|
1020 | |
---|
1021 | if (!child) |
---|
1022 | return 0; |
---|
1023 | if (parent == child) |
---|
1024 | return 1; |
---|
1025 | |
---|
1026 | process_snapshot_h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); |
---|
1027 | if (INVALID_HANDLE_VALUE != process_snapshot_h) |
---|
1028 | { |
---|
1029 | BOOL ok = TRUE; |
---|
1030 | PROCESSENTRY32 pinfo; |
---|
1031 | pinfo.dwSize = sizeof(PROCESSENTRY32); |
---|
1032 | for ( |
---|
1033 | ok = Process32First(process_snapshot_h, &pinfo); |
---|
1034 | ok == TRUE; |
---|
1035 | ok = Process32Next(process_snapshot_h, &pinfo) ) |
---|
1036 | { |
---|
1037 | if (pinfo.th32ProcessID == child) |
---|
1038 | { |
---|
1039 | /* |
---|
1040 | Unfortunately, process ids are not really unique. There might |
---|
1041 | be spurious "parent and child" relationship match between |
---|
1042 | two non-related processes if real parent process of a given |
---|
1043 | process has exited (while child process kept running as an |
---|
1044 | "orphan") and the process id of such parent process has been |
---|
1045 | reused by internals of the operating system when creating |
---|
1046 | another process. Thus additional check is needed - process |
---|
1047 | creation time. This check may fail (ie. return 0) for system |
---|
1048 | processes due to insufficient privileges, and that's OK. */ |
---|
1049 | double tchild = 0.0; |
---|
1050 | double tparent = 0.0; |
---|
1051 | HANDLE hchild = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ProcessID); |
---|
1052 | |
---|
1053 | CloseHandle(process_snapshot_h); |
---|
1054 | |
---|
1055 | /* csrss.exe may display message box like following: |
---|
1056 | xyz.exe - Unable To Locate Component |
---|
1057 | This application has failed to start because |
---|
1058 | boost_foo-bar.dll was not found. Re-installing the |
---|
1059 | application may fix the problem |
---|
1060 | This actually happens when starting test process that depends |
---|
1061 | on a dynamic library which failed to build. We want to |
---|
1062 | automatically close these message boxes even though csrss.exe |
---|
1063 | is not our child process. We may depend on the fact that (in |
---|
1064 | all current versions of Windows) csrss.exe is directly |
---|
1065 | child of smss.exe process, which in turn is directly child of |
---|
1066 | System process, which always has process id == 4 . |
---|
1067 | This check must be performed before comparison of process |
---|
1068 | creation time */ |
---|
1069 | if (stricmp(pinfo.szExeFile, "csrss.exe") == 0 |
---|
1070 | && is_parent_child(parent, pinfo.th32ParentProcessID) == 2) |
---|
1071 | { |
---|
1072 | return 1; |
---|
1073 | } |
---|
1074 | else if (stricmp(pinfo.szExeFile, "smss.exe") == 0 |
---|
1075 | && pinfo.th32ParentProcessID == 4) |
---|
1076 | { |
---|
1077 | return 2; |
---|
1078 | } |
---|
1079 | |
---|
1080 | if (hchild != 0) |
---|
1081 | { |
---|
1082 | HANDLE hparent = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pinfo.th32ParentProcessID); |
---|
1083 | if (hparent != 0) |
---|
1084 | { |
---|
1085 | tchild = creation_time(hchild); |
---|
1086 | tparent = creation_time(hparent); |
---|
1087 | |
---|
1088 | CloseHandle(hparent); |
---|
1089 | } |
---|
1090 | CloseHandle(hchild); |
---|
1091 | } |
---|
1092 | |
---|
1093 | /* return 0 if one of the following is true: |
---|
1094 | 1. we failed to read process creation time |
---|
1095 | 2. child was created before alleged parent */ |
---|
1096 | if (tchild == 0.0 || tparent == 0.0 || tchild < tparent) |
---|
1097 | return 0; |
---|
1098 | |
---|
1099 | return is_parent_child(parent, pinfo.th32ParentProcessID) & 1; |
---|
1100 | } |
---|
1101 | } |
---|
1102 | |
---|
1103 | CloseHandle(process_snapshot_h); |
---|
1104 | } |
---|
1105 | |
---|
1106 | return 0; |
---|
1107 | } |
---|
1108 | |
---|
1109 | typedef struct PROCESS_HANDLE_ID {HANDLE h; DWORD pid;} PROCESS_HANDLE_ID; |
---|
1110 | |
---|
1111 | /* This function is called by the operating system for each topmost window. */ |
---|
1112 | BOOL CALLBACK |
---|
1113 | window_enum(HWND hwnd, LPARAM lParam) |
---|
1114 | { |
---|
1115 | char buf[7] = {0}; |
---|
1116 | PROCESS_HANDLE_ID p = *((PROCESS_HANDLE_ID*) (lParam)); |
---|
1117 | DWORD pid = 0; |
---|
1118 | DWORD tid = 0; |
---|
1119 | |
---|
1120 | /* we want to find and close any window that: |
---|
1121 | 1. is visible and |
---|
1122 | 2. is a dialog and |
---|
1123 | 3. is displayed by any of our child processes */ |
---|
1124 | if (!IsWindowVisible(hwnd)) |
---|
1125 | return TRUE; |
---|
1126 | |
---|
1127 | if (!GetClassNameA(hwnd, buf, sizeof(buf))) |
---|
1128 | return TRUE; /* failed to read class name; presume it's not a dialog */ |
---|
1129 | |
---|
1130 | if (strcmp(buf, "#32770") != 0) |
---|
1131 | return TRUE; /* not a dialog */ |
---|
1132 | |
---|
1133 | /* GetWindowThreadProcessId returns 0 on error, otherwise thread id |
---|
1134 | of window message pump thread */ |
---|
1135 | tid = GetWindowThreadProcessId(hwnd, &pid); |
---|
1136 | |
---|
1137 | if (tid && is_parent_child(p.pid, pid)) |
---|
1138 | { |
---|
1139 | /* ask really nice */ |
---|
1140 | PostMessageA(hwnd, WM_CLOSE, 0, 0); |
---|
1141 | /* now wait and see if it worked. If not, insist */ |
---|
1142 | if (WaitForSingleObject(p.h, 200) == WAIT_TIMEOUT) |
---|
1143 | { |
---|
1144 | PostThreadMessageA(tid, WM_QUIT, 0, 0); |
---|
1145 | WaitForSingleObject(p.h, 300); |
---|
1146 | } |
---|
1147 | |
---|
1148 | /* done, we do not want to check any other window now */ |
---|
1149 | return FALSE; |
---|
1150 | } |
---|
1151 | |
---|
1152 | return TRUE; |
---|
1153 | } |
---|
1154 | |
---|
1155 | static void |
---|
1156 | close_alert(HANDLE process) |
---|
1157 | { |
---|
1158 | DWORD pid = get_process_id(process); |
---|
1159 | /* If process already exited or we just cannot get its process id, do not |
---|
1160 | go any further */ |
---|
1161 | if (pid) |
---|
1162 | { |
---|
1163 | PROCESS_HANDLE_ID p = {process, pid}; |
---|
1164 | EnumWindows(&window_enum, (LPARAM) &p); |
---|
1165 | } |
---|
1166 | } |
---|
1167 | |
---|
1168 | static int |
---|
1169 | my_wait( int *status ) |
---|
1170 | { |
---|
1171 | int i, num_active = 0; |
---|
1172 | DWORD exitcode, waitcode; |
---|
1173 | HANDLE active_handles[MAXJOBS]; |
---|
1174 | |
---|
1175 | /* first see if any non-waited-for processes are dead, |
---|
1176 | * and return if so. |
---|
1177 | */ |
---|
1178 | for ( i = 0; i < globs.jobs; i++ ) |
---|
1179 | { |
---|
1180 | int pid = cmdtab[i].pid; |
---|
1181 | |
---|
1182 | if ( pid ) |
---|
1183 | { |
---|
1184 | process_state state |
---|
1185 | = check_process_exit((HANDLE)pid, status, active_handles, &num_active); |
---|
1186 | |
---|
1187 | if ( state == process_error ) |
---|
1188 | goto FAILED; |
---|
1189 | else if ( state == process_finished ) |
---|
1190 | return pid; |
---|
1191 | } |
---|
1192 | } |
---|
1193 | |
---|
1194 | /* if a child exists, wait for it to die */ |
---|
1195 | if ( !num_active ) |
---|
1196 | { |
---|
1197 | errno = ECHILD; |
---|
1198 | return -1; |
---|
1199 | } |
---|
1200 | |
---|
1201 | if ( globs.timeout > 0 ) |
---|
1202 | { |
---|
1203 | unsigned int alert_wait = 1; |
---|
1204 | /* with a timeout we wait for a finish or a timeout, we check every second |
---|
1205 | to see if something timed out */ |
---|
1206 | for (waitcode = WAIT_TIMEOUT; waitcode == WAIT_TIMEOUT; ++alert_wait) |
---|
1207 | { |
---|
1208 | waitcode = WaitForMultipleObjects( num_active, active_handles, FALSE, 1*1000 /* 1 second */ ); |
---|
1209 | if ( waitcode == WAIT_TIMEOUT ) |
---|
1210 | { |
---|
1211 | /* check if any jobs have surpassed the maximum run time. */ |
---|
1212 | for ( i = 0; i < num_active; ++i ) |
---|
1213 | { |
---|
1214 | double t = running_time(active_handles[i]); |
---|
1215 | |
---|
1216 | /* periodically (each 5 secs) check and close message boxes |
---|
1217 | displayed by any of our child processes */ |
---|
1218 | if ((alert_wait % ((unsigned int) 5)) == 0) |
---|
1219 | close_alert(active_handles[i]); |
---|
1220 | |
---|
1221 | if ( t > (double)globs.timeout ) |
---|
1222 | { |
---|
1223 | /* the job may have left an alert dialog around, |
---|
1224 | try and get rid of it before killing */ |
---|
1225 | close_alert(active_handles[i]); |
---|
1226 | /* we have a "runaway" job, kill it */ |
---|
1227 | kill_all(0,active_handles[i]); |
---|
1228 | /* indicate the job "finished" so we query its status below */ |
---|
1229 | waitcode = WAIT_ABANDONED_0+i; |
---|
1230 | } |
---|
1231 | } |
---|
1232 | } |
---|
1233 | } |
---|
1234 | } |
---|
1235 | else |
---|
1236 | { |
---|
1237 | /* no timeout, so just wait indefinately for something to finish */ |
---|
1238 | waitcode = WaitForMultipleObjects( num_active, active_handles, FALSE, INFINITE ); |
---|
1239 | } |
---|
1240 | if ( waitcode != WAIT_FAILED ) |
---|
1241 | { |
---|
1242 | if ( waitcode >= WAIT_ABANDONED_0 |
---|
1243 | && waitcode < WAIT_ABANDONED_0 + num_active ) |
---|
1244 | i = waitcode - WAIT_ABANDONED_0; |
---|
1245 | else |
---|
1246 | i = waitcode - WAIT_OBJECT_0; |
---|
1247 | |
---|
1248 | if ( check_process_exit(active_handles[i], status, 0, 0) == process_finished ) |
---|
1249 | return (int)active_handles[i]; |
---|
1250 | } |
---|
1251 | |
---|
1252 | FAILED: |
---|
1253 | errno = GetLastError(); |
---|
1254 | return -1; |
---|
1255 | |
---|
1256 | } |
---|
1257 | |
---|
1258 | # endif /* !__BORLANDC__ */ |
---|
1259 | |
---|
1260 | # endif /* USE_EXECNT */ |
---|