it's now safe (i hope) to include Jambase.configure multiple times
[k8jam.git] / src / execcmd.c
blob93207b1c9c7a9cfa39d63c2f85b41ca37b782f15
1 /*
2 * Copyright 1993, 1995 Christopher Seiwald.
3 * This file is part of Jam - see jam.c for Copyright information.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * execcmd.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS
21 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
22 * The default is:
24 * /bin/sh -c % [ on UNIX/AmigaOS ]
25 * cmd.exe /c % [ on OS2/WinNT ]
27 * Each word must be an individual element in a jam variable value.
29 * In $(JAMSHELL), % expands to the command string and ! expands to
30 * the slot number (starting at 1) for multiprocess (-j) invocations.
31 * If $(JAMSHELL) doesn't include a %, it is tacked on as the last
32 * argument.
34 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
36 * External routines:
37 * execcmd() - launch an async command execution
38 * execwait() - wait and drive at most one execution completion
40 * Internal routines:
41 * onintr() - bump intr to note command interruption
43 #include <errno.h>
45 #include "jam.h"
46 #include "lists.h"
47 #include "execcmd.h"
50 #ifdef OS_NT
51 # define USE_EXECNT
52 # include <process.h>
53 # define WIN32_LEAN_AND_MEAN
54 # include <windows.h> /* do the ugly deed */
55 # define USE_MYWAIT
56 # define wait my_wait
57 static int my_wait (int *status);
58 #endif
61 //extern int execvp (const char *file, const char *argv[]);
63 static int intr = 0;
64 static int cmdsrunning = 0;
65 static void (*istat) (int);
67 static struct {
68 int pid; /* on win32, a real process handle */
69 void (*func) (void *closure, int status);
70 void *closure;
71 #ifdef USE_EXECNT
72 char *tempfile;
73 #endif
74 } cmdtab[MAXJOBS] = {{0}};
78 * onintr() - bump intr to note command interruption
80 static void onintr (int disp) {
81 ++intr;
82 printf("...interrupted\n");
87 * execcmd() - launch an async command execution
89 void execcmd (const char *string, void (*func) (void *closure, int status), void *closure, LIST *shell) {
90 int pid;
91 int slot;
92 const char *argv[MAXARGC+1]; /* +1 for NULL */
93 #ifdef USE_EXECNT
94 char *p;
95 #endif
97 /* find a slot in the running commands table for this one */
98 for (slot = 0; slot < MAXJOBS; ++slot) if (!cmdtab[slot].pid) break;
99 if (slot == MAXJOBS) { printf("no slots for child!\n"); exit(EXITBAD); }
100 #ifdef USE_EXECNT
101 if (!cmdtab[slot].tempfile) {
102 char *tempdir;
103 int ssz;
105 if (!(tempdir = getenv("TEMP")) && !(tempdir = getenv("TMP"))) tempdir = "\\temp";
106 /* +32 is room for \jamXXXXXtSS.bat (at least) */
107 ssz = strlen(tempdir)+32;
108 cmdtab[slot].tempfile = malloc(ssz);
109 snprintf(cmdtab[slot].tempfile, ssz, "%s\\jam%ut%d.bat", tempdir, (unsigned int)(GetCurrentProcessId()), slot);
111 /* trim leading, ending white space */
112 while (isspace(*string)) ++string;
113 p = strchr(string, '\n');
114 while (p && isspace(*p)) ++p;
115 /* If multi line, or too long, or JAMSHELL is set, write to bat file, otherwise, exec directly */
116 /* frankly, if it is a single long line I don't think the */
117 /* command interpreter will do any better -- it will fail */
118 if ((p && *p) || strlen(string) > MAXLINE || shell) {
119 FILE *f;
121 /* write command to bat file */
122 f = fopen(cmdtab[slot].tempfile, "w");
123 fputs(string, f);
124 fclose(f);
125 string = cmdtab[slot].tempfile;
127 #endif
128 /* forumulate argv */
129 /* if shell was defined, be prepared for % and ! subs, */
130 /* otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT) */
131 if (shell) {
132 int i;
133 char jobno[8];
134 int gotpercent = 0;
136 snprintf(jobno, sizeof(jobno), "%d", slot+1);
137 for (i = 0; shell && i < MAXARGC; ++i, shell = list_next(shell)) {
138 switch (shell->string[0]) {
139 case '%': argv[i] = string; ++gotpercent; break;
140 case '!': argv[i] = jobno; break;
141 default: argv[i] = shell->string; break;
143 if (DEBUG_EXECCMD) printf("argv[%d] = '%s'\n", i, argv[i]);
145 if (!gotpercent) argv[i++] = string;
146 argv[i] = 0;
147 } else {
148 #ifdef USE_EXECNT
149 argv[0] = "cmd.exe";
150 argv[1] = "/Q/C"; /* anything more is non-portable */
151 #else
152 argv[0] = "/bin/sh";
153 argv[1] = "-c";
154 #endif
155 argv[2] = string;
156 argv[3] = 0;
158 /* catch interrupts whenever commands are running */
159 if (!cmdsrunning++) istat = signal(SIGINT, onintr);
160 /* start the command */
161 #ifdef USE_EXECNT
162 if ((pid = spawnvp(P_NOWAIT, argv[0], argv)) == -1) { perror("spawn"); exit(EXITBAD); }
163 #else
164 # ifdef NO_VFORK
165 if ((pid = fork()) == 0) { execvp(argv[0], argv); _exit(127); }
166 # else
167 if ((pid = vfork()) == 0) { execvp(argv[0], (void *)argv); _exit(127); }
168 # endif
169 if (pid == -1) { perror("vfork"); exit(EXITBAD); }
170 #endif
171 /* save the operation for execwait() to find */
172 cmdtab[slot].pid = pid;
173 cmdtab[slot].func = func;
174 cmdtab[slot].closure = closure;
175 /* wait until we're under the limit of concurrent commands */
176 /* don't trust globs.jobs alone */
177 while (cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs) if (!execwait()) break;
182 * execwait() - wait and drive at most one execution completion
184 int execwait (void) {
185 int i;
186 int status, w;
187 int rstat;
189 /* handle naive make1() which doesn't know if cmds are running */
190 if (!cmdsrunning) return 0;
191 /* pick up process pid and status */
192 while ((w = wait(&status)) == -1 && errno == EINTR) ;
193 if (w == -1) {
194 printf("child process(es) lost!\n");
195 perror("wait");
196 exit(EXITBAD);
198 /* find the process in the cmdtab */
199 for (i = 0; i < MAXJOBS; ++i) if (w == cmdtab[i].pid) break;
200 if (i == MAXJOBS) { printf("waif child found!\n"); exit(EXITBAD); }
201 #ifdef USE_EXECNT
202 /* clear the temp file */
203 unlink(cmdtab[i].tempfile);
204 #endif
205 /* drive the completion */
206 if (!--cmdsrunning) signal(SIGINT, istat);
207 if (intr) rstat = EXEC_CMD_INTR;
208 else if (w == -1 || status != 0) rstat = EXEC_CMD_FAIL;
209 else rstat = EXEC_CMD_OK;
210 cmdtab[i].pid = 0;
211 (*cmdtab[i].func)(cmdtab[i].closure, rstat);
212 return 1;
216 #ifdef USE_MYWAIT
217 static int my_wait (int *status) {
218 int i, num_active = 0;
219 DWORD exitcode, waitcode;
220 static HANDLE *active_handles = 0;
222 if (!active_handles) active_handles = (HANDLE *)malloc(globs.jobs*sizeof(HANDLE));
223 /* first see if any non-waited-for processes are dead, and return if so. */
224 for (i = 0; i < globs.jobs; ++i) {
225 if (cmdtab[i].pid) {
226 if (GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode)) {
227 if (exitcode == STILL_ACTIVE) {
228 active_handles[num_active++] = (HANDLE)cmdtab[i].pid;
229 } else {
230 CloseHandle((HANDLE)cmdtab[i].pid);
231 *status = (int)((exitcode&0xff)<<8);
232 return cmdtab[i].pid;
234 } else {
235 goto failed;
239 /* if a child exists, wait for it to die */
240 if (!num_active) { errno = ECHILD; return -1; }
241 waitcode = WaitForMultipleObjects(num_active, active_handles, FALSE, INFINITE);
242 if (waitcode != WAIT_FAILED) {
243 if (waitcode >= WAIT_ABANDONED_0 && waitcode < WAIT_ABANDONED_0+num_active) i = waitcode-WAIT_ABANDONED_0;
244 else i = waitcode-WAIT_OBJECT_0;
245 if (GetExitCodeProcess(active_handles[i], &exitcode)) {
246 CloseHandle(active_handles[i]);
247 *status = (int)((exitcode&0xff)<<8);
248 return (int)active_handles[i];
251 failed:
252 errno = GetLastError();
253 return -1;
255 #endif /* USE_MYWAIT */