2 * Copyright 1993, 1995 Christopher Seiwald.
4 * This file is part of Jam - see jam.c for Copyright information.
8 * execunix.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS
10 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
13 * /bin/sh -c % [ on UNIX/AmigaOS ]
14 * cmd.exe /c % [ on OS2/WinNT ]
16 * Each word must be an individual element in a jam variable value.
18 * In $(JAMSHELL), % expands to the command string and ! expands to
19 * the slot number (starting at 1) for multiprocess (-j) invocations.
20 * If $(JAMSHELL) doesn't include a %, it is tacked on as the last
23 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
26 * execcmd() - launch an async command execution
27 * execwait() - wait and drive at most one execution completion
30 * onintr() - bump intr to note command interruption
32 * 04/08/94 (seiwald) - Coherent/386 support added.
33 * 05/04/94 (seiwald) - async multiprocess interface
34 * 01/22/95 (seiwald) - $(JAMSHELL) support
35 * 06/02/97 (gsar) - full async multiprocess support for Win32
36 * 01/20/00 (seiwald) - Upgraded from K&R to ANSI C
37 * 11/04/02 (seiwald) - const-ing for string literals
38 * 12/27/02 (seiwald) - grist .bat file with pid for system uniqueness
55 # define WIN32_LEAN_AND_MEAN
56 # include <windows.h> /* do the ugly deed */
58 # if !defined( __BORLANDC__ )
60 static int my_wait (int *status
);
65 //extern int execvp (const char *file, const char *argv[]);
68 static int cmdsrunning
= 0;
69 static void (*istat
) (int);
72 int pid
; /* on win32, a real process handle */
73 void (*func
) (void *closure
, int status
);
78 } cmdtab
[MAXJOBS
] = {{0}};
82 * onintr() - bump intr to note command interruption
84 void onintr (int disp
) {
86 printf("...interrupted\n");
91 * execcmd() - launch an async command execution
95 void (*func
) (void *closure
, int status
),
101 const char *argv
[MAXARGC
+1]; /* +1 for NULL */
106 /* Find a slot in the running commands table for this one. */
107 for (slot
= 0; slot
< MAXJOBS
; slot
++) if (!cmdtab
[slot
].pid
) break;
108 if (slot
== MAXJOBS
) {
109 printf("no slots for child!\n");
114 if (!cmdtab
[slot
].tempfile
) {
117 if (!(tempdir
= getenv("TEMP")) && !(tempdir
= getenv("TMP"))) tempdir
= "\\temp";
118 /* +32 is room for \jamXXXXXtSS.bat (at least) */
119 cmdtab
[slot
].tempfile
= malloc(strlen(tempdir
)+32);
120 sprintf(cmdtab
[slot
].tempfile
, "%s\\jam%ut%d.bat", tempdir
, (unsigned int)(GetCurrentProcessId()), slot
);
123 /* Trim leading, ending white space */
124 while(isspace(*string
)) ++string
;
125 p
= strchr(string
, '\n');
126 while (p
&& isspace(*p
)) ++p
;
128 /* If multi line, or too long, or JAMSHELL is set, write to bat file. */
129 /* Otherwise, exec directly. */
130 /* Frankly, if it is a single long line I don't think the */
131 /* command interpreter will do any better -- it will fail. */
132 if ((p
&& *p
) || strlen(string
) > MAXLINE
|| shell
) {
135 /* Write command to bat file. */
136 f
= fopen(cmdtab
[slot
].tempfile
, "w");
139 string
= cmdtab
[slot
].tempfile
;
143 /* Forumulate argv */
144 /* If shell was defined, be prepared for % and ! subs. */
145 /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */
151 sprintf(jobno
, "%d", slot
+1);
153 for (i
= 0; shell
&& i
< MAXARGC
; i
++, shell
= list_next(shell
)) {
154 switch (shell
->string
[0]) {
155 case '%': argv
[i
] = string
; gotpercent
++; break;
156 case '!': argv
[i
] = jobno
; break;
157 default: argv
[i
] = shell
->string
;
159 if (DEBUG_EXECCMD
) printf("argv[%d] = '%s'\n", i
, argv
[i
]);
161 if (!gotpercent
) argv
[i
++] = string
;
166 argv
[1] = "/Q/C"; /* anything more is non-portable */
175 /* Catch interrupts whenever commands are running. */
176 if (!cmdsrunning
++) istat
= signal(SIGINT
, onintr
);
178 /* Start the command */
180 if ((pid
= spawnvp(P_NOWAIT
, argv
[0], argv
)) == -1) {
186 if ((pid
= fork()) == 0) {
187 execvp(argv
[0], argv
);
191 if ((pid
= vfork()) == 0) {
192 execvp(argv
[0], (void *)argv
);
201 /* Save the operation for execwait() to find. */
202 cmdtab
[slot
].pid
= pid
;
203 cmdtab
[slot
].func
= func
;
204 cmdtab
[slot
].closure
= closure
;
206 /* Wait until we're under the limit of concurrent commands. */
207 /* Don't trust globs.jobs alone. */
208 while (cmdsrunning
>= MAXJOBS
|| cmdsrunning
>= globs
.jobs
) if (!execwait()) break;
213 * execwait() - wait and drive at most one execution completion
220 /* Handle naive make1() which doesn't know if cmds are running. */
221 if (!cmdsrunning
) return 0;
222 /* Pick up process pid and status */
223 while ((w
= wait(&status
)) == -1 && errno
== EINTR
) ;
225 printf("child process(es) lost!\n");
230 /* Find the process in the cmdtab. */
231 for (i
= 0; i
< MAXJOBS
; i
++) if (w
== cmdtab
[i
].pid
) break;
233 printf("waif child found!\n");
237 /* Clear the temp file */
238 unlink(cmdtab
[i
].tempfile
);
241 /* Drive the completion */
242 if (!--cmdsrunning
) signal(SIGINT
, istat
);
244 if (intr
) rstat
= EXEC_CMD_INTR
;
245 else if (w
== -1 || status
!= 0) rstat
= EXEC_CMD_FAIL
;
246 else rstat
= EXEC_CMD_OK
;
249 (*cmdtab
[i
].func
)(cmdtab
[i
].closure
, rstat
);
256 static int my_wait (int *status
) {
257 int i
, num_active
= 0;
258 DWORD exitcode
, waitcode
;
259 static HANDLE
*active_handles
= 0;
261 if (!active_handles
) active_handles
= (HANDLE
*)malloc(globs
.jobs
*sizeof(HANDLE
));
262 /* first see if any non-waited-for processes are dead, and return if so. */
263 for (i
= 0; i
< globs
.jobs
; i
++) {
265 if (GetExitCodeProcess((HANDLE
)cmdtab
[i
].pid
, &exitcode
)) {
266 if (exitcode
== STILL_ACTIVE
) {
267 active_handles
[num_active
++] = (HANDLE
)cmdtab
[i
].pid
;
269 CloseHandle((HANDLE
)cmdtab
[i
].pid
);
270 *status
= (int)((exitcode
& 0xff) << 8);
271 return cmdtab
[i
].pid
;
276 /* if a child exists, wait for it to die */
281 waitcode
= WaitForMultipleObjects(num_active
, active_handles
, FALSE
, INFINITE
);
282 if (waitcode
!= WAIT_FAILED
) {
283 if (waitcode
>= WAIT_ABANDONED_0
&& waitcode
< WAIT_ABANDONED_0
+num_active
) i
= waitcode
-WAIT_ABANDONED_0
;
284 else i
= waitcode
-WAIT_OBJECT_0
;
285 if (GetExitCodeProcess(active_handles
[i
], &exitcode
)) {
286 CloseHandle(active_handles
[i
]);
287 *status
= (int)((exitcode
& 0xff) << 8);
288 return (int)active_handles
[i
];
293 errno
= GetLastError();
296 # endif /* USE_MYWAIT */
299 # endif /* USE_EXECUNIX */