cosmetix
[k8jam.git] / src / execcmd.c
blobf1660004410ce97cd4e715a363685d0630de44a5
1 /*
2 * Copyright 1993, 1995 Christopher Seiwald.
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
6 /*
7 * execcmd.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS
9 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
10 * The default is:
12 * /bin/sh -c % [ on UNIX/AmigaOS ]
13 * cmd.exe /c % [ on OS2/WinNT ]
15 * Each word must be an individual element in a jam variable value.
17 * In $(JAMSHELL), % expands to the command string and ! expands to
18 * the slot number (starting at 1) for multiprocess (-j) invocations.
19 * If $(JAMSHELL) doesn't include a %, it is tacked on as the last
20 * argument.
22 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
24 * External routines:
25 * execcmd() - launch an async command execution
26 * execwait() - wait and drive at most one execution completion
28 * Internal routines:
29 * onintr() - bump intr to note command interruption
31 * 04/08/94 (seiwald) - Coherent/386 support added.
32 * 05/04/94 (seiwald) - async multiprocess interface
33 * 01/22/95 (seiwald) - $(JAMSHELL) support
34 * 06/02/97 (gsar) - full async multiprocess support for Win32
35 * 01/20/00 (seiwald) - Upgraded from K&R to ANSI C
36 * 11/04/02 (seiwald) - const-ing for string literals
37 * 12/27/02 (seiwald) - grist .bat file with pid for system uniqueness
39 #include <errno.h>
41 #include "jam.h"
42 #include "lists.h"
43 #include "execcmd.h"
46 #ifdef OS_NT
47 # define USE_EXECNT
48 # include <process.h>
49 # define WIN32_LEAN_AND_MEAN
50 # include <windows.h> /* do the ugly deed */
51 # define USE_MYWAIT
52 # define wait my_wait
53 static int my_wait (int *status);
54 #endif
57 //extern int execvp (const char *file, const char *argv[]);
59 static int intr = 0;
60 static int cmdsrunning = 0;
61 static void (*istat) (int);
63 static struct {
64 int pid; /* on win32, a real process handle */
65 void (*func) (void *closure, int status);
66 void *closure;
67 #ifdef USE_EXECNT
68 char *tempfile;
69 #endif
70 } cmdtab[MAXJOBS] = {{0}};
74 * onintr() - bump intr to note command interruption
76 static void onintr (int disp) {
77 ++intr;
78 printf("...interrupted\n");
83 * execcmd() - launch an async command execution
85 void execcmd (const char *string, void (*func) (void *closure, int status), void *closure, LIST *shell) {
86 int pid;
87 int slot;
88 const char *argv[MAXARGC+1]; /* +1 for NULL */
89 #ifdef USE_EXECNT
90 char *p;
91 #endif
93 /* find a slot in the running commands table for this one */
94 for (slot = 0; slot < MAXJOBS; ++slot) if (!cmdtab[slot].pid) break;
95 if (slot == MAXJOBS) { printf("no slots for child!\n"); exit(EXITBAD); }
96 #ifdef USE_EXECNT
97 if (!cmdtab[slot].tempfile) {
98 char *tempdir;
99 int ssz;
101 if (!(tempdir = getenv("TEMP")) && !(tempdir = getenv("TMP"))) tempdir = "\\temp";
102 /* +32 is room for \jamXXXXXtSS.bat (at least) */
103 ssz = strlen(tempdir)+32;
104 cmdtab[slot].tempfile = malloc(ssz);
105 snprintf(cmdtab[slot].tempfile, ssz, "%s\\jam%ut%d.bat", tempdir, (unsigned int)(GetCurrentProcessId()), slot);
107 /* trim leading, ending white space */
108 while (isspace(*string)) ++string;
109 p = strchr(string, '\n');
110 while (p && isspace(*p)) ++p;
111 /* If multi line, or too long, or JAMSHELL is set, write to bat file, otherwise, exec directly */
112 /* frankly, if it is a single long line I don't think the */
113 /* command interpreter will do any better -- it will fail */
114 if ((p && *p) || strlen(string) > MAXLINE || shell) {
115 FILE *f;
117 /* write command to bat file */
118 f = fopen(cmdtab[slot].tempfile, "w");
119 fputs(string, f);
120 fclose(f);
121 string = cmdtab[slot].tempfile;
123 #endif
124 /* forumulate argv */
125 /* if shell was defined, be prepared for % and ! subs, */
126 /* otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT) */
127 if (shell) {
128 int i;
129 char jobno[8];
130 int gotpercent = 0;
132 snprintf(jobno, sizeof(jobno), "%d", slot+1);
133 for (i = 0; shell && i < MAXARGC; ++i, shell = list_next(shell)) {
134 switch (shell->string[0]) {
135 case '%': argv[i] = string; ++gotpercent; break;
136 case '!': argv[i] = jobno; break;
137 default: argv[i] = shell->string; break;
139 if (DEBUG_EXECCMD) printf("argv[%d] = '%s'\n", i, argv[i]);
141 if (!gotpercent) argv[i++] = string;
142 argv[i] = 0;
143 } else {
144 #ifdef USE_EXECNT
145 argv[0] = "cmd.exe";
146 argv[1] = "/Q/C"; /* anything more is non-portable */
147 #else
148 argv[0] = "/bin/sh";
149 argv[1] = "-c";
150 #endif
151 argv[2] = string;
152 argv[3] = 0;
154 /* catch interrupts whenever commands are running */
155 if (!cmdsrunning++) istat = signal(SIGINT, onintr);
156 /* start the command */
157 #ifdef USE_EXECNT
158 if ((pid = spawnvp(P_NOWAIT, argv[0], argv)) == -1) { perror("spawn"); exit(EXITBAD); }
159 #else
160 # ifdef NO_VFORK
161 if ((pid = fork()) == 0) { execvp(argv[0], argv); _exit(127); }
162 # else
163 if ((pid = vfork()) == 0) { execvp(argv[0], (void *)argv); _exit(127); }
164 # endif
165 if (pid == -1) { perror("vfork"); exit(EXITBAD); }
166 #endif
167 /* save the operation for execwait() to find */
168 cmdtab[slot].pid = pid;
169 cmdtab[slot].func = func;
170 cmdtab[slot].closure = closure;
171 /* wait until we're under the limit of concurrent commands */
172 /* don't trust globs.jobs alone */
173 while (cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs) if (!execwait()) break;
178 * execwait() - wait and drive at most one execution completion
180 int execwait (void) {
181 int i;
182 int status, w;
183 int rstat;
185 /* handle naive make1() which doesn't know if cmds are running */
186 if (!cmdsrunning) return 0;
187 /* pick up process pid and status */
188 while ((w = wait(&status)) == -1 && errno == EINTR) ;
189 if (w == -1) {
190 printf("child process(es) lost!\n");
191 perror("wait");
192 exit(EXITBAD);
194 /* find the process in the cmdtab */
195 for (i = 0; i < MAXJOBS; ++i) if (w == cmdtab[i].pid) break;
196 if (i == MAXJOBS) { printf("waif child found!\n"); exit(EXITBAD); }
197 #ifdef USE_EXECNT
198 /* clear the temp file */
199 unlink(cmdtab[i].tempfile);
200 #endif
201 /* drive the completion */
202 if (!--cmdsrunning) signal(SIGINT, istat);
203 if (intr) rstat = EXEC_CMD_INTR;
204 else if (w == -1 || status != 0) rstat = EXEC_CMD_FAIL;
205 else rstat = EXEC_CMD_OK;
206 cmdtab[i].pid = 0;
207 (*cmdtab[i].func)(cmdtab[i].closure, rstat);
208 return 1;
212 #ifdef USE_MYWAIT
213 static int my_wait (int *status) {
214 int i, num_active = 0;
215 DWORD exitcode, waitcode;
216 static HANDLE *active_handles = 0;
218 if (!active_handles) active_handles = (HANDLE *)malloc(globs.jobs*sizeof(HANDLE));
219 /* first see if any non-waited-for processes are dead, and return if so. */
220 for (i = 0; i < globs.jobs; ++i) {
221 if (cmdtab[i].pid) {
222 if (GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode)) {
223 if (exitcode == STILL_ACTIVE) {
224 active_handles[num_active++] = (HANDLE)cmdtab[i].pid;
225 } else {
226 CloseHandle((HANDLE)cmdtab[i].pid);
227 *status = (int)((exitcode&0xff)<<8);
228 return cmdtab[i].pid;
230 } else {
231 goto failed;
235 /* if a child exists, wait for it to die */
236 if (!num_active) { errno = ECHILD; return -1; }
237 waitcode = WaitForMultipleObjects(num_active, active_handles, FALSE, INFINITE);
238 if (waitcode != WAIT_FAILED) {
239 if (waitcode >= WAIT_ABANDONED_0 && waitcode < WAIT_ABANDONED_0+num_active) i = waitcode-WAIT_ABANDONED_0;
240 else i = waitcode-WAIT_OBJECT_0;
241 if (GetExitCodeProcess(active_handles[i], &exitcode)) {
242 CloseHandle(active_handles[i]);
243 *status = (int)((exitcode&0xff)<<8);
244 return (int)active_handles[i];
247 failed:
248 errno = GetLastError();
249 return -1;
251 #endif /* USE_MYWAIT */