| 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 */ |
|---|