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().
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
34 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
37 * execcmd() - launch an async command execution
38 * execwait() - wait and drive at most one execution completion
41 * onintr() - bump intr to note command interruption
53 # define WIN32_LEAN_AND_MEAN
54 # include <windows.h> /* do the ugly deed */
57 static int my_wait (int *status
);
61 //extern int execvp (const char *file, const char *argv[]);
64 static int cmdsrunning
= 0;
65 static void (*istat
) (int);
68 int pid
; /* on win32, a real process handle */
69 void (*func
) (void *closure
, int status
);
74 } cmdtab
[MAXJOBS
] = {{0}};
78 * onintr() - bump intr to note command interruption
80 static void onintr (int disp
) {
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
) {
92 const char *argv
[MAXARGC
+1]; /* +1 for NULL */
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
); }
101 if (!cmdtab
[slot
].tempfile
) {
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
) {
121 /* write command to bat file */
122 f
= fopen(cmdtab
[slot
].tempfile
, "w");
125 string
= cmdtab
[slot
].tempfile
;
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) */
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
;
150 argv
[1] = "/Q/C"; /* anything more is non-portable */
158 /* catch interrupts whenever commands are running */
159 if (!cmdsrunning
++) istat
= signal(SIGINT
, onintr
);
160 /* start the command */
162 if ((pid
= spawnvp(P_NOWAIT
, argv
[0], argv
)) == -1) { perror("spawn"); exit(EXITBAD
); }
165 if ((pid
= fork()) == 0) { execvp(argv
[0], argv
); _exit(127); }
167 if ((pid
= vfork()) == 0) { execvp(argv
[0], (void *)argv
); _exit(127); }
169 if (pid
== -1) { perror("vfork"); exit(EXITBAD
); }
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) {
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
) ;
194 printf("child process(es) lost!\n");
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
); }
202 /* clear the temp file */
203 unlink(cmdtab
[i
].tempfile
);
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
;
211 (*cmdtab
[i
].func
)(cmdtab
[i
].closure
, rstat
);
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
) {
226 if (GetExitCodeProcess((HANDLE
)cmdtab
[i
].pid
, &exitcode
)) {
227 if (exitcode
== STILL_ACTIVE
) {
228 active_handles
[num_active
++] = (HANDLE
)cmdtab
[i
].pid
;
230 CloseHandle((HANDLE
)cmdtab
[i
].pid
);
231 *status
= (int)((exitcode
&0xff)<<8);
232 return cmdtab
[i
].pid
;
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
];
252 errno
= GetLastError();
255 #endif /* USE_MYWAIT */