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
);
25 * execnt.c - execute a shell command on Windows NT and Windows 95/98
27 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
30 * /bin/sh -c % [ on UNIX/AmigaOS ]
31 * cmd.exe /c % [ on Windows NT ]
33 * Each word must be an individual element in a jam variable value.
35 * In $(JAMSHELL), % expands to the command string and ! expands to
36 * the slot number (starting at 1) for multiprocess (-j) invocations.
37 * If $(JAMSHELL) doesn't include a %, it is tacked on as the last
40 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
43 * execcmd() - launch an async command execution
44 * execwait() - wait and drive at most one execution completion
47 * onintr() - bump intr to note command interruption
49 * 04/08/94 (seiwald) - Coherent/386 support added.
50 * 05/04/94 (seiwald) - async multiprocess interface
51 * 01/22/95 (seiwald) - $(JAMSHELL) support
52 * 06/02/97 (gsar) - full async multiprocess support for Win32
56 static int cmdsrunning
= 0;
57 static void (*istat
) (int);
59 static int is_nt_351
= 0;
60 static int is_win95
= 1;
61 static int is_win95_defined
= 0;
65 int pid
; /* on win32, a real process handle */
66 void (*func
) (void *closure
, int status
);
69 } cmdtab
[MAXJOBS
] = {{0}};
72 static void set_is_win95 (void) {
73 OSVERSIONINFO os_info
;
75 os_info
.dwOSVersionInfoSize
= sizeof(os_info
);
76 os_info
.dwPlatformId
= VER_PLATFORM_WIN32_WINDOWS
;
77 GetVersionEx(&os_info
);
79 is_win95
= (os_info
.dwPlatformId
== VER_PLATFORM_WIN32_WINDOWS
);
82 /* now, test wether we're running Windows 3.51 */
83 /* this is later used to limit the system call command length */
84 if (os_info
.dwPlatformId
== VER_PLATFORM_WIN32_NT
) is_nt_351
= (os_info
.dwMajorVersion
== 3);
88 static char **string_to_args (const char *string
, int *pcount
) {
89 int total
= strlen(string
);
90 int in_quote
= 0, num_args
;
97 /* do not copy trailing newlines, if any */
100 for (i
= total
-1; i
> 0; i
--) {
101 if (string
[i
] != '\n' && string
[i
] != '\r') break;
106 /* first of all, copy the input string */
107 line
= (char *)malloc(total
+2);
109 memcpy(line
+1, string
, total
);
114 for (p
= line
+1; p
[0]; p
++) {
116 case '"': in_quote
= !in_quote
; break;
117 case ' ': case '\t': if (!in_quote
) p
[0] = 0;
122 /* now count the arguments.. */
123 for (p
= line
; p
< line
+total
+1; p
++) if (!p
[0] && p
[1]) num_args
++;
125 /* allocate the args array */
126 args
= (char **)malloc(num_args
*sizeof(char*)+2);
127 if (!args
) { free(line
); return 0; }
130 for (p
= line
; p
< line
+total
+1; p
++) if (!p
[0] && p
[1]) { arg
[0] = p
+1; arg
++; }
138 static void free_args (char **args
) {
144 /* process a "del" or "erase" command under Windows 95/98 */
145 static int process_del (char *command
) {
147 char *p
= command
, *q
;
148 int wildcard
= 0, result
= 0;
150 /* first of all, skip the command itself */
151 if (p
[0] == 'd') p
+= 3; /* assumes "del..;" */
152 else if (p
[0] == 'e') p
+= 5; /* assumes "erase.." */
153 else return 1; /* invalid command */
155 /* process all targets independently */
157 /* skip leading spaces */
158 while (*p
&& isspace(*p
)) p
++;
159 /* exit if we encounter an end of string */
161 /* ignore toggles/flags */
164 while (*p
&& isalnum(*p
)) p
++;
173 case '"': in_quote
= !in_quote
; break;
174 case '?': case '*': if (!in_quote
) wildcard
= 1; break;
175 case '\0': if (in_quote
) return 1; /* fall-through */
182 /* q..p-1 contains the delete argument */
183 if (len
<= 0) return 1;
184 line
= (char *)malloc((len
+4+1)*sizeof(char));
187 strncpy(line
, "del ", 4);
188 strncpy(line
+4, q
, len
);
191 if (wildcard
) result
= system(line
); else result
= !DeleteFile(line
+4);
194 if (result
) return 1;
201 } /* while (go_on) */
208 * onintr() - bump intr to note command interruption
210 void onintr (int disp
) {
212 printf("...interrupted\n");
217 * execcmd() - launch an async command execution
221 void (*func
) (void *closure
, int status
),
228 char *argv
[ MAXARGC
+1]; /* +1 for NULL */
231 if (!is_win95_defined
) set_is_win95();
232 /* Find a slot in the running commands table for this one. */
234 /* only synchronous spans are supported on Windows 95/98 */
237 for (slot
= 0; slot
< MAXJOBS
; slot
++) if (!cmdtab
[slot
].pid
) break;
239 if (slot
== MAXJOBS
) {
240 printf("no slots for child!\n");
244 if (!cmdtab
[slot
].tempfile
) {
247 if (!(tempdir
= getenv("TEMP")) && !(tempdir
= getenv("TMP"))) tempdir
= "\\temp";
248 cmdtab
[slot
].tempfile
= malloc(strlen(tempdir
)+14);
250 sprintf(cmdtab
[slot
].tempfile
, "%s\\jamtmp%02d.bat", tempdir
, slot
);
253 /* Trim leading, ending white space */
254 while (isspace(*string
)) ++string
;
255 p
= strchr(string
, '\n');
256 while (p
&& isspace(*p
)) ++p
;
258 /* on Windows NT 3.51, the maximul line length is 996 bytes !! */
259 /* while it's much bigger NT 4 and 2k */
260 max_line
= is_nt_351
? 996 : MAXLINE
;
262 /* If multi line, or too long, or JAMSHELL is set, write to bat file. */
263 /* Otherwise, exec directly. */
264 /* Frankly, if it is a single long line I don't think the */
265 /* command interpreter will do any better -- it will fail. */
266 if (p
&& *p
|| strlen(string
) > max_line
|| shell
) {
269 /* Write command to bat file. */
270 f
= fopen(cmdtab
[slot
].tempfile
, "w");
273 string
= cmdtab
[slot
].tempfile
;
276 /* Forumulate argv */
277 /* If shell was defined, be prepared for % and ! subs. */
278 /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */
284 sprintf(jobno
, "%d", slot
+1);
285 for (i
= 0; shell
&& i
< MAXARGC
; i
++, shell
= list_next(shell
)) {
286 switch (shell
->string
[0]) {
287 case '%': argv
[i
] = string
; gotpercent
++; break;
288 case '!': argv
[i
] = jobno
; break;
289 default: argv
[i
] = shell
->string
;
291 if (DEBUG_EXECCMD
) printf("argv[%d] = '%s'\n", i
, argv
[i
]);
293 if (!gotpercent
) argv
[i
++] = string
;
296 /* don't worry, this is ignored on Win95/98, see later.. */
298 argv
[1] = "/Q/C"; /* anything more is non-portable */
303 /* Catch interrupts whenever commands are running. */
304 if (!cmdsrunning
++) istat
= signal(SIGINT
, onintr
);
306 /* Start the command */
307 /* on Win95, we only do a synchronous call */
309 static const char* hard_coded
[] = {
310 "del", "erase", "copy", "mkdir", "rmdir", "cls", "dir",
311 "ren", "rename", "move", 0
314 const char **keyword
;
318 for (keyword
= hard_coded
; keyword
[0]; keyword
++) {
319 len
= strlen(keyword
[0]);
320 if ( strnicmp(string
, keyword
[0], len
) == 0 && !isalnum(string
[len
])) {
321 /* this is one of the hard coded symbols, use 'system' to run */
322 /* them.. except for "del"/"erase" */
323 if (keyword
-hard_coded
< 2) result
= process_del(string
);
324 else result
= system(string
);
334 /* convert the string into an array of arguments */
335 /* we need to take care of double quotes !! */
336 args
= string_to_args(string
, &num_args
);
340 fprintf(stderr
, "%s: ", args
[0]);
343 fprintf(stderr
, " {%s}", arg
[0]);
346 fprintf(stderr
, "\n");
348 result
= spawnvp(P_WAIT
, args
[0], args
);
352 func(closure
, result
? EXEC_CMD_FAIL
: EXEC_CMD_OK
);
356 /* the rest is for Windows NT only */
357 if ((pid
= spawnvp(P_NOWAIT
, argv
[0], argv
)) == -1) {
361 /* Save the operation for execwait() to find. */
362 cmdtab
[slot
].pid
= pid
;
363 cmdtab
[slot
].func
= func
;
364 cmdtab
[slot
].closure
= closure
;
366 /* Wait until we're under the limit of concurrent commands. */
367 /* Don't trust globs.jobs alone. */
368 while (cmdsrunning
>= MAXJOBS
|| cmdsrunning
>= globs
.jobs
) if (!execwait()) break;
373 * execwait() - wait and drive at most one execution completion
380 /* Handle naive make1() which doesn't know if cmds are running. */
381 if (!cmdsrunning
) return 0;
382 if (is_win95
) return 0;
384 /* Pick up process pid and status */
385 while ((w
= wait(&status
)) == -1 && errno
== EINTR
) ;
388 printf("child process(es) lost!\n");
393 /* Find the process in the cmdtab. */
394 for (i
= 0; i
< MAXJOBS
; i
++) if (w
== cmdtab
[i
].pid
) break;
396 printf("waif child found!\n");
399 /* Drive the completion */
400 if (!--cmdsrunning
) signal(SIGINT
, istat
);
402 if (intr
) rstat
= EXEC_CMD_INTR
;
403 else if (w
== -1 || status
!= 0) rstat
= EXEC_CMD_FAIL
;
404 else rstat
= EXEC_CMD_OK
;
408 (*cmdtab
[i
].func
)(cmdtab
[i
].closure
, rstat
);
414 # if !defined( __BORLANDC__ )
415 static int my_wait (int *status
) {
416 int i
, num_active
= 0;
417 DWORD exitcode
, waitcode
;
418 static HANDLE
*active_handles
= 0;
420 if (!active_handles
) active_handles
= (HANDLE
*)malloc(globs
.jobs
*sizeof(HANDLE
));
421 /* first see if any non-waited-for processes are dead, and return if so. */
422 for (i
= 0; i
< globs
.jobs
; i
++) {
424 if (GetExitCodeProcess((HANDLE
)cmdtab
[i
].pid
, &exitcode
)) {
425 if (exitcode
== STILL_ACTIVE
) {
426 active_handles
[num_active
++] = (HANDLE
)cmdtab
[i
].pid
;
428 CloseHandle((HANDLE
)cmdtab
[i
].pid
);
429 *status
= (int)((exitcode
& 0xff) << 8);
430 return cmdtab
[i
].pid
;
435 /* if a child exists, wait for it to die */
440 waitcode
= WaitForMultipleObjects(num_active
, active_handles
, FALSE
, INFINITE
);
441 if (waitcode
!= WAIT_FAILED
) {
442 if (waitcode
>= WAIT_ABANDONED_0
&& waitcode
< WAIT_ABANDONED_0
+ num_active
) i
= waitcode
-WAIT_ABANDONED_0
;
443 else i
= waitcode
-WAIT_OBJECT_0
;
444 if (GetExitCodeProcess(active_handles
[i
], &exitcode
)) {
445 CloseHandle(active_handles
[i
]);
446 *status
= (int)((exitcode
& 0xff) << 8);
447 return (int)active_handles
[i
];
452 errno
= GetLastError();
456 # endif /* !__BORLANDC__ */
459 # endif /* USE_EXECNT */