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
49 # define WIN32_LEAN_AND_MEAN
50 # include <windows.h> /* do the ugly deed */
53 static int my_wait (int *status
);
57 //extern int execvp (const char *file, const char *argv[]);
60 static int cmdsrunning
= 0;
61 static void (*istat
) (int);
64 int pid
; /* on win32, a real process handle */
65 void (*func
) (void *closure
, int status
);
70 } cmdtab
[MAXJOBS
] = {{0}};
74 * onintr() - bump intr to note command interruption
76 static void onintr (int disp
) {
78 printf("...interrupted\n");
83 * execcmd() - launch an async command execution
85 void execcmd (const char *string
, void (*func
) (void *closure
, int status
), void *closure
, LIST
*shell
) {
88 const char *argv
[MAXARGC
+1]; /* +1 for NULL */
93 /* find a slot in the running commands table for this one */
94 for (slot
= 0; slot
< MAXJOBS
; ++slot
) if (!cmdtab
[slot
].pid
) break;
95 if (slot
== MAXJOBS
) { printf("no slots for child!\n"); exit(EXITBAD
); }
97 if (!cmdtab
[slot
].tempfile
) {
101 if (!(tempdir
= getenv("TEMP")) && !(tempdir
= getenv("TMP"))) tempdir
= "\\temp";
102 /* +32 is room for \jamXXXXXtSS.bat (at least) */
103 ssz
= strlen(tempdir
)+32;
104 cmdtab
[slot
].tempfile
= malloc(ssz
);
105 snprintf(cmdtab
[slot
].tempfile
, ssz
, "%s\\jam%ut%d.bat", tempdir
, (unsigned int)(GetCurrentProcessId()), slot
);
107 /* trim leading, ending white space */
108 while (isspace(*string
)) ++string
;
109 p
= strchr(string
, '\n');
110 while (p
&& isspace(*p
)) ++p
;
111 /* If multi line, or too long, or JAMSHELL is set, write to bat file, otherwise, exec directly */
112 /* frankly, if it is a single long line I don't think the */
113 /* command interpreter will do any better -- it will fail */
114 if ((p
&& *p
) || strlen(string
) > MAXLINE
|| shell
) {
117 /* write command to bat file */
118 f
= fopen(cmdtab
[slot
].tempfile
, "w");
121 string
= cmdtab
[slot
].tempfile
;
124 /* forumulate argv */
125 /* if shell was defined, be prepared for % and ! subs, */
126 /* otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT) */
132 snprintf(jobno
, sizeof(jobno
), "%d", slot
+1);
133 for (i
= 0; shell
&& i
< MAXARGC
; ++i
, shell
= list_next(shell
)) {
134 switch (shell
->string
[0]) {
135 case '%': argv
[i
] = string
; ++gotpercent
; break;
136 case '!': argv
[i
] = jobno
; break;
137 default: argv
[i
] = shell
->string
; break;
139 if (DEBUG_EXECCMD
) printf("argv[%d] = '%s'\n", i
, argv
[i
]);
141 if (!gotpercent
) argv
[i
++] = string
;
146 argv
[1] = "/Q/C"; /* anything more is non-portable */
154 /* catch interrupts whenever commands are running */
155 if (!cmdsrunning
++) istat
= signal(SIGINT
, onintr
);
156 /* start the command */
158 if ((pid
= spawnvp(P_NOWAIT
, argv
[0], argv
)) == -1) { perror("spawn"); exit(EXITBAD
); }
161 if ((pid
= fork()) == 0) { execvp(argv
[0], argv
); _exit(127); }
163 if ((pid
= vfork()) == 0) { execvp(argv
[0], (void *)argv
); _exit(127); }
165 if (pid
== -1) { perror("vfork"); exit(EXITBAD
); }
167 /* save the operation for execwait() to find */
168 cmdtab
[slot
].pid
= pid
;
169 cmdtab
[slot
].func
= func
;
170 cmdtab
[slot
].closure
= closure
;
171 /* wait until we're under the limit of concurrent commands */
172 /* don't trust globs.jobs alone */
173 while (cmdsrunning
>= MAXJOBS
|| cmdsrunning
>= globs
.jobs
) if (!execwait()) break;
178 * execwait() - wait and drive at most one execution completion
180 int execwait (void) {
185 /* handle naive make1() which doesn't know if cmds are running */
186 if (!cmdsrunning
) return 0;
187 /* pick up process pid and status */
188 while ((w
= wait(&status
)) == -1 && errno
== EINTR
) ;
190 printf("child process(es) lost!\n");
194 /* find the process in the cmdtab */
195 for (i
= 0; i
< MAXJOBS
; ++i
) if (w
== cmdtab
[i
].pid
) break;
196 if (i
== MAXJOBS
) { printf("waif child found!\n"); exit(EXITBAD
); }
198 /* clear the temp file */
199 unlink(cmdtab
[i
].tempfile
);
201 /* drive the completion */
202 if (!--cmdsrunning
) signal(SIGINT
, istat
);
203 if (intr
) rstat
= EXEC_CMD_INTR
;
204 else if (w
== -1 || status
!= 0) rstat
= EXEC_CMD_FAIL
;
205 else rstat
= EXEC_CMD_OK
;
207 (*cmdtab
[i
].func
)(cmdtab
[i
].closure
, rstat
);
213 static int my_wait (int *status
) {
214 int i
, num_active
= 0;
215 DWORD exitcode
, waitcode
;
216 static HANDLE
*active_handles
= 0;
218 if (!active_handles
) active_handles
= (HANDLE
*)malloc(globs
.jobs
*sizeof(HANDLE
));
219 /* first see if any non-waited-for processes are dead, and return if so. */
220 for (i
= 0; i
< globs
.jobs
; ++i
) {
222 if (GetExitCodeProcess((HANDLE
)cmdtab
[i
].pid
, &exitcode
)) {
223 if (exitcode
== STILL_ACTIVE
) {
224 active_handles
[num_active
++] = (HANDLE
)cmdtab
[i
].pid
;
226 CloseHandle((HANDLE
)cmdtab
[i
].pid
);
227 *status
= (int)((exitcode
&0xff)<<8);
228 return cmdtab
[i
].pid
;
235 /* if a child exists, wait for it to die */
236 if (!num_active
) { errno
= ECHILD
; return -1; }
237 waitcode
= WaitForMultipleObjects(num_active
, active_handles
, FALSE
, INFINITE
);
238 if (waitcode
!= WAIT_FAILED
) {
239 if (waitcode
>= WAIT_ABANDONED_0
&& waitcode
< WAIT_ABANDONED_0
+num_active
) i
= waitcode
-WAIT_ABANDONED_0
;
240 else i
= waitcode
-WAIT_OBJECT_0
;
241 if (GetExitCodeProcess(active_handles
[i
], &exitcode
)) {
242 CloseHandle(active_handles
[i
]);
243 *status
= (int)((exitcode
&0xff)<<8);
244 return (int)active_handles
[i
];
248 errno
= GetLastError();
251 #endif /* USE_MYWAIT */