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