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 <errno.h> |
---|
17 | # include <assert.h> |
---|
18 | # include <ctype.h> |
---|
19 | # include <time.h> |
---|
20 | |
---|
21 | # ifdef USE_EXECNT |
---|
22 | |
---|
23 | # define WIN32_LEAN_AND_MEAN |
---|
24 | # include <windows.h> /* do the ugly deed */ |
---|
25 | # include <process.h> |
---|
26 | |
---|
27 | # if !defined( __BORLANDC__ ) && !defined( OS_OS2 ) |
---|
28 | # define wait my_wait |
---|
29 | static int my_wait( int *status ); |
---|
30 | # endif |
---|
31 | |
---|
32 | /* |
---|
33 | * execnt.c - execute a shell command on Windows NT and Windows 95/98 |
---|
34 | * |
---|
35 | * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp(). |
---|
36 | * The default is: |
---|
37 | * |
---|
38 | * /bin/sh -c % [ on UNIX/AmigaOS ] |
---|
39 | * cmd.exe /c % [ on Windows NT ] |
---|
40 | * |
---|
41 | * Each word must be an individual element in a jam variable value. |
---|
42 | * |
---|
43 | * In $(JAMSHELL), % expands to the command string and ! expands to |
---|
44 | * the slot number (starting at 1) for multiprocess (-j) invocations. |
---|
45 | * If $(JAMSHELL) doesn't include a %, it is tacked on as the last |
---|
46 | * argument. |
---|
47 | * |
---|
48 | * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work! |
---|
49 | * |
---|
50 | * External routines: |
---|
51 | * execcmd() - launch an async command execution |
---|
52 | * execwait() - wait and drive at most one execution completion |
---|
53 | * |
---|
54 | * Internal routines: |
---|
55 | * onintr() - bump intr to note command interruption |
---|
56 | * |
---|
57 | * 04/08/94 (seiwald) - Coherent/386 support added. |
---|
58 | * 05/04/94 (seiwald) - async multiprocess interface |
---|
59 | * 01/22/95 (seiwald) - $(JAMSHELL) support |
---|
60 | * 06/02/97 (gsar) - full async multiprocess support for Win32 |
---|
61 | */ |
---|
62 | |
---|
63 | static int intr = 0; |
---|
64 | static int cmdsrunning = 0; |
---|
65 | static void (*istat)( int ); |
---|
66 | |
---|
67 | static int is_nt_351 = 0; |
---|
68 | static int is_win95 = 1; |
---|
69 | static int is_win95_defined = 0; |
---|
70 | |
---|
71 | |
---|
72 | static struct |
---|
73 | { |
---|
74 | int pid; /* on win32, a real process handle */ |
---|
75 | void (*func)( void *closure, int status, timing_info* ); |
---|
76 | void *closure; |
---|
77 | char *tempfile; |
---|
78 | |
---|
79 | } cmdtab[ MAXJOBS ] = {{0}}; |
---|
80 | |
---|
81 | |
---|
82 | static void |
---|
83 | set_is_win95( void ) |
---|
84 | { |
---|
85 | OSVERSIONINFO os_info; |
---|
86 | |
---|
87 | os_info.dwOSVersionInfoSize = sizeof(os_info); |
---|
88 | os_info.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; |
---|
89 | GetVersionEx( &os_info ); |
---|
90 | |
---|
91 | is_win95 = (os_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); |
---|
92 | is_win95_defined = 1; |
---|
93 | |
---|
94 | /* now, test wether we're running Windows 3.51 */ |
---|
95 | /* this is later used to limit the system call command length */ |
---|
96 | if (os_info.dwPlatformId == VER_PLATFORM_WIN32_NT) |
---|
97 | is_nt_351 = os_info.dwMajorVersion == 3; |
---|
98 | } |
---|
99 | |
---|
100 | int maxline() |
---|
101 | { |
---|
102 | if (!is_win95_defined) |
---|
103 | set_is_win95(); |
---|
104 | |
---|
105 | /* Set the maximum command line length according to the OS */ |
---|
106 | return is_nt_351 ? 996 |
---|
107 | : is_win95 ? 1023 |
---|
108 | : 2047; |
---|
109 | } |
---|
110 | |
---|
111 | static void |
---|
112 | free_argv( char** args ) |
---|
113 | { |
---|
114 | free( args[0] ); |
---|
115 | free( args ); |
---|
116 | } |
---|
117 | |
---|
118 | /* Convert a command string into arguments for spawnvp. The original |
---|
119 | * code, inherited from ftjam, tried to break up every argument on the |
---|
120 | * command-line, dealing with quotes, but that's really a waste of |
---|
121 | * time on Win32, at least. It turns out that all you need to do is |
---|
122 | * get the raw path to the executable in the first argument to |
---|
123 | * spawnvp, and you can pass all the rest of the command-line |
---|
124 | * arguments to spawnvp in one, un-processed string. |
---|
125 | * |
---|
126 | * New strategy: break the string in at most one place. |
---|
127 | */ |
---|
128 | static char** |
---|
129 | string_to_args( const char* string ) |
---|
130 | { |
---|
131 | int src_len; |
---|
132 | int in_quote; |
---|
133 | char* line; |
---|
134 | char const* src; |
---|
135 | char* dst; |
---|
136 | char** argv; |
---|
137 | |
---|
138 | /* drop leading and trailing whitespace if any */ |
---|
139 | while (isspace(*string)) |
---|
140 | ++string; |
---|
141 | |
---|
142 | src_len = strlen( string ); |
---|
143 | while ( src_len > 0 && isspace( string[src_len - 1] ) ) |
---|
144 | --src_len; |
---|
145 | |
---|
146 | /* Copy the input string into a buffer we can modify |
---|
147 | */ |
---|
148 | line = (char*)malloc( src_len+1 ); |
---|
149 | if (!line) |
---|
150 | return 0; |
---|
151 | |
---|
152 | /* allocate the argv array. |
---|
153 | * element 0: stores the path to the executable |
---|
154 | * element 1: stores the command-line arguments to the executable |
---|
155 | * element 2: NULL terminator |
---|
156 | */ |
---|
157 | argv = (char**)malloc( 3 * sizeof(char*) ); |
---|
158 | if (!argv) |
---|
159 | { |
---|
160 | free( line ); |
---|
161 | return 0; |
---|
162 | } |
---|
163 | |
---|
164 | /* Strip quotes from the first command-line argument and find |
---|
165 | * where it ends. Quotes are illegal in Win32 pathnames, so we |
---|
166 | * don't need to worry about preserving escaped quotes here. |
---|
167 | * Spaces can't be escaped in Win32, only enclosed in quotes, so |
---|
168 | * removing backslash escapes is also a non-issue. |
---|
169 | */ |
---|
170 | in_quote = 0; |
---|
171 | for ( src = string, dst = line ; *src; src++ ) |
---|
172 | { |
---|
173 | if (*src == '"') |
---|
174 | in_quote = !in_quote; |
---|
175 | else if (!in_quote && isspace(*src)) |
---|
176 | break; |
---|
177 | else |
---|
178 | *dst++ = *src; |
---|
179 | } |
---|
180 | *dst++ = 0; |
---|
181 | argv[0] = line; |
---|
182 | |
---|
183 | /* skip whitespace in src */ |
---|
184 | while (isspace(*src)) |
---|
185 | ++src; |
---|
186 | |
---|
187 | argv[1] = dst; |
---|
188 | |
---|
189 | /* Copy the rest of the arguments verbatim */ |
---|
190 | |
---|
191 | src_len -= src - string; |
---|
192 | |
---|
193 | /* Use strncat because it appends a trailing nul */ |
---|
194 | *dst = 0; |
---|
195 | strncat(dst, src, src_len); |
---|
196 | |
---|
197 | argv[2] = 0; |
---|
198 | |
---|
199 | return argv; |
---|
200 | } |
---|
201 | |
---|
202 | |
---|
203 | |
---|
204 | /* process a "del" or "erase" command under Windows 95/98 */ |
---|
205 | static int |
---|
206 | process_del( char* command ) |
---|
207 | { |
---|
208 | char** arg; |
---|
209 | char* p = command, *q; |
---|
210 | int wildcard = 0, result = 0; |
---|
211 | |
---|
212 | /* first of all, skip the command itself */ |
---|
213 | if ( p[0] == 'd' ) |
---|
214 | p += 3; /* assumes "del..;" */ |
---|
215 | else if ( p[0] == 'e' ) |
---|
216 | p += 5; /* assumes "erase.." */ |
---|
217 | else |
---|
218 | return 1; /* invalid command */ |
---|
219 | |
---|
220 | /* process all targets independently */ |
---|
221 | for (;;) |
---|
222 | { |
---|
223 | /* skip leading spaces */ |
---|
224 | while ( *p && isspace(*p) ) |
---|
225 | p++; |
---|
226 | |
---|
227 | /* exit if we encounter an end of string */ |
---|
228 | if (!*p) |
---|
229 | return 0; |
---|
230 | |
---|
231 | /* ignore toggles/flags */ |
---|
232 | while (*p == '/') |
---|
233 | { |
---|
234 | p++; |
---|
235 | while ( *p && isalnum(*p) ) |
---|
236 | p++; |
---|
237 | while (*p && isspace(*p) ) |
---|
238 | ++p; |
---|
239 | } |
---|
240 | |
---|
241 | |
---|
242 | { |
---|
243 | int in_quote = 0; |
---|
244 | int wildcard = 0; |
---|
245 | int go_on = 1; |
---|
246 | |
---|
247 | q = p; |
---|
248 | while (go_on) |
---|
249 | { |
---|
250 | switch (*p) |
---|
251 | { |
---|
252 | case '"': |
---|
253 | in_quote = !in_quote; |
---|
254 | break; |
---|
255 | |
---|
256 | case '?': |
---|
257 | case '*': |
---|
258 | if (!in_quote) |
---|
259 | wildcard = 1; |
---|
260 | break; |
---|
261 | |
---|
262 | case '\0': |
---|
263 | if (in_quote) |
---|
264 | return 1; |
---|
265 | /* fall-through */ |
---|
266 | |
---|
267 | case ' ': |
---|
268 | case '\t': |
---|
269 | if (!in_quote) |
---|
270 | { |
---|
271 | int len = p - q; |
---|
272 | int result; |
---|
273 | char* line; |
---|
274 | |
---|
275 | /* q..p-1 contains the delete argument */ |
---|
276 | if ( len <= 0 ) |
---|
277 | return 1; |
---|
278 | |
---|
279 | line = (char*)malloc( len+4+1 ); |
---|
280 | if (!line) |
---|
281 | return 1; |
---|
282 | |
---|
283 | strncpy( line, "del ", 4 ); |
---|
284 | strncpy( line+4, q, len ); |
---|
285 | line[len+4] = '\0'; |
---|
286 | |
---|
287 | if ( wildcard ) |
---|
288 | result = system( line ); |
---|
289 | else |
---|
290 | result = !DeleteFile( line+4 ); |
---|
291 | |
---|
292 | free( line ); |
---|
293 | if (result) |
---|
294 | return 1; |
---|
295 | |
---|
296 | go_on = 0; |
---|
297 | } |
---|
298 | |
---|
299 | default: |
---|
300 | ; |
---|
301 | } |
---|
302 | p++; |
---|
303 | } /* while (go_on) */ |
---|
304 | } |
---|
305 | } |
---|
306 | } |
---|
307 | |
---|
308 | |
---|
309 | /* |
---|
310 | * onintr() - bump intr to note command interruption |
---|
311 | */ |
---|
312 | |
---|
313 | void |
---|
314 | onintr( int disp ) |
---|
315 | { |
---|
316 | intr++; |
---|
317 | printf( "...interrupted\n" ); |
---|
318 | } |
---|
319 | |
---|
320 | /* |
---|
321 | * can_spawn() - If the command is suitable for execution via spawnvp, |
---|
322 | * return a number >= the number of characters it would occupy on the |
---|
323 | * command-line. Otherwise, return zero. |
---|
324 | */ |
---|
325 | long can_spawn(char* command) |
---|
326 | { |
---|
327 | char *p; |
---|
328 | |
---|
329 | char inquote = 0; |
---|
330 | |
---|
331 | /* Move to the first non-whitespace */ |
---|
332 | command += strspn( command, " \t" ); |
---|
333 | |
---|
334 | p = command; |
---|
335 | |
---|
336 | /* Look for newlines and unquoted i/o redirection */ |
---|
337 | do |
---|
338 | { |
---|
339 | p += strcspn( p, "'\n\"<>|" ); |
---|
340 | |
---|
341 | switch (*p) |
---|
342 | { |
---|
343 | case '\n': |
---|
344 | /* skip over any following spaces */ |
---|
345 | while( isspace( *p ) ) |
---|
346 | ++p; |
---|
347 | /* Must use a .bat file if there is anything significant |
---|
348 | * following the newline |
---|
349 | */ |
---|
350 | if (*p) |
---|
351 | return 0; |
---|
352 | break; |
---|
353 | |
---|
354 | case '"': |
---|
355 | case '\'': |
---|
356 | if (p > command && p[-1] != '\\') |
---|
357 | { |
---|
358 | if (inquote == *p) |
---|
359 | inquote = 0; |
---|
360 | else if (inquote == 0) |
---|
361 | inquote = *p; |
---|
362 | } |
---|
363 | |
---|
364 | ++p; |
---|
365 | break; |
---|
366 | |
---|
367 | case '<': |
---|
368 | case '>': |
---|
369 | case '|': |
---|
370 | if (!inquote) |
---|
371 | return 0; |
---|
372 | ++p; |
---|
373 | break; |
---|
374 | } |
---|
375 | } |
---|
376 | while (*p); |
---|
377 | |
---|
378 | /* Return the number of characters the command will occupy |
---|
379 | */ |
---|
380 | return p - command; |
---|
381 | } |
---|
382 | |
---|
383 | void execnt_unit_test() |
---|
384 | { |
---|
385 | #if !defined(NDEBUG) |
---|
386 | /* vc6 preprocessor is broken, so assert with these strings gets |
---|
387 | * confused. Use a table instead. |
---|
388 | */ |
---|
389 | typedef struct test { char* command; int result; } test; |
---|
390 | test tests[] = { |
---|
391 | { "x", 0 }, |
---|
392 | { "x\n ", 0 }, |
---|
393 | { "x\ny", 1 }, |
---|
394 | { "x\n\n y", 1 }, |
---|
395 | { "echo x > foo.bar", 1 }, |
---|
396 | { "echo x < foo.bar", 1 }, |
---|
397 | { "echo x \">\" foo.bar", 0 }, |
---|
398 | { "echo x \"<\" foo.bar", 0 }, |
---|
399 | { "echo x \\\">\\\" foo.bar", 1 }, |
---|
400 | { "echo x \\\"<\\\" foo.bar", 1 } |
---|
401 | }; |
---|
402 | int i; |
---|
403 | for ( i = 0; i < sizeof(tests)/sizeof(*tests); ++i) |
---|
404 | { |
---|
405 | assert( !can_spawn( tests[i].command ) == tests[i].result ); |
---|
406 | } |
---|
407 | |
---|
408 | { |
---|
409 | char* long_command = malloc(MAXLINE + 10); |
---|
410 | assert( long_command != 0 ); |
---|
411 | memset( long_command, 'x', MAXLINE + 9 ); |
---|
412 | long_command[MAXLINE + 9] = 0; |
---|
413 | assert( can_spawn( long_command ) == MAXLINE + 9); |
---|
414 | free( long_command ); |
---|
415 | } |
---|
416 | |
---|
417 | { |
---|
418 | /* Work around vc6 bug; it doesn't like escaped string |
---|
419 | * literals inside assert |
---|
420 | */ |
---|
421 | char** argv = string_to_args("\"g++\" -c -I\"Foobar\""); |
---|
422 | char const expected[] = "-c -I\"Foobar\""; |
---|
423 | |
---|
424 | assert(!strcmp(argv[0], "g++")); |
---|
425 | assert(!strcmp(argv[1], expected)); |
---|
426 | free_argv(argv); |
---|
427 | } |
---|
428 | #endif |
---|
429 | } |
---|
430 | |
---|
431 | /* SVA - handle temp dirs with spaces in the path */ |
---|
432 | static const char *getTempDir(void) |
---|
433 | { |
---|
434 | static char tempPath[_MAX_PATH]; |
---|
435 | static char *pTempPath=NULL; |
---|
436 | |
---|
437 | if(pTempPath == NULL) |
---|
438 | { |
---|
439 | char *p; |
---|
440 | |
---|
441 | p = getenv("TEMP"); |
---|
442 | if(p == NULL) |
---|
443 | { |
---|
444 | p = getenv("TMP"); |
---|
445 | } |
---|
446 | if(p == NULL) |
---|
447 | { |
---|
448 | pTempPath = "\\temp"; |
---|
449 | } |
---|
450 | else |
---|
451 | { |
---|
452 | GetShortPathName(p, tempPath, _MAX_PATH); |
---|
453 | pTempPath = tempPath; |
---|
454 | } |
---|
455 | } |
---|
456 | return pTempPath; |
---|
457 | } |
---|
458 | |
---|
459 | /* 64-bit arithmetic helpers */ |
---|
460 | |
---|
461 | /* Compute the carry bit from the addition of two 32-bit unsigned numbers */ |
---|
462 | #define add_carry_bit(a, b) ( (((a) | (b)) >> 31) & (~((a) + (b)) >> 31) & 0x1 ) |
---|
463 | |
---|
464 | /* Compute the high 32 bits of the addition of two 64-bit unsigned numbers, h1l1 and h2l2 */ |
---|
465 | #define add_64_hi(h1, l1, h2, l2) ((h1) + (h2) + add_carry_bit(l1, l2)) |
---|
466 | |
---|
467 | /* Add two 64-bit unsigned numbers, h1l1 and h2l2 */ |
---|
468 | static FILETIME add_64( |
---|
469 | unsigned long h1, unsigned long l1, |
---|
470 | unsigned long h2, unsigned long l2) |
---|
471 | { |
---|
472 | FILETIME result; |
---|
473 | result.dwLowDateTime = l1 + l2; |
---|
474 | result.dwHighDateTime = add_64_hi(h1, l1, h2, l2); |
---|
475 | |
---|
476 | return result; |
---|
477 | } |
---|
478 | |
---|
479 | static FILETIME add_FILETIME(FILETIME t1, FILETIME t2) |
---|
480 | { |
---|
481 | return add_64( |
---|
482 | t1.dwHighDateTime, t1.dwLowDateTime |
---|
483 | , t2.dwHighDateTime, t2.dwLowDateTime); |
---|
484 | } |
---|
485 | static FILETIME negate_FILETIME(FILETIME t) |
---|
486 | { |
---|
487 | /* 2s complement negation */ |
---|
488 | return add_64(~t.dwHighDateTime, ~t.dwLowDateTime, 0, 1); |
---|
489 | } |
---|
490 | |
---|
491 | /* COnvert a FILETIME to a number of seconds */ |
---|
492 | static double filetime_seconds(FILETIME t) |
---|
493 | { |
---|
494 | return t.dwHighDateTime * (double)(1UL << 31) * 2 + t.dwLowDateTime * 1.0e-7; |
---|
495 | } |
---|
496 | |
---|
497 | static void |
---|
498 | record_times(int pid, timing_info* time) |
---|
499 | { |
---|
500 | FILETIME creation, exit, kernel, user; |
---|
501 | if (GetProcessTimes((HANDLE)pid, &creation, &exit, &kernel, &user)) |
---|
502 | { |
---|
503 | /* Compute the elapsed time */ |
---|
504 | #if 0 /* We don't know how to get this number this on Unix */ |
---|
505 | time->elapsed = filetime_seconds( |
---|
506 | add_FILETIME( exit, negate_FILETIME(creation) ) |
---|
507 | ); |
---|
508 | #endif |
---|
509 | |
---|
510 | time->system = filetime_seconds(kernel); |
---|
511 | time->user = filetime_seconds(user); |
---|
512 | } |
---|
513 | |
---|
514 | CloseHandle((HANDLE)pid); |
---|
515 | } |
---|
516 | |
---|
517 | |
---|
518 | /* |
---|
519 | * execcmd() - launch an async command execution |
---|
520 | */ |
---|
521 | |
---|
522 | void |
---|
523 | execcmd( |
---|
524 | char *string, |
---|
525 | void (*func)( void *closure, int status, timing_info* ), |
---|
526 | void *closure, |
---|
527 | LIST *shell ) |
---|
528 | { |
---|
529 | int pid; |
---|
530 | int slot; |
---|
531 | int raw_cmd = 0 ; |
---|
532 | char *argv_static[ MAXARGC + 1 ]; /* +1 for NULL */ |
---|
533 | char **argv = argv_static; |
---|
534 | char *p; |
---|
535 | |
---|
536 | /* Check to see if we need to hack around the line-length limitation. */ |
---|
537 | /* Look for a JAMSHELL setting of "%", indicating that the command |
---|
538 | * should be invoked directly */ |
---|
539 | if ( shell && !strcmp(shell->string,"%") && !list_next(shell) ) |
---|
540 | { |
---|
541 | raw_cmd = 1; |
---|
542 | shell = 0; |
---|
543 | } |
---|
544 | |
---|
545 | if ( !is_win95_defined ) |
---|
546 | set_is_win95(); |
---|
547 | |
---|
548 | /* Find a slot in the running commands table for this one. */ |
---|
549 | if ( is_win95 ) |
---|
550 | { |
---|
551 | /* only synchronous spans are supported on Windows 95/98 */ |
---|
552 | slot = 0; |
---|
553 | } |
---|
554 | else |
---|
555 | { |
---|
556 | for( slot = 0; slot < MAXJOBS; slot++ ) |
---|
557 | if( !cmdtab[ slot ].pid ) |
---|
558 | break; |
---|
559 | } |
---|
560 | if( slot == MAXJOBS ) |
---|
561 | { |
---|
562 | printf( "no slots for child!\n" ); |
---|
563 | exit( EXITBAD ); |
---|
564 | } |
---|
565 | |
---|
566 | if( !cmdtab[ slot ].tempfile ) |
---|
567 | { |
---|
568 | const char *tempdir; |
---|
569 | DWORD procID; |
---|
570 | |
---|
571 | tempdir = getTempDir(); |
---|
572 | |
---|
573 | /* SVA - allocate 64 other just to be safe */ |
---|
574 | cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 64 ); |
---|
575 | |
---|
576 | procID = GetCurrentProcessId(); |
---|
577 | |
---|
578 | sprintf( cmdtab[ slot ].tempfile, "%s\\jam%d-%02d.bat", |
---|
579 | tempdir, procID, slot ); |
---|
580 | } |
---|
581 | |
---|
582 | /* Trim leading, ending white space */ |
---|
583 | |
---|
584 | while( isspace( *string ) ) |
---|
585 | ++string; |
---|
586 | |
---|
587 | /* Write to .BAT file unless the line would be too long and it |
---|
588 | * meets the other spawnability criteria. |
---|
589 | */ |
---|
590 | if( raw_cmd && can_spawn( string ) >= MAXLINE ) |
---|
591 | { |
---|
592 | if( DEBUG_EXECCMD ) |
---|
593 | printf("Executing raw command directly\n"); |
---|
594 | } |
---|
595 | else |
---|
596 | { |
---|
597 | FILE *f; |
---|
598 | raw_cmd = 0; |
---|
599 | |
---|
600 | /* Write command to bat file. */ |
---|
601 | f = fopen( cmdtab[ slot ].tempfile, "w" ); |
---|
602 | if (!f) |
---|
603 | { |
---|
604 | printf( "failed to write command file!\n" ); |
---|
605 | exit( EXITBAD ); |
---|
606 | } |
---|
607 | fputs( string, f ); |
---|
608 | fclose( f ); |
---|
609 | |
---|
610 | string = cmdtab[ slot ].tempfile; |
---|
611 | |
---|
612 | if( DEBUG_EXECCMD ) |
---|
613 | { |
---|
614 | if (shell) |
---|
615 | printf("using user-specified shell: %s", shell->string); |
---|
616 | else |
---|
617 | printf("Executing through .bat file\n"); |
---|
618 | } |
---|
619 | } |
---|
620 | |
---|
621 | /* Forumulate argv */ |
---|
622 | /* If shell was defined, be prepared for % and ! subs. */ |
---|
623 | /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */ |
---|
624 | |
---|
625 | if( shell ) |
---|
626 | { |
---|
627 | int i; |
---|
628 | char jobno[4]; |
---|
629 | int gotpercent = 0; |
---|
630 | |
---|
631 | sprintf( jobno, "%d", slot + 1 ); |
---|
632 | |
---|
633 | for( i = 0; shell && i < MAXARGC; i++, shell = list_next( shell ) ) |
---|
634 | { |
---|
635 | switch( shell->string[0] ) |
---|
636 | { |
---|
637 | case '%': argv[i] = string; gotpercent++; break; |
---|
638 | case '!': argv[i] = jobno; break; |
---|
639 | default: argv[i] = shell->string; |
---|
640 | } |
---|
641 | if( DEBUG_EXECCMD ) |
---|
642 | printf( "argv[%d] = '%s'\n", i, argv[i] ); |
---|
643 | } |
---|
644 | |
---|
645 | if( !gotpercent ) |
---|
646 | argv[i++] = string; |
---|
647 | |
---|
648 | argv[i] = 0; |
---|
649 | } |
---|
650 | else if (raw_cmd) |
---|
651 | { |
---|
652 | argv = string_to_args(string); |
---|
653 | } |
---|
654 | else |
---|
655 | { |
---|
656 | /* don't worry, this is ignored on Win95/98, see later.. */ |
---|
657 | argv[0] = "cmd.exe"; |
---|
658 | argv[1] = "/Q/C"; /* anything more is non-portable */ |
---|
659 | argv[2] = string; |
---|
660 | argv[3] = 0; |
---|
661 | } |
---|
662 | |
---|
663 | /* Catch interrupts whenever commands are running. */ |
---|
664 | |
---|
665 | if( !cmdsrunning++ ) |
---|
666 | istat = signal( SIGINT, onintr ); |
---|
667 | |
---|
668 | /* Start the command */ |
---|
669 | |
---|
670 | /* on Win95, we only do a synchronous call */ |
---|
671 | if ( is_win95 ) |
---|
672 | { |
---|
673 | static const char* hard_coded[] = |
---|
674 | { |
---|
675 | "del", "erase", "copy", "mkdir", "rmdir", "cls", "dir", |
---|
676 | "ren", "rename", "move", 0 |
---|
677 | }; |
---|
678 | |
---|
679 | const char** keyword; |
---|
680 | int len, spawn = 1; |
---|
681 | int result; |
---|
682 | timing_info time = {0,0}; |
---|
683 | |
---|
684 | for ( keyword = hard_coded; keyword[0]; keyword++ ) |
---|
685 | { |
---|
686 | len = strlen( keyword[0] ); |
---|
687 | if ( strnicmp( string, keyword[0], len ) == 0 && |
---|
688 | !isalnum(string[len]) ) |
---|
689 | { |
---|
690 | /* this is one of the hard coded symbols, use 'system' to run */ |
---|
691 | /* them.. except for "del"/"erase" */ |
---|
692 | if ( keyword - hard_coded < 2 ) |
---|
693 | result = process_del( string ); |
---|
694 | else |
---|
695 | result = system( string ); |
---|
696 | |
---|
697 | spawn = 0; |
---|
698 | break; |
---|
699 | } |
---|
700 | } |
---|
701 | |
---|
702 | if (spawn) |
---|
703 | { |
---|
704 | char** args; |
---|
705 | |
---|
706 | /* convert the string into an array of arguments */ |
---|
707 | /* we need to take care of double quotes !! */ |
---|
708 | args = string_to_args( string ); |
---|
709 | if ( args ) |
---|
710 | { |
---|
711 | #if 0 |
---|
712 | char** arg; |
---|
713 | fprintf( stderr, "%s: ", args[0] ); |
---|
714 | arg = args+1; |
---|
715 | while ( arg[0] ) |
---|
716 | { |
---|
717 | fprintf( stderr, " {%s}", arg[0] ); |
---|
718 | arg++; |
---|
719 | } |
---|
720 | fprintf( stderr, "\n" ); |
---|
721 | #endif |
---|
722 | result = spawnvp( P_WAIT, args[0], args ); |
---|
723 | record_times(result, &time); |
---|
724 | free_argv( args ); |
---|
725 | } |
---|
726 | else |
---|
727 | result = 1; |
---|
728 | } |
---|
729 | func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK, &time ); |
---|
730 | return; |
---|
731 | } |
---|
732 | |
---|
733 | if( DEBUG_EXECCMD ) |
---|
734 | { |
---|
735 | char **argp = argv; |
---|
736 | |
---|
737 | printf("Executing command"); |
---|
738 | while(*argp != 0) |
---|
739 | { |
---|
740 | printf(" [%s]", *argp); |
---|
741 | argp++; |
---|
742 | } |
---|
743 | printf("\n"); |
---|
744 | } |
---|
745 | |
---|
746 | /* the rest is for Windows NT only */ |
---|
747 | /* spawn doesn't like quotes around the command name */ |
---|
748 | if ( argv[0][0] == '"') |
---|
749 | { |
---|
750 | int l = strlen(argv[0]); |
---|
751 | |
---|
752 | /* Clobber any closing quote, shortening the string by one |
---|
753 | * element */ |
---|
754 | if (argv[0][l-1] == '"') |
---|
755 | argv[0][l-1] = '\0'; |
---|
756 | |
---|
757 | /* Move everything *including* the original terminating zero |
---|
758 | * back one place in memory, covering up the opening quote */ |
---|
759 | memmove(argv[0],argv[0]+1,l); |
---|
760 | } |
---|
761 | if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 ) |
---|
762 | { |
---|
763 | perror( "spawn" ); |
---|
764 | exit( EXITBAD ); |
---|
765 | } |
---|
766 | /* Save the operation for execwait() to find. */ |
---|
767 | |
---|
768 | cmdtab[ slot ].pid = pid; |
---|
769 | cmdtab[ slot ].func = func; |
---|
770 | cmdtab[ slot ].closure = closure; |
---|
771 | |
---|
772 | /* Wait until we're under the limit of concurrent commands. */ |
---|
773 | /* Don't trust globs.jobs alone. */ |
---|
774 | |
---|
775 | while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs ) |
---|
776 | if( !execwait() ) |
---|
777 | break; |
---|
778 | |
---|
779 | if (argv != argv_static) |
---|
780 | { |
---|
781 | free_argv(argv); |
---|
782 | } |
---|
783 | } |
---|
784 | |
---|
785 | /* |
---|
786 | * execwait() - wait and drive at most one execution completion |
---|
787 | */ |
---|
788 | |
---|
789 | int |
---|
790 | execwait() |
---|
791 | { |
---|
792 | int i; |
---|
793 | int status, w; |
---|
794 | int rstat; |
---|
795 | timing_info time; |
---|
796 | |
---|
797 | /* Handle naive make1() which doesn't know if cmds are running. */ |
---|
798 | |
---|
799 | if( !cmdsrunning ) |
---|
800 | return 0; |
---|
801 | |
---|
802 | if ( is_win95 ) |
---|
803 | return 0; |
---|
804 | |
---|
805 | /* Pick up process pid and status */ |
---|
806 | |
---|
807 | while( ( w = wait( &status ) ) == -1 && errno == EINTR ) |
---|
808 | ; |
---|
809 | |
---|
810 | if( w == -1 ) |
---|
811 | { |
---|
812 | printf( "child process(es) lost!\n" ); |
---|
813 | perror("wait"); |
---|
814 | exit( EXITBAD ); |
---|
815 | } |
---|
816 | |
---|
817 | /* Find the process in the cmdtab. */ |
---|
818 | |
---|
819 | for( i = 0; i < MAXJOBS; i++ ) |
---|
820 | if( w == cmdtab[ i ].pid ) |
---|
821 | break; |
---|
822 | |
---|
823 | if( i == MAXJOBS ) |
---|
824 | { |
---|
825 | printf( "waif child found!\n" ); |
---|
826 | exit( EXITBAD ); |
---|
827 | } |
---|
828 | |
---|
829 | record_times(cmdtab[i].pid, &time); |
---|
830 | |
---|
831 | /* Clear the temp file */ |
---|
832 | if ( cmdtab[i].tempfile ) |
---|
833 | unlink( cmdtab[ i ].tempfile ); |
---|
834 | |
---|
835 | /* Drive the completion */ |
---|
836 | |
---|
837 | if( !--cmdsrunning ) |
---|
838 | signal( SIGINT, istat ); |
---|
839 | |
---|
840 | if( intr ) |
---|
841 | rstat = EXEC_CMD_INTR; |
---|
842 | else if( w == -1 || status != 0 ) |
---|
843 | rstat = EXEC_CMD_FAIL; |
---|
844 | else |
---|
845 | rstat = EXEC_CMD_OK; |
---|
846 | |
---|
847 | cmdtab[ i ].pid = 0; |
---|
848 | /* SVA don't leak temp files */ |
---|
849 | if(cmdtab[i].tempfile != NULL) |
---|
850 | { |
---|
851 | free(cmdtab[i].tempfile); |
---|
852 | cmdtab[i].tempfile = NULL; |
---|
853 | } |
---|
854 | (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat, &time ); |
---|
855 | |
---|
856 | return 1; |
---|
857 | } |
---|
858 | |
---|
859 | # if !defined( __BORLANDC__ ) |
---|
860 | |
---|
861 | /* The possible result codes from check_process_exit, below */ |
---|
862 | typedef enum { process_error, process_active, process_finished } process_state; |
---|
863 | |
---|
864 | /* Helper for my_wait() below. Checks to see whether the process has |
---|
865 | * exited and if so, records timing information. |
---|
866 | */ |
---|
867 | static process_state |
---|
868 | check_process_exit( |
---|
869 | HANDLE process /* The process we're looking at */ |
---|
870 | |
---|
871 | , int* status /* Storage for the finished process' exit |
---|
872 | * code. If the process is still active |
---|
873 | * this location is left untouched. */ |
---|
874 | |
---|
875 | , HANDLE* active_handles /* Storage for the process handle if it is |
---|
876 | * found to be still active, or NULL. The |
---|
877 | * process is treated as though it is |
---|
878 | * complete. */ |
---|
879 | |
---|
880 | , int* num_active /* The current length of active_handles */ |
---|
881 | ) |
---|
882 | { |
---|
883 | DWORD exitcode; |
---|
884 | process_state result; |
---|
885 | |
---|
886 | /* Try to get the process exit code */ |
---|
887 | if (!GetExitCodeProcess(process, &exitcode)) |
---|
888 | { |
---|
889 | result = process_error; /* signal an error */ |
---|
890 | } |
---|
891 | else if ( |
---|
892 | exitcode == STILL_ACTIVE /* If the process is still active */ |
---|
893 | && active_handles != 0 /* and we've been passed a place to buffer it */ |
---|
894 | ) |
---|
895 | { |
---|
896 | active_handles[(*num_active)++] = process; /* push it onto the active stack */ |
---|
897 | result = process_active; |
---|
898 | } |
---|
899 | else |
---|
900 | { |
---|
901 | *status = (int)((exitcode & 0xff) << 8); |
---|
902 | result = process_finished; |
---|
903 | } |
---|
904 | |
---|
905 | return result; |
---|
906 | } |
---|
907 | |
---|
908 | static int |
---|
909 | my_wait( int *status ) |
---|
910 | { |
---|
911 | int i, num_active = 0; |
---|
912 | DWORD exitcode, waitcode; |
---|
913 | HANDLE active_handles[MAXJOBS]; |
---|
914 | |
---|
915 | /* first see if any non-waited-for processes are dead, |
---|
916 | * and return if so. |
---|
917 | */ |
---|
918 | for ( i = 0; i < globs.jobs; i++ ) |
---|
919 | { |
---|
920 | int pid = cmdtab[i].pid; |
---|
921 | |
---|
922 | if ( pid ) |
---|
923 | { |
---|
924 | process_state state |
---|
925 | = check_process_exit((HANDLE)pid, status, active_handles, &num_active); |
---|
926 | |
---|
927 | if ( state == process_error ) |
---|
928 | goto FAILED; |
---|
929 | else if ( state == process_finished ) |
---|
930 | return pid; |
---|
931 | } |
---|
932 | } |
---|
933 | |
---|
934 | /* if a child exists, wait for it to die */ |
---|
935 | if ( !num_active ) |
---|
936 | { |
---|
937 | errno = ECHILD; |
---|
938 | return -1; |
---|
939 | } |
---|
940 | |
---|
941 | waitcode = WaitForMultipleObjects( num_active, |
---|
942 | active_handles, |
---|
943 | FALSE, |
---|
944 | INFINITE ); |
---|
945 | if ( waitcode != WAIT_FAILED ) |
---|
946 | { |
---|
947 | if ( waitcode >= WAIT_ABANDONED_0 |
---|
948 | && waitcode < WAIT_ABANDONED_0 + num_active ) |
---|
949 | i = waitcode - WAIT_ABANDONED_0; |
---|
950 | else |
---|
951 | i = waitcode - WAIT_OBJECT_0; |
---|
952 | |
---|
953 | if ( check_process_exit(active_handles[i], status, 0, 0) == process_finished ) |
---|
954 | return (int)active_handles[i]; |
---|
955 | } |
---|
956 | |
---|
957 | FAILED: |
---|
958 | errno = GetLastError(); |
---|
959 | return -1; |
---|
960 | |
---|
961 | } |
---|
962 | |
---|
963 | # endif /* !__BORLANDC__ */ |
---|
964 | |
---|
965 | # endif /* USE_EXECNT */ |
---|