bugs in Jambase fixed; new vars added (for building Main in separate dirs); C++ vars...
[k8jam.git] / execnt.c
blob0b2fc5912b8b0430f09f131363b1c665c0ef536e
1 /*
2 * Copyright 1993, 1995 Christopher Seiwald.
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
7 # include "jam.h"
8 # include "lists.h"
9 # include "execcmd.h"
10 # include <errno.h>
12 # ifdef USE_EXECNT
14 # define WIN32_LEAN_AND_MEAN
15 # include <windows.h> /* do the ugly deed */
16 # include <process.h>
18 # if !defined( __BORLANDC__ ) && !defined( OS_OS2 )
19 # define wait my_wait
20 static int my_wait (int *status);
21 # endif
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().
28 * The default is:
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
38 * argument.
40 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
42 * External routines:
43 * execcmd() - launch an async command execution
44 * execwait() - wait and drive at most one execution completion
46 * Internal routines:
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
55 static int intr = 0;
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;
64 static struct {
65 int pid; /* on win32, a real process handle */
66 void (*func) (void *closure, int status);
67 void *closure;
68 char *tempfile;
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);
80 is_win95_defined = 1;
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;
91 char *line;
92 char *p;
93 char **arg;
94 char **args;
96 *pcount = 0;
97 /* do not copy trailing newlines, if any */
99 int i;
100 for (i = total-1; i > 0; i--) {
101 if (string[i] != '\n' && string[i] != '\r') break;
102 total--;
106 /* first of all, copy the input string */
107 line = (char *)malloc(total+2);
108 if (!line) return 0;
109 memcpy(line+1, string, total);
110 line[0] = 0;
111 line[total+1] = 0;
113 in_quote = 0;
114 for (p = line+1; p[0]; p++) {
115 switch (p[0]) {
116 case '"': in_quote = !in_quote; break;
117 case ' ': case '\t': if (!in_quote) p[0] = 0;
118 default: ;
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; }
129 arg = args+1;
130 for (p = line; p < line+total+1; p++) if (!p[0] && p[1]) { arg[0] = p+1; arg++; }
131 arg[0] = 0;
132 *pcount = num_args;
133 args[0] = line;
134 return args+1;
138 static void free_args (char **args) {
139 free(args[-1]);
140 free(args-1);
144 /* process a "del" or "erase" command under Windows 95/98 */
145 static int process_del (char *command) {
146 char **arg;
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 */
156 for (;;) {
157 /* skip leading spaces */
158 while (*p && isspace(*p)) p++;
159 /* exit if we encounter an end of string */
160 if (!*p) return 0;
161 /* ignore toggles/flags */
162 if (*p == '/') {
163 p++;
164 while (*p && isalnum(*p)) p++;
165 } else {
166 int in_quote = 0;
167 int wildcard = 0;
168 int go_on = 1;
170 q = p;
171 while (go_on) {
172 switch (*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 */
176 case ' ': case '\t':
177 if (!in_quote) {
178 int len = p-q;
179 int result;
180 char *line;
182 /* q..p-1 contains the delete argument */
183 if (len <= 0) return 1;
184 line = (char *)malloc((len+4+1)*sizeof(char));
185 if (!line) return 1;
187 strncpy(line, "del ", 4);
188 strncpy(line+4, q, len);
189 line[len+4] = '\0';
191 if (wildcard) result = system(line); else result = !DeleteFile(line+4);
193 free(line);
194 if (result) return 1;
195 go_on = 0;
197 break;
198 default: ;
200 p++;
201 } /* while (go_on) */
208 * onintr() - bump intr to note command interruption
210 void onintr (int disp) {
211 intr++;
212 printf("...interrupted\n");
217 * execcmd() - launch an async command execution
219 void execcmd (
220 char *string,
221 void (*func) (void *closure, int status),
222 void *closure,
223 LIST *shell)
225 int pid;
226 int slot;
227 int max_line;
228 char *argv[ MAXARGC+1]; /* +1 for NULL */
229 char *p;
231 if (!is_win95_defined) set_is_win95();
232 /* Find a slot in the running commands table for this one. */
233 if (is_win95) {
234 /* only synchronous spans are supported on Windows 95/98 */
235 slot = 0;
236 } else {
237 for (slot = 0; slot < MAXJOBS; slot++) if (!cmdtab[slot].pid) break;
239 if (slot == MAXJOBS) {
240 printf("no slots for child!\n");
241 exit(EXITBAD);
244 if (!cmdtab[slot].tempfile) {
245 char *tempdir;
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) {
267 FILE *f;
269 /* Write command to bat file. */
270 f = fopen(cmdtab[slot].tempfile, "w");
271 fputs(string, f);
272 fclose(f);
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). */
279 if (shell) {
280 int i;
281 char jobno[4];
282 int gotpercent = 0;
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;
294 argv[i] = 0;
295 } else {
296 /* don't worry, this is ignored on Win95/98, see later.. */
297 argv[0] = "cmd.exe";
298 argv[1] = "/Q/C"; /* anything more is non-portable */
299 argv[2] = string;
300 argv[3] = 0;
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 */
308 if (is_win95) {
309 static const char* hard_coded[] = {
310 "del", "erase", "copy", "mkdir", "rmdir", "cls", "dir",
311 "ren", "rename", "move", 0
314 const char **keyword;
315 int len, spawn = 1;
316 int result;
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);
325 spawn = 0;
326 break;
330 if (spawn) {
331 char **args;
332 int num_args;
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);
337 if (args) {
338 #if 0
339 char **arg;
340 fprintf(stderr, "%s: ", args[0]);
341 arg = args+1;
342 while (arg[0]) {
343 fprintf(stderr, " {%s}", arg[0]);
344 arg++;
346 fprintf(stderr, "\n");
347 #endif
348 result = spawnvp(P_WAIT, args[0], args);
349 free_args(args);
350 } else result = 1;
352 func(closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK);
353 return;
356 /* the rest is for Windows NT only */
357 if ((pid = spawnvp(P_NOWAIT, argv[0], argv)) == -1) {
358 perror("spawn");
359 exit(EXITBAD);
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
375 int execwait () {
376 int i;
377 int status, w;
378 int rstat;
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) ;
387 if (w == -1) {
388 printf("child process(es) lost!\n");
389 perror("wait");
390 exit(EXITBAD);
393 /* Find the process in the cmdtab. */
394 for (i = 0; i < MAXJOBS; i++) if (w == cmdtab[i].pid) break;
395 if (i == MAXJOBS) {
396 printf("waif child found!\n");
397 exit(EXITBAD);
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;
406 cmdtab[i].pid = 0;
408 (*cmdtab[i].func)(cmdtab[i].closure, rstat);
410 return 1;
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++) {
423 if (cmdtab[i].pid) {
424 if (GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode)) {
425 if (exitcode == STILL_ACTIVE) {
426 active_handles[num_active++] = (HANDLE)cmdtab[i].pid;
427 } else {
428 CloseHandle((HANDLE)cmdtab[i].pid);
429 *status = (int)((exitcode & 0xff) << 8);
430 return cmdtab[i].pid;
432 } else goto FAILED;
435 /* if a child exists, wait for it to die */
436 if (!num_active) {
437 errno = ECHILD;
438 return -1;
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];
451 FAILED:
452 errno = GetLastError();
453 return -1;
456 # endif /* !__BORLANDC__ */
459 # endif /* USE_EXECNT */