execunix.c #20

  • //
  • guest/
  • perforce_software/
  • jam/
  • src/
  • execunix.c
  • View
  • Commits
  • Open Download .zip Download (9 KB)
/*
 * Copyright 1993, 1995 Christopher Seiwald.
 *
 * This file is part of Jam - see jam.c for Copyright information.
 */

/*
 * execunix.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS
 *
 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp().
 * The default is:
 *
 *	/bin/sh -c %		[ on UNIX/AmigaOS ]
 *	cmd.exe /c %		[ on OS2/WinNT ]
 *
 * Each word must be an individual element in a jam variable value.
 *
 * In $(JAMSHELL), % expands to the command string and ! expands to 
 * the slot number (starting at 1) for multiprocess (-j) invocations.
 * If $(JAMSHELL) doesn't include a %, it is tacked on as the last
 * argument.
 *
 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work!
 *
 * External routines:
 *	execcmd() - launch an async command execution
 * 	execwait() - wait and drive at most one execution completion
 *
 * Internal routines:
 *	onintr() - bump intr to note command interruption
 *
 * 04/08/94 (seiwald) - Coherent/386 support added.
 * 05/04/94 (seiwald) - async multiprocess interface
 * 01/22/95 (seiwald) - $(JAMSHELL) support
 * 06/02/97 (gsar)    - full async multiprocess support for Win32
 * 01/20/00 (seiwald) - Upgraded from K&R to ANSI C
 * 11/04/02 (seiwald) - const-ing for string literals
 * 12/27/02 (seiwald) - grist .bat file with pid for system uniqueness
 * 05/06/05 (seiwald) - new execmax() to return max command line len.
 */

# include "jam.h"
# include "lists.h"
# include "execcmd.h"
# include <errno.h>

# ifdef USE_EXECUNIX

# ifdef OS_OS2
# define USE_EXECNT
# include <process.h>
# endif

# ifdef OS_MACOSX
# include <unistd.h>
# endif

# ifdef OS_NT 
# define USE_EXECNT
# include <process.h>
# define WIN32_LEAN_AND_MEAN
# include <windows.h>		/* do the ugly deed */
# define USE_MYWAIT
# if !defined( __BORLANDC__ ) 
# define wait my_wait
static long my_wait( int *status );
# endif
# endif

static int intr = 0;
static int cmdsrunning = 0;
static void (*istat)( int );

static struct
{
	long	pid; /* on win32, a real process handle */
	void	(*func)( void *closure, int status );
	void 	*closure;

# ifdef USE_EXECNT
	char	*tempfile;
# endif

} cmdtab[ MAXJOBS ] = {{0}};

/*
 * onintr() - bump intr to note command interruption
 */

void
onintr( int disp )
{
	intr++;
	printf( "...interrupted\n" );
}

/*
 * execmax() - max permitted string to execcmd()
 */

# ifdef OS_NT

int
execmax()
{
	static int maxline = 0;
	OSVERSIONINFO o;

	if( maxline )
	    return maxline;

	/* Get from OS */

	/* Beyond Win 2003?  (6+?) -- 8191 */
	/* WinXP (5/0) or Win 2003 (5/1) -- 8191 */
	/* Win 2k (5/0) or Win NT 4 (4/0) -- 2047 */
	/* older -- 996 */

	o.dwOSVersionInfoSize = sizeof( o );

	if( !GetVersionEx( &o ) )
	    maxline = MAXLINE;
	else if( o.dwMajorVersion >= 6 )
	    maxline = 8191;
	else if( o.dwMajorVersion == 5 && o.dwMinorVersion >= 1 )
	    maxline = 8191;
	else if( o.dwMajorVersion == 5 && o.dwMinorVersion == 0 )
	    maxline = 2047;
	else if( o.dwMajorVersion == 4 && o.dwMinorVersion == 0 )
	    maxline = 2047;
	else							  
	    maxline = MAXLINE;

	if( DEBUG_EXECCMD )
	    printf( "Windows %d/%d maxline %d\n",
	    	o.dwMajorVersion,
		o.dwMinorVersion,
		maxline );

	return maxline;
}

# else

int
execmax()
{
	return MAXLINE;
}

# endif

/*
 * execcmd() - launch an async command execution
 */

void
execcmd( 
	char *string,
	void (*func)( void *closure, int status ),
	void *closure,
	LIST *shell )
{
	int pid;
	int slot;
	const char *argv[ MAXARGC + 1 ];	/* +1 for NULL */

# ifdef USE_EXECNT
	char *p;
# endif

	/* Find a slot in the running commands table for this one. */

	for( slot = 0; slot < MAXJOBS; slot++ )
	    if( !cmdtab[ slot ].pid )
		break;

	if( slot == MAXJOBS )
	{
	    printf( "no slots for child!\n" );
	    exit( EXITBAD );
	}

# ifdef USE_EXECNT
	if( !cmdtab[ slot ].tempfile )
	{
	    char *tempdir;

	    if( !( tempdir = getenv( "TEMP" ) ) &&
		!( tempdir = getenv( "TMP" ) ) )
		    tempdir = "\\temp";

	    /* +32 is room for \jamXXXXXtSS.bat (at least) */

	    cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 32 );

	    sprintf( cmdtab[ slot ].tempfile, "%s\\jam%dt%d.bat", 
				tempdir, GetCurrentProcessId(), slot );
	}

	/* Trim leading, ending white space */

	while( isspace( *string ) )
		++string;

	p = strchr( string, '\n' );

	while( p && isspace( *p ) )
		++p;

	/* If multi line, or too long, or JAMSHELL is set, write to bat file. */
	/* Otherwise, exec directly. */
	/* Frankly, if it is a single long line I don't think the */
	/* command interpreter will do any better -- it will fail. */

	if( p && *p || strlen( string ) > MAXLINE || shell )
	{
	    FILE *f;

	    /* Write command to bat file. */

	    f = fopen( cmdtab[ slot ].tempfile, "w" );
	    fputs( string, f );
	    fclose( f );

	    string = cmdtab[ slot ].tempfile;
	}
# endif

	/* Forumulate argv */
	/* If shell was defined, be prepared for % and ! subs. */
	/* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */

	if( shell )
	{
	    int i;
	    char jobno[4];
	    int gotpercent = 0;

	    sprintf( jobno, "%d", slot + 1 );

	    for( i = 0; shell && i < MAXARGC; i++, shell = list_next( shell ) )
	    {
		switch( shell->string[0] )
		{
		case '%':	argv[i] = string; gotpercent++; break;
		case '!':	argv[i] = jobno; break;
		default:	argv[i] = shell->string;
		}
		if( DEBUG_EXECCMD )
		    printf( "argv[%d] = '%s'\n", i, argv[i] );
	    }

	    if( !gotpercent )
		argv[i++] = string;

	    argv[i] = 0;
	}
	else
	{
# ifdef USE_EXECNT
	    argv[0] = "cmd.exe";
	    argv[1] = "/Q/C";		/* anything more is non-portable */
# else
	    argv[0] = "/bin/sh";
	    argv[1] = "-c";
# endif
	    argv[2] = string;
	    argv[3] = 0;
	}

	/* Catch interrupts whenever commands are running. */

	if( !cmdsrunning++ )
	    istat = signal( SIGINT, onintr );

	/* Start the command */

# ifdef USE_EXECNT
	if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 )
	{
	    perror( "spawn" );
	    exit( EXITBAD );
	}
# else
# ifdef NO_VFORK
	if ((pid = fork()) == 0) 
# else
	if ((pid = vfork()) == 0) 
# endif
   	{
	    /* hpux doesn't like const here */
	    execvp( argv[0], (char **)argv );
	    _exit(127);
	}
	if( pid == -1 )
	{
	    perror( "vfork" );
	    exit( EXITBAD );
	}
# endif
	/* Save the operation for execwait() to find. */

	cmdtab[ slot ].pid = pid;
	cmdtab[ slot ].func = func;
	cmdtab[ slot ].closure = closure;

	/* Wait until we're under the limit of concurrent commands. */
	/* Don't trust globs.jobs alone. */

	while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs )
	    if( !execwait() )
		break;
}

/*
 * execwait() - wait and drive at most one execution completion
 */

int
execwait()
{
	int i;
	int status, w;
	int rstat;

	/* Handle naive make1() which doesn't know if cmds are running. */

	if( !cmdsrunning )
	    return 0;

	/* Pick up process pid and status */
    
	while( ( w = wait( &status ) ) == -1 && errno == EINTR )
		;

	if( w == -1 )
	{
	    printf( "child process(es) lost!\n" );
	    perror("wait");
	    exit( EXITBAD );
	}

	/* Find the process in the cmdtab. */

	for( i = 0; i < MAXJOBS; i++ )
	    if( w == cmdtab[ i ].pid )
		break;

	if( i == MAXJOBS )
	{
	    printf( "waif child found!\n" );
	    exit( EXITBAD );
	}

# ifdef USE_EXECNT
	/* Clear the temp file */

	unlink( cmdtab[ i ].tempfile );
# endif

	/* Drive the completion */

	if( !--cmdsrunning )
	    signal( SIGINT, istat );

	if( intr )
	    rstat = EXEC_CMD_INTR;
	else if( w == -1 || status != 0 )
	    rstat = EXEC_CMD_FAIL;
	else
	    rstat = EXEC_CMD_OK;

	cmdtab[ i ].pid = 0;

	(*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat );

	return 1;
}

# ifdef USE_MYWAIT

static long
my_wait( int *status )
{
	int i, num_active = 0;
	DWORD exitcode, waitcode;
	static HANDLE *active_handles = 0;

	if (!active_handles)
	    active_handles = (HANDLE *)malloc(globs.jobs * sizeof(HANDLE) );

	/* first see if any non-waited-for processes are dead,
	 * and return if so.
	 */
	for ( i = 0; i < globs.jobs; i++ ) {
	    if ( cmdtab[i].pid ) {
		if ( GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode) ) {
		    if ( exitcode == STILL_ACTIVE )
			active_handles[num_active++] = (HANDLE)cmdtab[i].pid;
		    else {
			CloseHandle((HANDLE)cmdtab[i].pid);
			*status = (int)((exitcode & 0xff) << 8);
			return cmdtab[i].pid;
		    }
		}
		else
		    goto FAILED;
	    }
	}

	/* if a child exists, wait for it to die */
	if ( !num_active ) {
	    errno = ECHILD;
	    return -1;
	}
	waitcode = WaitForMultipleObjects( num_active,
					   active_handles,
					   FALSE,
					   INFINITE );
	if ( waitcode != WAIT_FAILED ) {
	    if ( waitcode >= WAIT_ABANDONED_0
		&& waitcode < WAIT_ABANDONED_0 + num_active )
		i = waitcode - WAIT_ABANDONED_0;
	    else
		i = waitcode - WAIT_OBJECT_0;
	    if ( GetExitCodeProcess(active_handles[i], &exitcode) ) {
		CloseHandle(active_handles[i]);
		*status = (int)((exitcode & 0xff) << 8);
		return (long)active_handles[i];
	    }
	}

FAILED:
	errno = GetLastError();
	return -1;
    
}

# endif /* USE_MYWAIT */

# endif /* USE_EXECUNIX */
# Change User Description Committed
#20 23298 Nick Poole Updating Jam source

The changes here include a simplified Windows build process:
the makefile now has 2 MSVC sections, one 32-bit the other
64-bit.
#19 9902 Perforce staff Build jam without so many warnings on macosxx86_64.


p4transfer.py: Transferred from production
#18 9901 Perforce staff Compute max line length on windows by OS type using GetVersionEx()
call in execmax() function.  Now instead of just 996 for the default,
Win NT 4.0 and Win 2K get 2047, and Win XP and 2003+ get 8191.

The wheels of progress grind slowly.

Bumped JAMVERSION to 2.5.1.

New feature documented in RELNOTES.


p4transfer.py: Transferred from production
#17 9900 Perforce staff Maximum command line length now determined by execmax() function,
which still just returns the compiled-in MAXLINE.


p4transfer.py: Transferred from production
#16 9899 tony Port Jam to Zeta.
Zeta is a fork of BeOS so this port is
broadly the same as the BeOS port.


p4transfer.py: Transferred from production
#15 9898 Perforce maintenance HPUX 11 jam porting: a cast for exec() and include unistd.h.

Porting change.


p4transfer.py: Transferred from production
#14 3069 Perforce staff Remove temp .bat files created on NT.

Bug fix documented in RELNOTES.
#13 2845 rmg Make jam 2.5rc2 build with BorlandC 5.5

Porting Change, note in RELNOTES.

=== computer:1666: Change 40450 by anton@anton-diogenes on 2003/01/21 10:26:05
#12 2562 rmg Grist jam temp file names with pid on NT so as to ensure
uniqueness.

Bug fix documented in RELNOTES.

=== computer:1666: Change 39577 by seiwald@tricks-seiwald on 2002/12/27 15:55:21
#11 2493 rmg Rewrite the past: update all jam's source with comments to
reflect changes since about 2.3, very early 2001.

Whitespace only change.

=== computer:1666: Change 37660 by seiwald@play-seiwald on 2002/11/06 22:41:35

Note: I regenerated jamgram.c on my linux 7.3 system prior to
the submit, since patch was so unhappy trying to lay down the
changes from Christopher's change. Presumably this is just due to
different yacc/bison/whatever particulars on the system where
Christopher made the changes originally. - rmg
#10 2491 rmg Some consting in jam to make it more compilable by C++ compilers.

No functional change.

=== computer:1666: Change 37433 by perforce@perforce on 2002/10/30 16:08:51

Recreational const-ing of jam, for compilers that don't allow
"string" to be passed as a non-const char *.

This included a few places where we were modifying what could
possibly have been read-only storage, oddly enough.

No functional change.

=== computer:1666: Change 37602 by seiwald@play-seiwald on 2002/11/04 17:25:40
#9 1351 rmg This change is integration history only.
(Accept -ay'ed
integrating:

    Change 216 by peter_glasscock@peter_glasscock on 1999/07/27 03:25:01

    Integrate recent changes to public source

So, these were apparently Matt's tweaks to changes being
integrated from //public/jam, and we'll ignore them here.
#8 1350 rmg No change - this is just to record integration history.
(Resolve -ay'ed from Change 292 by Matt Armstrong
#7 486 Perforce staff Jam 2.3.
 See RELNOTES for a list of changes from 2.2.x.

Just about every source file was touched when jam got ANSI-fied.
#6 212 Perforce staff An interpretative integration of Peter Glasscock's -o file support.
This is handled in the make1() routine, rather than in all the
exec*.c files.  -o x writes the actions to file x rather than
actually running them.  Implies -n (but not -d2).
#5 67 Laura Wingerd Integrate Perforce's jam changes & project page update
(change 59, change 60, change 61, change 62, change 63, change 64,
change 66)
#4 5 Perforce maintenance Jam/MR 2.2.4 (HDRPATTERN, JAMUNAME, JAMSHELL, plus misc tweaks)
#3 4 Perforce maintenance Jam/MR 2.2.2 (AmigaOS support)
#2 3 Perforce maintenance Jam/MR 2.2.1 (fix for NT handle leak)
#1 2 laura Add Jam/MR 2.2 source