2 * Copyright 1993, 1995 Christopher Seiwald.
4 * This file is part of Jam - see jam.c for Copyright information.
14 # define WIN32_LEAN_AND_MEAN
15 # include <windows.h> /* do the ugly deed */
18 # if !defined( __BORLANDC__ ) && !defined( OS_OS2 )
20 static int my_wait( int *status
);
24 * execnt.c - execute a shell command on Windows NT and Windows 95/98
26 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
29 * /bin/sh -c % [ on UNIX/AmigaOS ]
30 * cmd.exe /c % [ on Windows NT ]
32 * Each word must be an individual element in a jam variable value.
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
39 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
42 * execcmd() - launch an async command execution
43 * execwait() - wait and drive at most one execution completion
46 * onintr() - bump intr to note command interruption
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
55 static int cmdsrunning
= 0;
56 static void (*istat
)( int );
58 static int is_nt_351
= 0;
59 static int is_win95
= 1;
60 static int is_win95_defined
= 0;
65 int pid
; /* on win32, a real process handle */
66 void (*func
)( void *closure
, int status
);
70 } cmdtab
[ MAXJOBS
] = {{0}};
76 OSVERSIONINFO os_info
;
78 os_info
.dwOSVersionInfoSize
= sizeof(os_info
);
79 os_info
.dwPlatformId
= VER_PLATFORM_WIN32_WINDOWS
;
80 GetVersionEx( &os_info
);
82 is_win95
= (os_info
.dwPlatformId
== VER_PLATFORM_WIN32_WINDOWS
);
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;
93 string_to_args( const char* string
, int* pcount
)
95 int total
= strlen( string
);
96 int in_quote
= 0, num_args
;
104 /* do not copy trailing newlines, if any */
108 for ( i
= total
-1; i
> 0; i
-- )
110 if ( string
[i
] != '\n' && string
[i
] != '\r' )
116 /* first of all, copy the input string */
117 line
= (char*)malloc( total
+2 );
121 memcpy( line
+1, string
, total
);
126 for ( p
= line
+1; p
[0]; p
++ )
131 in_quote
= !in_quote
;
144 /* now count the arguments.. */
145 for ( p
= line
; p
< line
+total
+1; p
++ )
149 /* allocate the args array */
150 args
= (char**)malloc( num_args
*sizeof(char*)+2 );
158 for ( p
= line
; p
< line
+total
+1; p
++ )
171 free_args( char** args
)
178 /* process a "del" or "erase" command under Windows 95/98 */
180 process_del( char* command
)
183 char* p
= command
, *q
;
184 int wildcard
= 0, result
= 0;
186 /* first of all, skip the command itself */
188 p
+= 3; /* assumes "del..;" */
189 else if ( p
[0] == 'e' )
190 p
+= 5; /* assumes "erase.." */
192 return 1; /* invalid command */
194 /* process all targets independently */
197 /* skip leading spaces */
198 while ( *p
&& isspace(*p
) )
201 /* exit if we encounter an end of string */
205 /* ignore toggles/flags */
209 while ( *p
&& isalnum(*p
) )
224 in_quote
= !in_quote
;
246 /* q..p-1 contains the delete argument */
250 line
= (char*)malloc( len
+4+1 );
254 strncpy( line
, "del ", 4 );
255 strncpy( line
+4, q
, len
);
259 result
= system( line
);
261 result
= !DeleteFile( line
+4 );
274 } /* while (go_on) */
281 * onintr() - bump intr to note command interruption
288 printf( "...interrupted\n" );
292 * execcmd() - launch an async command execution
298 void (*func
)( void *closure
, int status
),
305 char *argv
[ MAXARGC
+ 1 ]; /* +1 for NULL */
308 if ( !is_win95_defined
)
311 /* Find a slot in the running commands table for this one. */
314 /* only synchronous spans are supported on Windows 95/98 */
319 for( slot
= 0; slot
< MAXJOBS
; slot
++ )
320 if( !cmdtab
[ slot
].pid
)
323 if( slot
== MAXJOBS
)
325 printf( "no slots for child!\n" );
329 if( !cmdtab
[ slot
].tempfile
)
333 if( !( tempdir
= getenv( "TEMP" ) ) &&
334 !( tempdir
= getenv( "TMP" ) ) )
337 cmdtab
[ slot
].tempfile
= malloc( strlen( tempdir
) + 14 );
339 sprintf( cmdtab
[ slot
].tempfile
, "%s\\jamtmp%02d.bat",
343 /* Trim leading, ending white space */
345 while( isspace( *string
) )
348 p
= strchr( string
, '\n' );
350 while( p
&& isspace( *p
) )
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
;
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. */
362 if( p
&& *p
|| strlen( string
) > max_line
|| shell
)
366 /* Write command to bat file. */
368 f
= fopen( cmdtab
[ slot
].tempfile
, "w" );
372 string
= cmdtab
[ slot
].tempfile
;
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). */
385 sprintf( jobno
, "%d", slot
+ 1 );
387 for( i
= 0; shell
&& i
< MAXARGC
; i
++, shell
= list_next( shell
) )
389 switch( shell
->string
[0] )
391 case '%': argv
[i
] = string
; gotpercent
++; break;
392 case '!': argv
[i
] = jobno
; break;
393 default: argv
[i
] = shell
->string
;
396 printf( "argv[%d] = '%s'\n", i
, argv
[i
] );
406 /* don't worry, this is ignored on Win95/98, see later.. */
408 argv
[1] = "/Q/C"; /* anything more is non-portable */
413 /* Catch interrupts whenever commands are running. */
416 istat
= signal( SIGINT
, onintr
);
418 /* Start the command */
420 /* on Win95, we only do a synchronous call */
423 static const char* hard_coded
[] =
425 "del", "erase", "copy", "mkdir", "rmdir", "cls", "dir",
426 "ren", "rename", "move", 0
429 const char** keyword
;
433 for ( keyword
= hard_coded
; keyword
[0]; keyword
++ )
435 len
= strlen( keyword
[0] );
436 if ( strnicmp( string
, keyword
[0], len
) == 0 &&
437 !isalnum(string
[len
]) )
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
);
444 result
= system( string
);
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
);
463 fprintf( stderr
, "%s: ", args
[0] );
467 fprintf( stderr
, " {%s}", arg
[0] );
470 fprintf( stderr
, "\n" );
472 result
= spawnvp( P_WAIT
, args
[0], args
);
478 func( closure
, result
? EXEC_CMD_FAIL
: EXEC_CMD_OK
);
482 /* the rest is for Windows NT only */
483 if( ( pid
= spawnvp( P_NOWAIT
, argv
[0], argv
) ) == -1 )
488 /* Save the operation for execwait() to find. */
490 cmdtab
[ slot
].pid
= pid
;
491 cmdtab
[ slot
].func
= func
;
492 cmdtab
[ slot
].closure
= closure
;
494 /* Wait until we're under the limit of concurrent commands. */
495 /* Don't trust globs.jobs alone. */
497 while( cmdsrunning
>= MAXJOBS
|| cmdsrunning
>= globs
.jobs
)
503 * execwait() - wait and drive at most one execution completion
513 /* Handle naive make1() which doesn't know if cmds are running. */
521 /* Pick up process pid and status */
523 while( ( w
= wait( &status
) ) == -1 && errno
== EINTR
)
528 printf( "child process(es) lost!\n" );
533 /* Find the process in the cmdtab. */
535 for( i
= 0; i
< MAXJOBS
; i
++ )
536 if( w
== cmdtab
[ i
].pid
)
541 printf( "waif child found!\n" );
545 /* Drive the completion */
548 signal( SIGINT
, istat
);
551 rstat
= EXEC_CMD_INTR
;
552 else if( w
== -1 || status
!= 0 )
553 rstat
= EXEC_CMD_FAIL
;
559 (*cmdtab
[ i
].func
)( cmdtab
[ i
].closure
, rstat
);
564 # if !defined( __BORLANDC__ )
567 my_wait( int *status
)
569 int i
, num_active
= 0;
570 DWORD exitcode
, waitcode
;
571 static HANDLE
*active_handles
= 0;
574 active_handles
= (HANDLE
*)malloc(globs
.jobs
* sizeof(HANDLE
) );
576 /* first see if any non-waited-for processes are dead,
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
;
585 CloseHandle((HANDLE
)cmdtab
[i
].pid
);
586 *status
= (int)((exitcode
& 0xff) << 8);
587 return cmdtab
[i
].pid
;
595 /* if a child exists, wait for it to die */
600 waitcode
= WaitForMultipleObjects( num_active
,
604 if ( waitcode
!= WAIT_FAILED
) {
605 if ( waitcode
>= WAIT_ABANDONED_0
606 && waitcode
< WAIT_ABANDONED_0
+ num_active
)
607 i
= waitcode
- WAIT_ABANDONED_0
;
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
];
618 errno
= GetLastError();
623 # endif /* !__BORLANDC__ */
625 # endif /* USE_EXECNT */