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