1 /*
2 * Copyright 1993, 1995 Christopher Seiwald.
3 *
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
6
7 /*
8 * execunix.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS
9 *
10 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
11 * The default is:
12 *
13 * /bin/sh -c % [ on UNIX/AmigaOS ]
14 * cmd.exe /c % [ on OS2/WinNT ]
15 *
16 * Each word must be an individual element in a jam variable value.
17 *
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
21 * argument.
22 *
23 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
24 *
25 * External routines:
26 * execcmd() - launch an async command execution
27 * execwait() - wait and drive at most one execution completion
28 *
29 * Internal routines:
30 * onintr() - bump intr to note command interruption
31 *
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
39 */
40
41 # include "jam.h"
42 # include "lists.h"
43 # include "execcmd.h"
44 # include <errno.h>
45
46 # ifdef USE_EXECUNIX
47
48
49 # ifdef OS_FREEBSD
50 # include <unistd.h>
51 # include <sys/types.h>
52 # include <sys/wait.h>
53 # endif
54
55 # ifdef OS_OS2
56 # define USE_EXECNT
57 # include <process.h>
58 # endif
59
60 # ifdef OS_NT
61 # define USE_EXECNT
62 # include <process.h>
63 # define WIN32_LEAN_AND_MEAN
64 # include <windows.h> /* do the ugly deed */
65 # define USE_MYWAIT
66 # if !defined( __BORLANDC__ )
67 # define wait my_wait
68 static int my_wait( int *status );
69 # endif
70 # endif
71
72 static int intr = 0;
73 static int cmdsrunning = 0;
74 static void (*istat)( int );
75
76 static struct
77 {
78 int pid; /* on win32, a real process handle */
79 void (*func)( void *closure, int status );
80 void *closure;
81
82 # ifdef USE_EXECNT
83 char *tempfile;
84 # endif
85
86 } cmdtab[ MAXJOBS ] = {{0}};
87
88 /*
89 * onintr() - bump intr to note command interruption
90 */
91
92 void
onintr(int disp)93 onintr( int disp )
94 {
95 (void)disp;
96
97 intr++;
98 printf( "...interrupted\n" );
99 }
100
101 /*
102 * execcmd() - launch an async command execution
103 */
104
105 void
execcmd(char * string,void (* func)(void * closure,int status),void * closure,LIST * shell)106 execcmd(
107 char *string,
108 void (*func)( void *closure, int status ),
109 void *closure,
110 LIST *shell )
111 {
112 int pid;
113 int slot;
114 const char *argv[ MAXARGC + 1 ]; /* +1 for NULL */
115
116 # ifdef USE_EXECNT
117 char *p;
118 # endif
119
120 /* Find a slot in the running commands table for this one. */
121
122 for( slot = 0; slot < MAXJOBS; slot++ )
123 if( !cmdtab[ slot ].pid )
124 break;
125
126 if( slot == MAXJOBS )
127 {
128 printf( "no slots for child!\n" );
129 exit( EXITBAD );
130 }
131
132 # ifdef USE_EXECNT
133 if( !cmdtab[ slot ].tempfile )
134 {
135 char *tempdir;
136
137 if( !( tempdir = getenv( "TEMP" ) ) &&
138 !( tempdir = getenv( "TMP" ) ) )
139 tempdir = "\\temp";
140
141 /* +32 is room for \jamXXXXXtSS.bat (at least) */
142
143 cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 32 );
144
145 sprintf( cmdtab[ slot ].tempfile, "%s\\jam%dt%d.bat",
146 tempdir, GetCurrentProcessId(), slot );
147 }
148
149 /* Trim leading, ending white space */
150
151 while( isspace( *string ) )
152 ++string;
153
154 p = strchr( string, '\n' );
155
156 while( p && isspace( *p ) )
157 ++p;
158
159 /* If multi line, or too long, or JAMSHELL is set, write to bat file. */
160 /* Otherwise, exec directly. */
161 /* Frankly, if it is a single long line I don't think the */
162 /* command interpreter will do any better -- it will fail. */
163
164 if( p && *p || strlen( string ) > MAXLINE || shell )
165 {
166 FILE *f;
167
168 /* Write command to bat file. */
169
170 f = fopen( cmdtab[ slot ].tempfile, "w" );
171 fputs( string, f );
172 fclose( f );
173
174 string = cmdtab[ slot ].tempfile;
175 }
176 # endif
177
178 /* Forumulate argv */
179 /* If shell was defined, be prepared for % and ! subs. */
180 /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */
181
182 if( shell )
183 {
184 int i;
185 char jobno[4];
186 int gotpercent = 0;
187
188 sprintf( jobno, "%d", slot + 1 );
189
190 for( i = 0; shell && i < MAXARGC; i++, shell = list_next( shell ) )
191 {
192 switch( shell->string[0] )
193 {
194 case '%': argv[i] = string; gotpercent++; break;
195 case '!': argv[i] = jobno; break;
196 default: argv[i] = shell->string;
197 }
198 if( DEBUG_EXECCMD )
199 printf( "argv[%d] = '%s'\n", i, argv[i] );
200 }
201
202 if( !gotpercent )
203 argv[i++] = string;
204
205 argv[i] = 0;
206 }
207 else
208 {
209 # ifdef USE_EXECNT
210 argv[0] = "cmd.exe";
211 argv[1] = "/Q/C"; /* anything more is non-portable */
212 # else
213 argv[0] = "/bin/sh";
214 argv[1] = "-c";
215 # endif
216 argv[2] = string;
217 argv[3] = 0;
218 }
219
220 /* Catch interrupts whenever commands are running. */
221
222 if( !cmdsrunning++ )
223 istat = signal( SIGINT, onintr );
224
225 /* Start the command */
226
227 # ifdef USE_EXECNT
228 if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 )
229 {
230 perror( "spawn" );
231 exit( EXITBAD );
232 }
233 # else
234 # ifdef NO_VFORK
235 if ((pid = fork()) == 0)
236 {
237 execvp( argv[0], argv );
238 _exit(127);
239 }
240 # else
241 if ((pid = vfork()) == 0)
242 {
243 execvp( argv[0], (char * const *)argv );
244 _exit(127);
245 }
246 # endif
247
248 if( pid == -1 )
249 {
250 perror( "vfork" );
251 exit( EXITBAD );
252 }
253 # endif
254 /* Save the operation for execwait() to find. */
255
256 cmdtab[ slot ].pid = pid;
257 cmdtab[ slot ].func = func;
258 cmdtab[ slot ].closure = closure;
259
260 /* Wait until we're under the limit of concurrent commands. */
261 /* Don't trust globs.jobs alone. */
262
263 while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs )
264 if( !execwait() )
265 break;
266 }
267
268 /*
269 * execwait() - wait and drive at most one execution completion
270 */
271
272 int
execwait()273 execwait()
274 {
275 int i;
276 int status, w;
277 int rstat;
278
279 /* Handle naive make1() which doesn't know if cmds are running. */
280
281 if( !cmdsrunning )
282 return 0;
283
284 /* Pick up process pid and status */
285
286 while( ( w = wait( &status ) ) == -1 && errno == EINTR )
287 ;
288
289 if( w == -1 )
290 {
291 printf( "child process(es) lost!\n" );
292 perror("wait");
293 exit( EXITBAD );
294 }
295
296 /* Find the process in the cmdtab. */
297
298 for( i = 0; i < MAXJOBS; i++ )
299 if( w == cmdtab[ i ].pid )
300 break;
301
302 if( i == MAXJOBS )
303 {
304 printf( "waif child found!\n" );
305 exit( EXITBAD );
306 }
307
308 # ifdef USE_EXECNT
309 /* Clear the temp file */
310
311 unlink( cmdtab[ i ].tempfile );
312 # endif
313
314 /* Drive the completion */
315
316 if( !--cmdsrunning )
317 signal( SIGINT, istat );
318
319 if( intr )
320 rstat = EXEC_CMD_INTR;
321 else if( w == -1 || status != 0 )
322 rstat = EXEC_CMD_FAIL;
323 else
324 rstat = EXEC_CMD_OK;
325
326 cmdtab[ i ].pid = 0;
327
328 (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat );
329
330 return 1;
331 }
332
333 # ifdef USE_MYWAIT
334
335 static int
my_wait(int * status)336 my_wait( int *status )
337 {
338 int i, num_active = 0;
339 DWORD exitcode, waitcode;
340 static HANDLE *active_handles = 0;
341
342 if (!active_handles)
343 active_handles = (HANDLE *)malloc(globs.jobs * sizeof(HANDLE) );
344
345 /* first see if any non-waited-for processes are dead,
346 * and return if so.
347 */
348 for ( i = 0; i < globs.jobs; i++ ) {
349 if ( cmdtab[i].pid ) {
350 if ( GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode) ) {
351 if ( exitcode == STILL_ACTIVE )
352 active_handles[num_active++] = (HANDLE)cmdtab[i].pid;
353 else {
354 CloseHandle((HANDLE)cmdtab[i].pid);
355 *status = (int)((exitcode & 0xff) << 8);
356 return cmdtab[i].pid;
357 }
358 }
359 else
360 goto FAILED;
361 }
362 }
363
364 /* if a child exists, wait for it to die */
365 if ( !num_active ) {
366 errno = ECHILD;
367 return -1;
368 }
369 waitcode = WaitForMultipleObjects( num_active,
370 active_handles,
371 FALSE,
372 INFINITE );
373 if ( waitcode != WAIT_FAILED ) {
374 if ( waitcode >= WAIT_ABANDONED_0
375 && waitcode < WAIT_ABANDONED_0 + num_active )
376 i = waitcode - WAIT_ABANDONED_0;
377 else
378 i = waitcode - WAIT_OBJECT_0;
379 if ( GetExitCodeProcess(active_handles[i], &exitcode) ) {
380 CloseHandle(active_handles[i]);
381 *status = (int)((exitcode & 0xff) << 8);
382 return (int)active_handles[i];
383 }
384 }
385
386 FAILED:
387 errno = GetLastError();
388 return -1;
389
390 }
391
392 # endif /* USE_MYWAIT */
393
394 # endif /* USE_EXECUNIX */
395