1 /*
2 * Copyright 1993, 1995 Christopher Seiwald.
3 *
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
6
7 # include "jam.h"
8 # include "lists.h"
9 # include "execcmd.h"
10 # include <errno.h>
11
12 # ifdef USE_EXECNT
13
14 # define WIN32_LEAN_AND_MEAN
15 # include <windows.h> /* do the ugly deed */
16 # include <process.h>
17
18 # if !defined( __BORLANDC__ ) && !defined( OS_OS2 )
19 # define wait my_wait
20 static int my_wait( int *status );
21 # endif
22
23 /*
24 * execnt.c - execute a shell command on Windows NT and Windows 95/98
25 *
26 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
27 * The default is:
28 *
29 * /bin/sh -c % [ on UNIX/AmigaOS ]
30 * cmd.exe /c % [ on Windows NT ]
31 *
32 * Each word must be an individual element in a jam variable value.
33 *
34 * In $(JAMSHELL), % expands to the command string and ! expands to
35 * the slot number (starting at 1) for multiprocess (-j) invocations.
36 * If $(JAMSHELL) doesn't include a %, it is tacked on as the last
37 * argument.
38 *
39 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
40 *
41 * External routines:
42 * execcmd() - launch an async command execution
43 * execwait() - wait and drive at most one execution completion
44 *
45 * Internal routines:
46 * onintr() - bump intr to note command interruption
47 *
48 * 04/08/94 (seiwald) - Coherent/386 support added.
49 * 05/04/94 (seiwald) - async multiprocess interface
50 * 01/22/95 (seiwald) - $(JAMSHELL) support
51 * 06/02/97 (gsar) - full async multiprocess support for Win32
52 */
53
54 static int intr = 0;
55 static int cmdsrunning = 0;
56 static void (*istat)( int );
57
58 static int is_nt_351 = 0;
59 static int is_win95 = 1;
60 static int is_win95_defined = 0;
61
62
63 static struct
64 {
65 int pid; /* on win32, a real process handle */
66 void (*func)( void *closure, int status );
67 void *closure;
68 char *tempfile;
69
70 } cmdtab[ MAXJOBS ] = {{0}};
71
72
73 static void
set_is_win95(void)74 set_is_win95( void )
75 {
76 OSVERSIONINFO os_info;
77
78 os_info.dwOSVersionInfoSize = sizeof(os_info);
79 os_info.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS;
80 GetVersionEx( &os_info );
81
82 is_win95 = (os_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);
83 is_win95_defined = 1;
84
85 /* now, test wether we're running Windows 3.51 */
86 /* this is later used to limit the system call command length */
87 if (os_info.dwPlatformId == VER_PLATFORM_WIN32_NT)
88 is_nt_351 = os_info.dwMajorVersion == 3;
89 }
90
91
92 static char**
string_to_args(const char * string,int * pcount)93 string_to_args( const char* string, int* pcount )
94 {
95 int total = strlen( string );
96 int in_quote = 0, num_args;
97 char* line;
98 char* p;
99 char** arg;
100 char** args;
101
102 *pcount = 0;
103
104 /* do not copy trailing newlines, if any */
105 {
106 int i;
107
108 for ( i = total-1; i > 0; i-- )
109 {
110 if ( string[i] != '\n' && string[i] != '\r' )
111 break;
112 total --;
113 }
114 }
115
116 /* first of all, copy the input string */
117 line = (char*)malloc( total+2 );
118 if (!line)
119 return 0;
120
121 memcpy( line+1, string, total );
122 line[0] = 0;
123 line[total+1] = 0;
124
125 in_quote = 0;
126 for ( p = line+1; p[0]; p++ )
127 {
128 switch (p[0])
129 {
130 case '"':
131 in_quote = !in_quote;
132 break;
133
134 case ' ':
135 case '\t':
136 if (!in_quote)
137 p[0] = 0;
138
139 default:
140 ;
141 }
142 }
143
144 /* now count the arguments.. */
145 for ( p = line; p < line+total+1; p++ )
146 if ( !p[0] && p[1] )
147 num_args++;
148
149 /* allocate the args array */
150 args = (char**)malloc( num_args*sizeof(char*)+2 );
151 if (!args)
152 {
153 free( line );
154 return 0;
155 }
156
157 arg = args+1;
158 for ( p = line; p < line+total+1; p++ )
159 if ( !p[0] && p[1] )
160 {
161 arg[0] = p+1;
162 arg++;
163 }
164 arg[0] = 0;
165 *pcount = num_args;
166 args[0] = line;
167 return args+1;
168 }
169
170 static void
free_args(char ** args)171 free_args( char** args )
172 {
173 free( args[-1] );
174 free( args-1 );
175 }
176
177
178 /* process a "del" or "erase" command under Windows 95/98 */
179 static int
process_del(char * command)180 process_del( char* command )
181 {
182 char** arg;
183 char* p = command, *q;
184 int wildcard = 0, result = 0;
185
186 /* first of all, skip the command itself */
187 if ( p[0] == 'd' )
188 p += 3; /* assumes "del..;" */
189 else if ( p[0] == 'e' )
190 p += 5; /* assumes "erase.." */
191 else
192 return 1; /* invalid command */
193
194 /* process all targets independently */
195 for (;;)
196 {
197 /* skip leading spaces */
198 while ( *p && isspace(*p) )
199 p++;
200
201 /* exit if we encounter an end of string */
202 if (!*p)
203 return 0;
204
205 /* ignore toggles/flags */
206 if (*p == '/')
207 {
208 p++;
209 while ( *p && isalnum(*p) )
210 p++;
211 }
212 else
213 {
214 int in_quote = 0;
215 int wildcard = 0;
216 int go_on = 1;
217
218 q = p;
219 while (go_on)
220 {
221 switch (*p)
222 {
223 case '"':
224 in_quote = !in_quote;
225 break;
226
227 case '?':
228 case '*':
229 if (!in_quote)
230 wildcard = 1;
231 break;
232
233 case '\0':
234 if (in_quote)
235 return 1;
236 /* fall-through */
237
238 case ' ':
239 case '\t':
240 if (!in_quote)
241 {
242 int len = p - q;
243 int result;
244 char* line;
245
246 /* q..p-1 contains the delete argument */
247 if ( len <= 0 )
248 return 1;
249
250 line = (char*)malloc( len+4+1 );
251 if (!line)
252 return 1;
253
254 strncpy( line, "del ", 4 );
255 strncpy( line+4, q, len );
256 line[len+4] = '\0';
257
258 if ( wildcard )
259 result = system( line );
260 else
261 result = !DeleteFile( line+4 );
262
263 free( line );
264 if (result)
265 return 1;
266
267 go_on = 0;
268 }
269
270 default:
271 ;
272 }
273 p++;
274 } /* while (go_on) */
275 }
276 }
277 }
278
279
280 /*
281 * onintr() - bump intr to note command interruption
282 */
283
284 void
onintr(int disp)285 onintr( int disp )
286 {
287 intr++;
288 printf( "...interrupted\n" );
289 }
290
291 /*
292 * execcmd() - launch an async command execution
293 */
294
295 void
execcmd(char * string,void (* func)(void * closure,int status),void * closure,LIST * shell)296 execcmd(
297 char *string,
298 void (*func)( void *closure, int status ),
299 void *closure,
300 LIST *shell )
301 {
302 int pid;
303 int slot;
304 int max_line;
305 char *argv[ MAXARGC + 1 ]; /* +1 for NULL */
306 char *p;
307
308 if ( !is_win95_defined )
309 set_is_win95();
310
311 /* Find a slot in the running commands table for this one. */
312 if ( is_win95 )
313 {
314 /* only synchronous spans are supported on Windows 95/98 */
315 slot = 0;
316 }
317 else
318 {
319 for( slot = 0; slot < MAXJOBS; slot++ )
320 if( !cmdtab[ slot ].pid )
321 break;
322 }
323 if( slot == MAXJOBS )
324 {
325 printf( "no slots for child!\n" );
326 exit( EXITBAD );
327 }
328
329 if( !cmdtab[ slot ].tempfile )
330 {
331 char *tempdir;
332
333 if( !( tempdir = getenv( "TEMP" ) ) &&
334 !( tempdir = getenv( "TMP" ) ) )
335 tempdir = "\\temp";
336
337 cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 14 );
338
339 sprintf( cmdtab[ slot ].tempfile, "%s\\jamtmp%02d.bat",
340 tempdir, slot );
341 }
342
343 /* Trim leading, ending white space */
344
345 while( isspace( *string ) )
346 ++string;
347
348 p = strchr( string, '\n' );
349
350 while( p && isspace( *p ) )
351 ++p;
352
353 /* on Windows NT 3.51, the maximul line length is 996 bytes !! */
354 /* while it's much bigger NT 4 and 2k */
355 max_line = is_nt_351 ? 996 : MAXLINE;
356
357 /* If multi line, or too long, or JAMSHELL is set, write to bat file. */
358 /* Otherwise, exec directly. */
359 /* Frankly, if it is a single long line I don't think the */
360 /* command interpreter will do any better -- it will fail. */
361
362 if( p && *p || strlen( string ) > max_line || shell )
363 {
364 FILE *f;
365
366 /* Write command to bat file. */
367
368 f = fopen( cmdtab[ slot ].tempfile, "w" );
369 fputs( string, f );
370 fclose( f );
371
372 string = cmdtab[ slot ].tempfile;
373 }
374
375 /* Forumulate argv */
376 /* If shell was defined, be prepared for % and ! subs. */
377 /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */
378
379 if( shell )
380 {
381 int i;
382 char jobno[4];
383 int gotpercent = 0;
384
385 sprintf( jobno, "%d", slot + 1 );
386
387 for( i = 0; shell && i < MAXARGC; i++, shell = list_next( shell ) )
388 {
389 switch( shell->string[0] )
390 {
391 case '%': argv[i] = string; gotpercent++; break;
392 case '!': argv[i] = jobno; break;
393 default: argv[i] = shell->string;
394 }
395 if( DEBUG_EXECCMD )
396 printf( "argv[%d] = '%s'\n", i, argv[i] );
397 }
398
399 if( !gotpercent )
400 argv[i++] = string;
401
402 argv[i] = 0;
403 }
404 else
405 {
406 /* don't worry, this is ignored on Win95/98, see later.. */
407 argv[0] = "cmd.exe";
408 argv[1] = "/Q/C"; /* anything more is non-portable */
409 argv[2] = string;
410 argv[3] = 0;
411 }
412
413 /* Catch interrupts whenever commands are running. */
414
415 if( !cmdsrunning++ )
416 istat = signal( SIGINT, onintr );
417
418 /* Start the command */
419
420 /* on Win95, we only do a synchronous call */
421 if ( is_win95 )
422 {
423 static const char* hard_coded[] =
424 {
425 "del", "erase", "copy", "mkdir", "rmdir", "cls", "dir",
426 "ren", "rename", "move", 0
427 };
428
429 const char** keyword;
430 int len, spawn = 1;
431 int result;
432
433 for ( keyword = hard_coded; keyword[0]; keyword++ )
434 {
435 len = strlen( keyword[0] );
436 if ( strnicmp( string, keyword[0], len ) == 0 &&
437 !isalnum(string[len]) )
438 {
439 /* this is one of the hard coded symbols, use 'system' to run */
440 /* them.. except for "del"/"erase" */
441 if ( keyword - hard_coded < 2 )
442 result = process_del( string );
443 else
444 result = system( string );
445
446 spawn = 0;
447 break;
448 }
449 }
450
451 if (spawn)
452 {
453 char** args;
454 int num_args;
455
456 /* convert the string into an array of arguments */
457 /* we need to take care of double quotes !! */
458 args = string_to_args( string, &num_args );
459 if ( args )
460 {
461 #if 0
462 char** arg;
463 fprintf( stderr, "%s: ", args[0] );
464 arg = args+1;
465 while ( arg[0] )
466 {
467 fprintf( stderr, " {%s}", arg[0] );
468 arg++;
469 }
470 fprintf( stderr, "\n" );
471 #endif
472 result = spawnvp( P_WAIT, args[0], args );
473 free_args( args );
474 }
475 else
476 result = 1;
477 }
478 func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK );
479 return;
480 }
481
482 /* the rest is for Windows NT only */
483 if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 )
484 {
485 perror( "spawn" );
486 exit( EXITBAD );
487 }
488 /* Save the operation for execwait() to find. */
489
490 cmdtab[ slot ].pid = pid;
491 cmdtab[ slot ].func = func;
492 cmdtab[ slot ].closure = closure;
493
494 /* Wait until we're under the limit of concurrent commands. */
495 /* Don't trust globs.jobs alone. */
496
497 while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs )
498 if( !execwait() )
499 break;
500 }
501
502 /*
503 * execwait() - wait and drive at most one execution completion
504 */
505
506 int
execwait()507 execwait()
508 {
509 int i;
510 int status, w;
511 int rstat;
512
513 /* Handle naive make1() which doesn't know if cmds are running. */
514
515 if( !cmdsrunning )
516 return 0;
517
518 if ( is_win95 )
519 return 0;
520
521 /* Pick up process pid and status */
522
523 while( ( w = wait( &status ) ) == -1 && errno == EINTR )
524 ;
525
526 if( w == -1 )
527 {
528 printf( "child process(es) lost!\n" );
529 perror("wait");
530 exit( EXITBAD );
531 }
532
533 /* Find the process in the cmdtab. */
534
535 for( i = 0; i < MAXJOBS; i++ )
536 if( w == cmdtab[ i ].pid )
537 break;
538
539 if( i == MAXJOBS )
540 {
541 printf( "waif child found!\n" );
542 exit( EXITBAD );
543 }
544
545 /* Drive the completion */
546
547 if( !--cmdsrunning )
548 signal( SIGINT, istat );
549
550 if( intr )
551 rstat = EXEC_CMD_INTR;
552 else if( w == -1 || status != 0 )
553 rstat = EXEC_CMD_FAIL;
554 else
555 rstat = EXEC_CMD_OK;
556
557 cmdtab[ i ].pid = 0;
558
559 (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat );
560
561 return 1;
562 }
563
564 # if !defined( __BORLANDC__ )
565
566 static int
my_wait(int * status)567 my_wait( int *status )
568 {
569 int i, num_active = 0;
570 DWORD exitcode, waitcode;
571 static HANDLE *active_handles = 0;
572
573 if (!active_handles)
574 active_handles = (HANDLE *)malloc(globs.jobs * sizeof(HANDLE) );
575
576 /* first see if any non-waited-for processes are dead,
577 * and return if so.
578 */
579 for ( i = 0; i < globs.jobs; i++ ) {
580 if ( cmdtab[i].pid ) {
581 if ( GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode) ) {
582 if ( exitcode == STILL_ACTIVE )
583 active_handles[num_active++] = (HANDLE)cmdtab[i].pid;
584 else {
585 CloseHandle((HANDLE)cmdtab[i].pid);
586 *status = (int)((exitcode & 0xff) << 8);
587 return cmdtab[i].pid;
588 }
589 }
590 else
591 goto FAILED;
592 }
593 }
594
595 /* if a child exists, wait for it to die */
596 if ( !num_active ) {
597 errno = ECHILD;
598 return -1;
599 }
600 waitcode = WaitForMultipleObjects( num_active,
601 active_handles,
602 FALSE,
603 INFINITE );
604 if ( waitcode != WAIT_FAILED ) {
605 if ( waitcode >= WAIT_ABANDONED_0
606 && waitcode < WAIT_ABANDONED_0 + num_active )
607 i = waitcode - WAIT_ABANDONED_0;
608 else
609 i = waitcode - WAIT_OBJECT_0;
610 if ( GetExitCodeProcess(active_handles[i], &exitcode) ) {
611 CloseHandle(active_handles[i]);
612 *status = (int)((exitcode & 0xff) << 8);
613 return (int)active_handles[i];
614 }
615 }
616
617 FAILED:
618 errno = GetLastError();
619 return -1;
620
621 }
622
623 # endif /* !__BORLANDC__ */
624
625 # endif /* USE_EXECNT */
626