/*
* 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;
}