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