2 * Copyright 1993, 1995 Christopher Seiwald.
4 * This file is part of Jam - see jam.c for Copyright information.
7 * execcmd.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS
9 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
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
22 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
25 * execcmd() - launch an async command execution
26 * execwait() - wait and drive at most one execution completion
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
54 # define WIN32_LEAN_AND_MEAN
55 # include <windows.h> /* do the ugly deed */
58 static int my_wait (int *status
);
62 //extern int execvp (const char *file, const char *argv[]);
65 static int cmdsrunning
= 0;
66 static void (*istat
) (int);
69 int pid
; /* on win32, a real process handle */
70 void (*func
) (void *closure
, int status
);
75 } cmdtab
[MAXJOBS
] = {{0}};
79 * onintr() - bump intr to note command interruption
81 void onintr (int disp
) {
83 printf("...interrupted\n");
88 * execcmd() - launch an async command execution
90 void execcmd (const char *string
, void (*func
) (void *closure
, int status
), void *closure
, LIST
*shell
) {
93 const char *argv
[MAXARGC
+1]; /* +1 for NULL */
98 /* find a slot in the running commands table for this one */
99 for (slot
= 0; slot
< MAXJOBS
; ++slot
) if (!cmdtab
[slot
].pid
) break;
100 if (slot
== MAXJOBS
) { printf("no slots for child!\n"); exit(EXITBAD
); }
102 if (!cmdtab
[slot
].tempfile
) {
105 if (!(tempdir
= getenv("TEMP")) && !(tempdir
= getenv("TMP"))) tempdir
= "\\temp";
106 /* +32 is room for \jamXXXXXtSS.bat (at least) */
107 cmdtab
[slot
].tempfile
= malloc(strlen(tempdir
)+32);
108 sprintf(cmdtab
[slot
].tempfile
, "%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
) {
120 /* write command to bat file */
121 f
= fopen(cmdtab
[slot
].tempfile
, "w");
124 string
= cmdtab
[slot
].tempfile
;
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) */
135 sprintf(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
;
149 argv
[1] = "/Q/C"; /* anything more is non-portable */
157 /* catch interrupts whenever commands are running */
158 if (!cmdsrunning
++) istat
= signal(SIGINT
, onintr
);
159 /* start the command */
161 if ((pid
= spawnvp(P_NOWAIT
, argv
[0], argv
)) == -1) { perror("spawn"); exit(EXITBAD
); }
164 if ((pid
= fork()) == 0) { execvp(argv
[0], argv
); _exit(127); }
166 if ((pid
= vfork()) == 0) { execvp(argv
[0], (void *)argv
); _exit(127); }
168 if (pid
== -1) { perror("vfork"); exit(EXITBAD
); }
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) {
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
) ;
193 printf("child process(es) lost!\n");
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
); }
201 /* clear the temp file */
202 unlink(cmdtab
[i
].tempfile
);
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
;
210 (*cmdtab
[i
].func
)(cmdtab
[i
].closure
, rstat
);
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
) {
225 if (GetExitCodeProcess((HANDLE
)cmdtab
[i
].pid
, &exitcode
)) {
226 if (exitcode
== STILL_ACTIVE
) {
227 active_handles
[num_active
++] = (HANDLE
)cmdtab
[i
].pid
;
229 CloseHandle((HANDLE
)cmdtab
[i
].pid
);
230 *status
= (int)((exitcode
&0xff)<<8);
231 return cmdtab
[i
].pid
;
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
];
251 errno
= GetLastError();
254 #endif /* USE_MYWAIT */