runcmd.cc #1

  • //
  • guest/
  • perforce_software/
  • p4/
  • 2014-2/
  • sys/
  • runcmd.cc
  • View
  • Commits
  • Open Download .zip Download (24 KB)
/*
 * Copyright 1995, 2003 Perforce Software.  All rights reserved.
 *
 * This file is part of Perforce - the FAST SCM System.
 */

/*
 * RunCommand() -- Just run a command and capture its output
 */

# define NEED_ERRNO
# define NEED_FILE
# define NEED_FCNTL
# define NEED_POPEN
# define NEED_FORK
# define NEED_SOCKETPAIR

# include <stdhdrs.h>

# include <strbuf.h>
# include <strops.h>
# include <error.h>

# include <pathsys.h>
# include <filesys.h>

# include "strarray.h"
# include <runcmd.h>

/*
 * RunCommand::SetArgs() - clear buffer and add (quoted) args 
 * RunCommand::AddArg() - add (quoted) arg to buffer
 */

# ifdef OS_VMS
# define QUOTE ""
# endif
# if defined (OS_OS2) || defined (OS_NT)
# define QUOTE "\""
# endif
# ifndef QUOTE
# define QUOTE "'"
# endif

// max number of cmdline args + 1
# define CMD_ARGSIZE	1024

void
RunArgs::SetArgs( int argc, const char * const *argv )
{
	buf.Clear();

	while( argc-- )
	    AddArg( *argv++ );
}

void
RunArgs::AddArg( const char *arg )
{
	AddArg( StrRef( arg ) );
}

void
RunArgs::AddArg( const StrPtr &arg )
{
	// Space before 2nd+ args

	if( buf.Length() )
	    buf << " ";

	// Quote args with spaces. On AS400 & NT, quote everything unless it
	// starts with a quote already.

# if defined (OS_AS400) || defined (OS_NT)
	if( arg[0] == '"' )
# else
	if( !memchr( arg.Text(), ' ', arg.Length() ) )
# endif
	    buf << arg;
	else
	    buf << QUOTE << arg << QUOTE;
}

void
RunArgs::AddCmd( const char *arg )
{
	const char *p = arg;

#if OS_NT
	// Try to distinguish a command with spaces in it from a 
	// command followed by flags.  We do so by looking for a
	// - or a / introducing the flags.  Everything before is
	// a single command name, which can get quoted by AddArg().

	while( ( p = strchr( p, ' ' ) ) && !strchr( "-+/", p[1] ) )
	    p = p + 1;
#else
	p = strchr( p, ' ' );
#endif

	// p = the first command flag, or nul if none
	// Add arg up to this flag, and then look for next.

	while( p ) 
	{
	    AddArg( StrRef( arg, p - arg ) );
	    p = strchr( arg = p + 1, ' ' );
	}

	AddArg( StrRef( arg ) );
}

int
RunArgs::Argc( char **argv, int nargs )
{
	return StrOps::Words( argbuf, buf.Text(), argv, nargs );
}

/*
 * Argv-based version of RunArgs
 */

RunArgv::RunArgv()
{
    args = new StrArray();
}

RunArgv::~RunArgv()
{
    delete args;
}

void
RunArgv::SetArgs( int argc, const char * const *argv )
{
	while( argc-- )
	    AddArg( *argv++ );
}

void
RunArgv::AddArg( const char *arg )
{
	AddArg( StrRef( arg ) );
}

void
RunArgv::AddArg( const StrPtr &arg )
{
	args->Put()->Set(arg);
}

void
RunArgv::AddCmd( const char *arg )
{
	const char *p = arg;

#if OS_NT
	// Try to distinguish a command with spaces in it from a
	// command followed by flags.  We do so by looking for a
	// - or a / introducing the flags.  Everything before is
	// a single command name, which can get quoted by AddArg().

	while( ( p = strchr( p, ' ' ) ) && !strchr( "-+/", p[1] ) )
	    p = p + 1;
#else
	p = strchr( p, ' ' );
#endif

	// p = the first command flag, or nul if none
	// Add arg up to this flag, and then look for next.

	while( p )
	{
	    AddArg( StrRef( arg, p - arg ) );
	    p = strchr( arg = p + 1, ' ' );
	}

	AddArg( StrRef( arg ) );
}

/**
 * Fill in the output array with a C-style char *[] from our args,
 * suitable for execv().
 */
int
RunArgv::Argc( char **argv, int nargs )
{
	int	count = args->Count();
	if( nargs <= count)
	{
	    // don't scribble past the end of the array
	    // it would be nice to throw an exception here ...
	    count = nargs-1;
	}

	for( int i = 0; i < count; i++ )
	{
	    argv[i] = args->Get(i)->Text();
	}
	argv[count] = NULL;

	return count;
}

/**
 * Copy all the args into the provided buf and NUL-terminate.
 * Quote any args containing spaces (very crude).
 */
char *
RunArgv::Text(
        StrBuf  &buf)
{
        buf.Clear();

	for( int i = 0; i < args->Count(); i++)
	{
            if( i > 0 )
            {
                buf.Append( " " );
            }
	    const StrBuf
			*argbuf = args->Get(i);

	    if( strchr(argbuf->Text(), ' ') )
	    {
                buf.Append( QUOTE );
                buf.Append( argbuf->Text() );
                buf.Append( QUOTE );
	    }
	    else
	    {
                buf.Append( argbuf->Text() );
	    }
	}
        buf.Terminate();

	return buf.Text();
}

/*
 *
 * NT 
 *	RunCommand::Run
 *	RunCommand::RunInWindow
 *	RunCommand::RunChild
 *	RunCommand::WaitChild
 *	RunCommand::PollChild
 *
 */

# ifdef OS_NT 

# define HAVE_RUN
# define HAVE_RUNINWINDOW
# define HAVE_RUNCHILD

# define WIN32_LEAN_AND_MEAN
# include <windows.h>

/*
 * RunProcess - do NT's CreateProcess. The WaitForMultipleObjects dance
 *              is now finished in the caller.  This requires the caller
 *              to process output on the caller created pipe.
 */

enum RunProcessMode { RPM_Normal, RPM_Silent, RPM_Window, RPM_Detach };

PROCESS_INFORMATION *
RunProcess( 
	char *commandText,	// really should be const char *, but CreateProcess wants LPSTR
	RunProcessMode mode,
	HANDLE inputHandle,
	HANDLE outputHandle,
	HANDLE stderrHandle,
	Error *e )
{
	// The proc structure is freed in WaitChild().

	PROCESS_INFORMATION *ProcInfo;
	ProcInfo = (PROCESS_INFORMATION *)malloc(sizeof(PROCESS_INFORMATION));
	memset( (void *)ProcInfo, 0, sizeof( PROCESS_INFORMATION ) );

	// Run the sucker
	// Must inherit handles for stdout to work

	STARTUPINFO StartInfo = {0};

	StartInfo.cb = sizeof( StartInfo );
	StartInfo.lpReserved = NULL;
	StartInfo.lpTitle = NULL;
	StartInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
	StartInfo.wShowWindow = SW_SHOW;

	StartInfo.hStdInput = inputHandle;
	StartInfo.hStdOutput = outputHandle;
	StartInfo.hStdError = stderrHandle;

	DWORD creationFlags = NORMAL_PRIORITY_CLASS;

	// Must be true to inherit startinfo handles.

	BOOL inheritHandles = TRUE;

	// New window?  If so, don't inherit handles, as stdout 
	// means nothing and we don't want this long-term process 
	// holding our fds. If the parent process is a service, any 
	// inherited fds may not work in the new window.

	switch( mode )
	{
	case RPM_Silent:
	    StartInfo.wShowWindow = SW_HIDE;
	    break;

	case RPM_Window:
	    inheritHandles = FALSE;
	    creationFlags |= CREATE_NEW_CONSOLE;
	    // Standard handles will not be inherited.
	    StartInfo.dwFlags &= ~STARTF_USESTDHANDLES;
	    break;

	case RPM_Detach:
	    creationFlags |= DETACHED_PROCESS;
	    break;
	}

	if( !CreateProcess( 
		NULL,			// application name
		commandText,		// command line string
		NULL,			// process security attributes
		NULL,			// thread security attributes
		inheritHandles,		// inherit handles
		creationFlags,		// creation flags
		NULL,			// new environment block
		NULL,			// startup directory
		&StartInfo,		// startup info structure
		ProcInfo		// process info, returned
		) )
	{
	    // We collect the error here and process it later.

	    e->Sys( "Execution Failed", commandText );
	    free( ProcInfo );
	    return 0;
	}

	return ProcInfo;
}

RunCommand::RunCommand()
{
	pid = 0;
}

RunCommand::~RunCommand()
{
	WaitChild();
}

int
RunCommand::Run( RunArgs &command, Error *e )
{
	// Run and wait for the subprocess to exit.

	pid = RunProcess(
		    command.Text(),
		    RPM_Normal,
		    GetStdHandle( STD_INPUT_HANDLE ),
		    GetStdHandle( STD_OUTPUT_HANDLE ),
		    GetStdHandle( STD_ERROR_HANDLE ),
		    e );

	if( e->Test() )
	    return -1;

	return WaitChild();
}

int
RunCommand::Run( RunArgv &command, Error *e )
{
	// Run and wait for the subprocess to exit.

	StrBuf  buf;

	pid = RunProcess(
		    command.Text(buf),
		    RPM_Normal,
		    GetStdHandle( STD_INPUT_HANDLE ),
		    GetStdHandle( STD_OUTPUT_HANDLE ),
		    GetStdHandle( STD_ERROR_HANDLE ),
		    e );

	if( e->Test() )
	    return -1;

	return WaitChild();
}

int
RunCommand::RunInWindow( RunArgs &command, Error *e )
{
	// Don't pass stdin, stdout to a standalone window as it
	// should not hold our fds. If process is launched from
	// a service, inherited fds may not have an association with
	// with the user's desktop and wouldn't work as expected.

	// We don't wait for the subprocess to exit, so unless the launch
	// failed we'll assume the command ran ok.

	pid = RunProcess(
		    command.Text(),
		    RPM_Window,
		    NULL,
		    NULL,
		    NULL,
		    e );

	if( e->Test() )
	    return -1;

	return 0;
}

int
RunCommand::RunInWindow( RunArgv &command, Error *e )
{
	// Don't pass stdin, stdout to a standalone window as it
	// should not hold our fds. If process is launched from
	// a service, inherited fds may not have an association with
	// with the user's desktop and wouldn't work as expected.

	// We don't wait for the subprocess to exit, so unless the launch
	// failed we'll assume the command ran ok.

	StrBuf  buf;

	pid = RunProcess(
		    command.Text(buf),
		    RPM_Window,
		    NULL,
		    NULL,
		    NULL,
		    e );

	if( e->Test() )
	    return -1;

	return 0;
}

void
RunCommand::RunChild( RunArgs &cmd, int ops, int fds[2], Error *e )
{
	DoRunChild( cmd.Text(), NULL, ops, fds, e );
}

void
RunCommand::RunChild( RunArgv &cmd, int ops, int fds[2], Error *e )
{
	StrBuf  buf;

	DoRunChild( cmd.Text(buf), NULL, ops, fds, e );
}

// On Windows, char *argv[] is NULL and not used -- we execute cmdText instead
void
RunCommand::DoRunChild( char *cmdText, char *argv[], int ops, int fds[2], Error *e )
{
	/*
	 * Windows doesn't have socketpair(), so use separate pipes.
	 * We use pipe(), rather than CreatePipe(), because we need fds.
	 *
	 * rp = read pipe (from subprocess' stdout)
	 * wp = write pipe (to subprocess' stdin)
	 */

	int rp[2], wp[2];
	HANDLE	outHandle;
	HANDLE	errHandle;
	
	// Return read/write pipe to caller/parent via fds[].
	//
	// Child process will not inherit fds[1], parent's writer.
	// Always create the subprocess' stdin pipe, wp[0].

	if( _pipe( wp, 8192, O_BINARY ) < 0 )
	{
	    e->Sys( "pipe", "" );
	    return;
	}
	fds[1] = wp[1];
	SetHandleInformation( (HANDLE)_get_osfhandle( wp[1] ), 
		HANDLE_FLAG_INHERIT, 0 );

	if( ops & RCO_USE_STDOUT )
	{
	    // Use parent's stdout/stderr for the subprocess' stdout/stderr.

	    fds[0] = -1;
	    outHandle = GetStdHandle( STD_OUTPUT_HANDLE );
	    errHandle = GetStdHandle( STD_ERROR_HANDLE );
	}
	else
	{
	    // Setup a pipe to collect stdout from the subprocess.
	    // Child process will not inherit fds[0], parent's reader.

	    if( _pipe( rp, 8192, O_BINARY ) < 0 )
	    {
		e->Sys( "pipe", "" );
		return;
	    }
	    fds[0] = rp[0];
	    SetHandleInformation( (HANDLE)_get_osfhandle( rp[0] ), 
		HANDLE_FLAG_INHERIT, 0 );

	    outHandle = (HANDLE)_get_osfhandle( rp[1] );
	    errHandle = (HANDLE)_get_osfhandle( rp[1] );
	}

	// The Server can emit logging on stderr, if using p4 rpc
	// protocol avoid redirecting stderr to the protocol stream.

	if( ops & RCO_P4_RPC )
	    errHandle = GetStdHandle( STD_ERROR_HANDLE );

	// Processes run as a shell can't be run as detached processes

	RunProcessMode rpm = ( ops & RCO_AS_SHELL ) ? RPM_Silent : RPM_Detach;

	// Don't wait for the subprocess to exit.
	// Callers can use WaitChild() if they want to see status.

	pid = RunProcess(
			cmdText,
			rpm,
			(HANDLE)_get_osfhandle( wp[0] ),
			outHandle,
			errHandle,
			e );

	// Only the parent will be running this code.
	// Close the subprocess' end of the pipes, wp[0] and rp[1].

	close( wp[0] );
	if( ~ops & RCO_USE_STDOUT )
	    close( rp[1] );

	if( e->Test() )
	{
	    // We close off the parent's pipes since WaitChild()
	    // may never be called when process creation fails.
	    // Leave error set to indicate process creation failed.

	    if( ~ops & RCO_USE_STDOUT )
		{ close( fds[0] ); fds[0] = -1; }
	    close( fds[1] ); fds[1] = -1;
	}
}

int
RunCommand::WaitChild()
{
	// If pid is NULL, there was no subprocess.

	if( !pid )
	    return 0;

	PROCESS_INFORMATION *ProcInfo = (PROCESS_INFORMATION *)pid;

	// Wait for the process.

	DWORD status = -1;

	if( WaitForMultipleObjects( 
		1,				// # of handles
		&ProcInfo->hProcess,		// handle array
		TRUE,				// wait on all
		INFINITE			// wait forever
		) != WAIT_FAILED )
	{
	    GetExitCodeProcess( ProcInfo->hProcess, &status );
	}

	// You have to close these together, ask Bill.
	// Closing these handles, releases the subprocess.

	CloseHandle( ProcInfo->hThread );
	CloseHandle( ProcInfo->hProcess );
	free( ProcInfo );
	pid = 0;

	return status;
}

/*
 * Returns false if the child process is still alive, else returns true.
 * Also returns false if there's an error and we can't tell whether or not
 * the child is still alive.
 * Does *NOT* reap the child; you still need to call WaitChild() for that.
 *
 * Currently implemented *only* on Windows (it's not needed anywhere else).
 * Added to support p4sandbox.
 */
bool
RunCommand::PollChild(unsigned long millisecs) const
{
	// If pid is NULL, there was no subprocess.
	if( !pid )
	    return true;

	PROCESS_INFORMATION *ProcInfo = (PROCESS_INFORMATION *)pid;
	DWORD status = -1;
	bool ret = false;

	if( WaitForMultipleObjects( 
		1,				// # of handles
		&ProcInfo->hProcess,		// handle array
		TRUE,				// wait on all
		millisecs			// wait specified time
		) != WAIT_FAILED )
	{
	    ret = GetExitCodeProcess( ProcInfo->hProcess, &status );
	}

	if( !ret )
	    return false;

	return status != STILL_ACTIVE;
}

# else

/*
 * dummy no-op implementation for non-Windows platforms
 */
bool
RunCommand::PollChild(unsigned long millisecs) const
{
    return false;
}

# endif

/*
 *
 * MaxOSX 
 *	RunCommand::RunInWindow
 *
 */

# ifdef OS_MACOSX

# define HAVE_RUNINWINDOW

# include <i18napi.h>
# include <macutil.h>

int
RunCommand::RunInWindow( RunArgs &command, Error *e )
{
	if( getenv( "DISPLAY" ) )
	{
	    // xterm variety
	    // to launch in background

	    command << "&";
	    return system( command.Text() );
	}
	else if( WindowServicesAvailable() )
	{
	    RunCommandInNewTerminal( command.Text() );
	}

	return 0;
}

int
RunCommand::RunInWindow( RunArgv &command, Error *e )
{
	StrBuf  buf;

	if( getenv( "DISPLAY" ) )
	{
	    // xterm variety
	    // to launch in background

	    command << "&";
	    return system( command.Text(buf) );
	}
	else if( WindowServicesAvailable() )
	{
	    RunCommandInNewTerminal( command.Text(buf) );
	}

	return 0;
}

# endif

/*
 *
 * UNIX 
 *	RunCommand::RunChild 
 *
 */

# ifdef HAVE_FORK
# ifndef HAVE_RUNCHILD
# define HAVE_RUNCHILD

void
RunCommand::RunChild( RunArgs &cmd, int opts, int fds[2], Error *e )
{
	// Set up for direct execvp.
	// We used to use 'sh -c cmd' for RCO_AS_SHELL, but we removed
	// the shell execution since it exposed a security risk (job020599)

	char *argv[CMD_ARGSIZE];
	int argc;

	argc = cmd.Argc( argv, CMD_ARGSIZE );
	argv[argc] = 0;
	DoRunChild( cmd.Text(), argv, opts, fds, e);
}

/**
 * Almost exact copy of the RunArgs version of RunChild(), but takes a RunArgv
 * rather than a RunArgs.
 */
void
RunCommand::RunChild( RunArgv &cmd, int opts, int fds[2], Error *e )
{
	// Set up for direct execvp.
	// We used to use 'sh -c cmd' for RCO_AS_SHELL, but we removed
	// the shell execution since it exposed a security risk (job020599)

	char *argv[CMD_ARGSIZE];
	int argc;

	argc = cmd.Argc( argv, CMD_ARGSIZE );
	argv[argc] = 0;

	StrBuf  buf;
	DoRunChild( cmd.Text(buf), argv, opts, fds, e );
}

/**
 * Code common to the two flavors of RunChild()
 */
void
RunCommand::DoRunChild( char *cmdText, char *argv[], int opts, int fds[2], Error *e )
{
	// Create an error pipe which allows the subprocess to report
	// to the parent that the exec failed.  The parent reads from
	// this pipe after the fork.  If the exec succeeds the pipe
	// will break allowing the parent to continue.  If the exec fails
	// the subprocess sends errno through the pipe to the parent.

	int errchk[2];
	if( pipe( errchk ) < 0 )
	{
	    e->Sys( "pipe", "" );
	    return;
	}
	fcntl( errchk[1], F_SETFD, 1 );

	/*
	 * rp = read pipe (from subprocess' stdout)
	 * wp = write pipe (to subprocess' stdin)
	 */

	int rp[2], wp[2];

	// if RCO_USE_STDOUT
	// Create wp for subprocess' stdin/parent's stdout, wp[0]/wp[1].

	if( opts & RCO_USE_STDOUT )
	{
	    if( pipe( wp ) < 0 )
	    {
		e->Sys( "pipe", "" );
		return;
	    }
	}
	else
# ifdef HAVE_SOCKETPAIR

	// We use socketpair if SOLO_FD is set, so that a single
	// fd is the read/write to the child.  That's because
	// our NetStdioEndpoint::Accept() just uses fd 0 for I/O.

	if( opts & RCO_SOLO_FD )
	{
	    if( socketpair( AF_UNIX, SOCK_STREAM, 0, rp ) < 0 )
	    {
		e->Sys( "socketpair", "" );
		return;
	    }

	    wp[1] = dup( rp[0] );
	    wp[0] = dup( rp[1] );
	}
	else

# endif
	// default
	// Create rp for parent's stdin/subprocess' stdout, rp[0]/rp[1].
	// Create wp for subprocess' stdin/parent's stdout, wp[0]/wp[1].

	{
	    if( pipe( rp ) < 0 || pipe( wp ) < 0 )
	    {
		e->Sys( "pipe", "" );
		return;
	    }
	}

	// Return parent's read/write descriptors via fds[0]/fds[1].
	// Tell subprocess to close off parent end of pipes, close
	// on exec.  (1 is FD_CLOEXEC)

	if( opts & RCO_USE_STDOUT )
	    rp[0] = rp[1] = -1;
	else
	    fcntl( rp[0], F_SETFD, 1 );
	fcntl( wp[1], F_SETFD, 1 );

	// Assign parent's stdin/stdout fds[0]/fds[1]

	fds[0] = rp[0];
	fds[1] = wp[1];

	// UNIX wizardry, entry level.

	StrBuf buf;

	switch( pid = fork() )
	{
	case -1:
	    e->Sys( "fork", "" );
	    break;

	case 0:
	    // child

	    // Close the parent's side of the error pipe.

	    close( errchk[0] );

	    // Set stdin to wp[0]

	    if( 0 != wp[0] )
		{ close( 0 ); dup( wp[0] ); close( wp[0] ); }

	    // Set stdout to rp[1] and maybe stderr to rp[1]

	    if( !( opts & RCO_USE_STDOUT ) && 1 != rp[1] )
	    {
		close( 1 ); dup( rp[1] );

		// The Server can emit logging on stderr, if using p4 rpc
		// protocol avoid redirecting stderr to the protocol stream.

		if( ~opts & RCO_P4_RPC )
		    { close( 2 ); dup( rp[1] ); }

		close( rp[1] );
	    }

	    execvp( argv[0], argv );

	    // Send errno and the nil.

	    buf = StrNum( errno );
	    write( errchk[1], buf.Text(), buf.Length()+1 );

	    _exit( -1 );
	    break;

	default:
	    // parent

	    // Close the subprocess' side of the error pipe.

	    close( errchk[1] );

	    break;
	}

	// Only the parent will be running this code.

	if( ! e->Test() )
	{
	    // Read the error pipe for a possible exec error.

	    int len;
	    buf.Clear();
	    buf.Alloc(16);

	    if( (len = read( errchk[0], buf.Text(), 8 )) > 0 )
	    {
		errno = buf.Atoi();
		e->Sys( "Execution Failed", cmdText );
	    }

	}

	close( errchk[0] );

	// Close the subprocess' end of the pipes, wp[0] and rp[1].

	close( wp[0] );
	if( ~opts & RCO_USE_STDOUT )
	    close( rp[1] );

	// If in error, close off the parent's end of the pipes.

	if( e->Test() )
	{
	    if( ~opts & RCO_USE_STDOUT )
		{ close( fds[0] ); fds[0] = -1; }
	    close( fds[1] ); fds[1] = -1;
	}
}

int
RunCommand::WaitChild()
{
	// The fork() or execvp() failed, nothing to wait for.

	if( !pid )
	    return 0;

	int status = 0;
	int rtn;

	for( ;; )
	{
	    rtn = waitpid( pid, &status, 0 );

	    // application might be catching signals,  in that case
	    // the waitpid() could get interrupted - make sure we can
	    // restart.

	    if( rtn < 0 && errno == EINTR )
	        continue;

	    break;
	}

	pid = 0;

	return ( rtn < 0 ) ? rtn : WEXITSTATUS(status);
}

# endif
# endif

/*
 *
 * Defaults 
 *	RunCommand::Run
 *	RunCommand::RunInWindow
 *	RunCommand::RunChild
 *	RunCommand::WaitChild
 *
 */

# ifndef HAVE_RUN

RunCommand::RunCommand()
{
# ifdef HAVE_FORK
	pid = 0;
# endif
}

RunCommand::~RunCommand()
{
	WaitChild();
}

int
RunCommand::Run( RunArgs &command, Error *e )
{
	return system( command.Text() );
}

int
RunCommand::Run( RunArgv &command, Error *e )
{
	StrBuf  buf;

	return system( command.Text(buf) );
}

# endif

# ifndef HAVE_RUNINWINDOW

int
RunCommand::RunInWindow( RunArgs &command, Error *e )
{
	// simply append & to run in background.
	// We don't wait (no way to).  So our parent
	// (init?) may reap child status.

	return Run( command << "&", e );
}

int
RunCommand::RunInWindow( RunArgv &command, Error *e )
{
	// simply append & to run in background.
	// We don't wait (no way to).  So our parent
	// (init?) may reap child status.

	return Run( command << "&", e );
}

# endif

# ifndef HAVE_RUNCHILD

void
RunCommand::RunChild( RunArgs &cmd, int opts, int fds[2], Error *e )
{
        e->Set( E_FAILED, "No RunChild() available!\n" );
}

void
RunCommand::RunChild( RunArgv &cmd, int opts, int fds[2], Error *e )
{
        e->Set( E_FAILED, "No RunChild() available!\n" );
}

int
RunCommand::WaitChild()
{
	return 0;
}

# endif

/*
 *
 * RunCommandIo::Run() -- generic version built upon RunChild()
 *
 */

RunCommandIo::RunCommandIo()
{
	fds[0] = fds[1] = -1;
}

RunCommandIo::~RunCommandIo()
{
	if( fds[0] != -1 ) close( fds[0] );
	if( fds[1] != -1 ) close( fds[1] );
}

/*
 * RunCommandIo::Write() - write the the running command's stdin
 */

void
RunCommandIo::Write( const StrPtr &in, Error *e )
{
	// Send stdin

	if( write( fds[1], in.Text(), in.Length() ) < 0 )
	    e->Sys( "write", "command" );
}

/*
 * RunCommandIo::Read() - internal read with error checking
 */

int
RunCommandIo::Read( char *buf, int len, Error *e )
{
	// Close write fd before reading.

	if( fds[1] != -1 )
	{
	    close( fds[1] );
	    fds[1] = -1;
	}

	// Already done?

	if( fds[0] == -1 )
	    return 0;

	int l = read( fds[0], buf, len );

	if( l < 0 )
	{
	    e->Sys( "read", "command" );
	    return -1;
	}
	else if( !l )
	{
	    close( fds[0] );
	    fds[0] = -1;
	}

	return l;
}

/*
 * RunCommandIo::ReadError() - read a little and see if command failed
 *
 * Reads a buffer's worth of commands output and if it hits EOF checks
 * to see if the command exited non-zero.  If so, returns that buffer
 * as error output.  If the command outputs a full buffer's worth, or
 * exits zero, the buffered data is saved for the next Read() call and
 * ReadError() returns zero.
 */

StrPtr *
RunCommandIo::ReadError( Error *e )
{
	errBuf.Clear();
	int len = 4096;

	while( len )
	{
	    int l;

	    if( ( l = Read( errBuf.Alloc( len ), len, e ) ) < 0 )
		return 0;

	    len -= l;

	    errBuf.SetLength( errBuf.Length() - len );

	    if( !l )
		break;
	}

	if( !len || !WaitChild() )
	    return 0;

	StrOps::StripNewline( errBuf );

	return &errBuf;
}

/*
 * RunCommandIo::Read() - read from the running command's stdout
 */

int
RunCommandIo::Read( const StrPtr &out, Error *e )
{
	// If anything buffered from ReadError, return it first.

	if( errBuf.Length() )
	{
	    int l = errBuf.Length() < out.Length() 
		  ? errBuf.Length() : out.Length() - 1;

	    StrRef( errBuf.Text(), l ).StrCpy( out.Text() );

	    // copy up remainder

	    errBuf.Set( StrRef( errBuf.Text() + l, errBuf.Length() - l ) );

	    return l;
	}

	// Read stdout into result buffer
	// Close read fd at end of reading.

	return Read( out.Text(), out.Length(), e );
}

/*
 * RunCommandIo::Run() - run the command, sending stdin, capturing stdout. 
 */

int
RunCommandIo::Run( RunArgs &command, const StrPtr &in, StrBuf &out, Error *e )
{
	// Run command, capturing stdout
	// Stderr goes to process's stderr

	RunChild( command, RCO_AS_SHELL, fds, e );

	return ProcessRunResults( in, out, e );
}

/*
 * RunCommandIo::Run() - RunArgv version of Run().
 */

int
RunCommandIo::Run( RunArgv &command, const StrPtr &in, StrBuf &out, Error *e )
{
	// Run command, capturing stdout
	// Stderr goes to process's stderr

	RunChild( command, RCO_AS_SHELL, fds, e );

	return ProcessRunResults( in, out, e );
}

int
RunCommandIo::ProcessRunResults( const StrPtr &in, StrBuf &out, Error *e )
{
	if( e->Test() )
	    return -1;

	out.Clear();

	// Send stdin

	if( in.Length() )
	    Write( in, e );

	if( e->Test() )
	{
	    e->Fmt( &out );
	    e->Clear();
	}

	// Read stdout into result buffer

	for(;;)
	{
	    const int len = 1024;

	    int l = Read( StrRef( out.Alloc( len ), len ), e );

	    if( e->Test() )
		return -1;

	    if( l >= 0 )
		out.SetLength( out.Length() - len + l );

	    if( l <= 0 )
		break;
	}

	// The above Read closed both parent read and write fds.

	// Collect exit status

	int status = WaitChild();

	if( status && !out.Length() )
	    out << "no error message";

	// Strip any trailing newline

	StrOps::StripNewline( out );

	return status;
}
# Change User Description Committed
#1 15903 Matt Attaway Everything should be happy now between the Workshop and the depot paths
//guest/perforce_software/p4/2014_2/sys/runcmd.cc
#1 15901 Matt Attaway Clean up code to fit modern Workshop naming standards
//guest/perforce_software/p4/2014.2/sys/runcmd.cc
#1 12189 Matt Attaway Initial (and much belated) drop of 2014.2 p4 source code