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