|
|
//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#if !defined( _X360 )
#include <windows.h>
#endif
#include "vstdlib/iprocessutils.h"
#include "tier1/utllinkedlist.h"
#include "tier1/utlstring.h"
#include "tier1/utlbuffer.h"
#include "tier1/tier1.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
class CProcess;
class CProcessPipeRead : public IPipeRead { // IPipeRead overrides.
public: virtual int GetNumBytesAvailable(); virtual void ReadAvailable( CUtlString &sStr, int nMaxBytes ); virtual void ReadAvailable( CUtlBuffer *pOutBuffer, int nMaxBytes ); virtual void Read( CUtlString &sStr, int nMaxBytes ); virtual void ReadLine( CUtlString &sStr );
public: bool IsValid() const;
// This reads in whatever data is available in the pipe and caches it.
void CacheOutput();
// Returns true when there is data in m_hRead, false otherwise.
bool WaitForOutput();
int GetActualProcessOutputSize(); int GetProcessOutput( void *pBuf, int nBytes ); int GetActualProcessOutput( void *pBuf, int nBufLen );
public: CProcess *m_pProcess;
HANDLE m_hRead; // Comes from ProcessInfo_t::m_hChildStdoutRd or m_hChildStderrRd.
// This is stored if they wait for the process to exit. Even though they haven't read the data yet,
// they can still call ReadAvailable() or GetNumBytesAvailable() on stdio after the process was terminated.
CUtlBuffer m_CachedOutput; };
struct ProcessInfo_t { HANDLE m_hChildStdinRd; HANDLE m_hChildStdinWr; HANDLE m_hChildStdoutRd; HANDLE m_hChildStdoutWr;
HANDLE m_hChildStderrRd; HANDLE m_hChildStderrWr;
HANDLE m_hProcess; CUtlString m_CommandLine; int m_fFlags; // PROCESSSTART_xxx.
};
class CProcessUtils;
class CProcess : public IProcess { public: CProcess( CProcessUtils *pProcessUtils, const ProcessInfo_t &info );
// IProcess overrides.
public: virtual void Release(); virtual void Abort(); virtual bool IsComplete(); virtual int WaitUntilComplete();
virtual int WriteStdin( char *pBuf, int nBufLen ); virtual IPipeRead* GetStdout(); virtual IPipeRead* GetStderr();
virtual int GetExitCode();
public: ProcessInfo_t m_Info;
CProcessPipeRead m_StdoutRead; CProcessPipeRead m_StderrRead;
CProcessUtils *m_pProcessUtils; intp m_nProcessesIndex; // Index into CProcessUtils::m_Processes.
};
//-----------------------------------------------------------------------------
// At the moment, we can only run one process at a time
//-----------------------------------------------------------------------------
class CProcessUtils : public CTier1AppSystem< IProcessUtils > { typedef CTier1AppSystem< IProcessUtils > BaseClass;
public: CProcessUtils() {}
// Inherited from IAppSystem
virtual InitReturnVal_t Init(); virtual void Shutdown();
// Inherited from IProcessUtils
virtual IProcess* StartProcess( const char *pCommandLine, int fFlags, const char *pWorkingDir ); virtual IProcess* StartProcess( int argc, const char **argv, int fFlags, const char *pWorkingDir ); virtual int SimpleRunProcess( const char *pCommandLine, const char *pWorkingDir, CUtlString *pStdout );
public: void OnProcessDelete( CProcess *pProcess );
private:
// creates the process, adds it to the list and writes the windows HANDLE into info.m_hProcess
CProcess* CreateProcess( ProcessInfo_t &info, int fFlags, const char *pWorkingDir );
CUtlFixedLinkedList< CProcess* > m_Processes; bool m_bInitialized; };
//-----------------------------------------------------------------------------
// Purpose: singleton accessor
//-----------------------------------------------------------------------------
static CProcessUtils s_ProcessUtils; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CProcessUtils, IProcessUtils, PROCESS_UTILS_INTERFACE_VERSION, s_ProcessUtils );
//-----------------------------------------------------------------------------
// Returns the last error that occurred
//-----------------------------------------------------------------------------
char *GetErrorString( char *pBuf, int nBufLen ) { FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, pBuf, nBufLen, NULL ); char *p = strchr(pBuf, '\r'); // get rid of \r\n
if(p) { p[0] = 0; } return pBuf; }
// ------------------------------------------------------------------------------------- //
// CProcessPipeRead implementation.
// ------------------------------------------------------------------------------------- //
bool CProcessPipeRead::IsValid() const { return m_hRead != INVALID_HANDLE_VALUE; }
int CProcessPipeRead::GetNumBytesAvailable() { return GetActualProcessOutputSize() + m_CachedOutput.TellPut(); }
void CProcessPipeRead::ReadAvailable( CUtlString &sStr, int32 nMaxBytes ) { int nBytesAvailable = GetNumBytesAvailable(); nBytesAvailable = MIN( nBytesAvailable, nMaxBytes );
sStr.SetLength( nBytesAvailable ); // If nBytesAvailable != 0, this auto allocates an extra byte for the null terminator.
if ( nBytesAvailable > 0 ) { char *pOut = sStr.Get(); int nBytesGotten = GetProcessOutput( pOut, nBytesAvailable ); Assert( nBytesGotten == nBytesAvailable );
// Null-terminate it.
pOut[nBytesGotten] = 0; } }
void CProcessPipeRead::ReadAvailable( CUtlBuffer *pOutBuffer, int nMaxBytes ) { int nBytesAvailable = GetNumBytesAvailable(); nBytesAvailable = MIN( nBytesAvailable, nMaxBytes );
if ( nBytesAvailable > 0 ) { char *pOut = (char*)pOutBuffer->AccessForDirectRead(nBytesAvailable+1); int nBytesGotten = GetProcessOutput( pOut, nBytesAvailable ); Assert( nBytesGotten == nBytesAvailable );
// Null-terminate it.
pOut[nBytesGotten] = 0; } }
void CProcessPipeRead::Read( CUtlString &sStr, int32 nBytes ) { sStr.SetLength( 0 );
int nBytesLeftToRead = nBytes; while ( nBytesLeftToRead > 0 ) { int nAvail = GetNumBytesAvailable(); if ( nAvail == 0 ) { if ( m_pProcess->IsComplete() ) { return; } else { WaitForOutput(); continue; } }
int nToRead = MIN( nBytesLeftToRead, nAvail );
// Read as much as we need and add it to the string.
CUtlString sTemp; ReadAvailable( sTemp, nToRead ); Assert( sTemp.Length() == nToRead ); sStr += sTemp;
nBytesLeftToRead -= nToRead; } }
void CProcessPipeRead::ReadLine( CUtlString &sStr ) { sStr.SetLength( 0 ); while ( 1 ) { // Wait for output if there's nothing left in our cached output.
if ( m_CachedOutput.GetBytesRemaining() == 0 && !WaitForOutput() ) return;
CacheOutput();
char *pStr = (char*)m_CachedOutput.PeekGet(); int nBytes = m_CachedOutput.GetBytesRemaining(); // No more stuff available and the process is dead?
if ( nBytes == 0 && m_pProcess->IsComplete() ) break;
int i; for ( i=0; i < nBytes; i++ ) { if ( pStr[i] == '\n' ) break; }
if ( i < nBytes ) { // We hit a line ending.
int nBytesToRead = i; if ( nBytesToRead > 0 && pStr[nBytesToRead-1] == '\r' ) --nBytesToRead;
CUtlString sTemp; sTemp.SetDirect( pStr, nBytesToRead ); sStr += sTemp;
m_CachedOutput.SeekGet( CUtlBuffer::SEEK_CURRENT, i+1 ); // Use i here because it'll be at the \n, not the \r\n.
if ( m_CachedOutput.GetBytesRemaining() == 0 ) m_CachedOutput.Purge();
return; } } }
bool CProcessPipeRead::WaitForOutput() { if ( m_hRead == INVALID_HANDLE_VALUE ) { Assert( false ); return false; }
while ( GetActualProcessOutputSize() == 0 ) { if ( m_pProcess->IsComplete() ) return false; else ThreadSleep( 1 ); }
return true; }
void CProcessPipeRead::CacheOutput() { int nBytes = GetActualProcessOutputSize(); if ( nBytes == 0 ) return;
int nPut = m_CachedOutput.TellPut(); m_CachedOutput.EnsureCapacity( nPut + nBytes ); int nBytesRead = GetActualProcessOutput( (char*)m_CachedOutput.PeekPut(), nBytes ); Assert( nBytesRead == nBytes );
m_CachedOutput.SeekPut( CUtlBuffer::SEEK_HEAD, nPut + nBytesRead ); }
//-----------------------------------------------------------------------------
// Methods used to read output back from a process
//-----------------------------------------------------------------------------
int CProcessPipeRead::GetActualProcessOutputSize() { if ( m_hRead == INVALID_HANDLE_VALUE ) { Assert( false ); return 0; }
DWORD dwCount = 0; if ( !PeekNamedPipe( m_hRead, NULL, NULL, NULL, &dwCount, NULL ) ) { char buf[ 512 ]; Warning( "Could not read from pipe associated with command %s\n" "Windows gave the error message:\n \"%s\"\n", m_pProcess->m_Info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) ); return 0; }
return (int)dwCount; }
int CProcessPipeRead::GetProcessOutput( void *pBuf, int nBytes ) { int nCachedBytes = MIN( nBytes, m_CachedOutput.TellPut() ); int nPipeBytes = nBytes - nCachedBytes;
// Read from the cached buffer.
m_CachedOutput.Get( pBuf, nCachedBytes ); if ( m_CachedOutput.GetBytesRemaining() == 0 ) { m_CachedOutput.Purge(); }
// Read from the pipe.
int nPipedBytesRead = GetActualProcessOutput( (char*)pBuf + nCachedBytes, nPipeBytes ); return nCachedBytes + nPipedBytesRead; }
int CProcessPipeRead::GetActualProcessOutput( void *pBuf, int nBufLen ) { if ( m_hRead == INVALID_HANDLE_VALUE ) { Assert( false ); return 0; }
// ReadFile can deadlock in a blaze of awesomeness if you ask for 0 bytes.
if ( nBufLen == 0 ) return 0;
DWORD nBytesRead = 0; BOOL bSuccess = ReadFile( m_hRead, pBuf, nBufLen, &nBytesRead, NULL );
if ( bSuccess ) { // ReadFile -should- block until it gets the number of bytes requested OR until
// the process is complete. So if it didn't return the # of bytes requested,
// then make sure the process is complete.
if ( (int)nBytesRead != nBufLen ) { Assert( m_pProcess->IsComplete() ); } } else { Assert( false ); }
return (int)nBytesRead; }
// ------------------------------------------------------------------------------------- //
// CProcess implementation.
// ------------------------------------------------------------------------------------- //
CProcess::CProcess( CProcessUtils *pProcessUtils, const ProcessInfo_t &info ) { m_pProcessUtils = pProcessUtils; m_Info = info; m_StdoutRead.m_pProcess = this; m_StdoutRead.m_hRead = info.m_hChildStdoutRd;
m_StderrRead.m_pProcess = this; m_StderrRead.m_hRead = info.m_hChildStderrRd; }
void CProcess::Release() { if ( !( m_Info.m_fFlags & STARTPROCESS_NOAUTOKILL ) ) { Abort(); }
ProcessInfo_t& info = m_Info; CloseHandle( info.m_hChildStderrRd ); CloseHandle( info.m_hChildStderrWr ); CloseHandle( info.m_hChildStdinRd ); CloseHandle( info.m_hChildStdinWr ); CloseHandle( info.m_hChildStdoutRd ); CloseHandle( info.m_hChildStdoutWr );
m_pProcessUtils->OnProcessDelete( this ); delete this; }
void CProcess::Abort() { if ( !IsComplete() ) { TerminateProcess( m_Info.m_hProcess, 1 ); } }
bool CProcess::IsComplete() { HANDLE h = m_Info.m_hProcess; return ( WaitForSingleObject( h, 0 ) != WAIT_TIMEOUT ); }
int CProcess::WaitUntilComplete() { ProcessInfo_t &info = m_Info;
if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE ) { WaitForSingleObject( info.m_hProcess, INFINITE ); } else { // NOTE: The called process can block during writes to stderr + stdout
// if the pipe buffer is empty. Therefore, waiting INFINITE is not
// possible here. We must queue up messages received to allow the
// process to continue
while ( WaitForSingleObject( info.m_hProcess, 50 ) == WAIT_TIMEOUT ) { m_StdoutRead.CacheOutput(); if ( m_StderrRead.IsValid() ) m_StderrRead.CacheOutput(); } }
return GetExitCode(); }
int CProcess::WriteStdin( char *pBuf, int nBufLen ) { ProcessInfo_t& info = m_Info; if ( info.m_hChildStdinWr == INVALID_HANDLE_VALUE ) { Assert( false ); return 0; }
DWORD nBytesWritten = 0; if ( WriteFile( info.m_hChildStdinWr, pBuf, nBufLen, &nBytesWritten, NULL ) ) return (int)nBytesWritten; else return 0; }
IPipeRead* CProcess::GetStdout() { return &m_StdoutRead; }
IPipeRead* CProcess::GetStderr() { return &m_StderrRead; }
int CProcess::GetExitCode() { ProcessInfo_t &info = m_Info; DWORD nExitCode; BOOL bOk = GetExitCodeProcess( info.m_hProcess, &nExitCode ); if ( !bOk || nExitCode == STILL_ACTIVE ) return -1; return nExitCode; }
//-----------------------------------------------------------------------------
// Initialize, shutdown process system
//-----------------------------------------------------------------------------
InitReturnVal_t CProcessUtils::Init() { InitReturnVal_t nRetVal = BaseClass::Init(); if ( nRetVal != INIT_OK ) return nRetVal;
m_bInitialized = true; return INIT_OK; }
void CProcessUtils::Shutdown() { Assert( m_bInitialized ); Assert( m_Processes.Count() == 0 ); // Delete any lingering processes.
while ( m_Processes.Count() > 0 ) { m_Processes[ m_Processes.Head() ]->Release(); }
m_bInitialized = false; return BaseClass::Shutdown(); }
void CProcessUtils::OnProcessDelete( CProcess *pProcess ) { m_Processes.Remove( pProcess->m_nProcessesIndex ); }
CProcess *CProcessUtils::CreateProcess( ProcessInfo_t &info, int fFlags, const char *pWorkingDir ) { STARTUPINFO si; memset(&si, 0, sizeof si); si.cb = sizeof(si); if ( fFlags & STARTPROCESS_CONNECTSTDPIPES ) { si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = info.m_hChildStdinRd; si.hStdError = info.m_hChildStderrWr; si.hStdOutput = info.m_hChildStdoutWr; }
DWORD dwCreateProcessFlags = 0; if ( !( fFlags & STARTPROCESS_SHARE_CONSOLE ) ) { dwCreateProcessFlags |= DETACHED_PROCESS; }
PROCESS_INFORMATION pi; if ( ::CreateProcess( NULL, info.m_CommandLine.Get(), NULL, NULL, TRUE, dwCreateProcessFlags, NULL, pWorkingDir, &si, &pi ) ) { info.m_hProcess = pi.hProcess;
CProcess *pProcess = new CProcess( this, info ); pProcess->m_nProcessesIndex = m_Processes.AddToTail( pProcess ); return pProcess; }
char buf[ 512 ]; Warning( "Could not execute the command:\n %s\n" "Windows gave the error message:\n \"%s\"\n", info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) );
return NULL; }
//-----------------------------------------------------------------------------
// Options for compilation
//-----------------------------------------------------------------------------
IProcess* CProcessUtils::StartProcess( const char *pCommandLine, int fFlags, const char *pWorkingDir ) { Assert( m_bInitialized );
ProcessInfo_t info; info.m_CommandLine = pCommandLine;
if ( !(fFlags & STARTPROCESS_CONNECTSTDPIPES) ) { info.m_hChildStderrRd = info.m_hChildStderrWr = INVALID_HANDLE_VALUE; info.m_hChildStdinRd = info.m_hChildStdinWr = INVALID_HANDLE_VALUE; info.m_hChildStdoutRd = info.m_hChildStdoutWr = INVALID_HANDLE_VALUE;
return CreateProcess( info, fFlags, pWorkingDir ); }
SECURITY_ATTRIBUTES saAttr;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL;
DWORD nPipeSize = 0; // default size
if ( fFlags & STARTPROCESS_FATPIPES ) { nPipeSize = 1024*1024; }
// Create a pipe for the child's STDOUT.
if ( CreatePipe( &info.m_hChildStdoutRd, &info.m_hChildStdoutWr, &saAttr, nPipeSize ) ) { if ( CreatePipe( &info.m_hChildStdinRd, &info.m_hChildStdinWr, &saAttr, nPipeSize ) ) { BOOL bSuccess = false; if ( fFlags & STARTPROCESS_SEPARATE_STDERR ) { bSuccess = CreatePipe( &info.m_hChildStderrRd, &info.m_hChildStderrWr, &saAttr, nPipeSize ); } else { bSuccess = DuplicateHandle( GetCurrentProcess(), info.m_hChildStdoutWr, GetCurrentProcess(), &info.m_hChildStderrWr, 0, TRUE, DUPLICATE_SAME_ACCESS );
info.m_hChildStderrRd = INVALID_HANDLE_VALUE; }
if ( bSuccess ) { IProcess *pProcess = CreateProcess( info, fFlags, pWorkingDir ); if ( pProcess ) return pProcess;
CloseHandle( info.m_hChildStderrRd ); CloseHandle( info.m_hChildStderrWr ); } CloseHandle( info.m_hChildStdinRd ); CloseHandle( info.m_hChildStdinWr ); } CloseHandle( info.m_hChildStdoutRd ); CloseHandle( info.m_hChildStdoutWr ); } return NULL; }
//-----------------------------------------------------------------------------
// Start up a process
//-----------------------------------------------------------------------------
IProcess* CProcessUtils::StartProcess( int argc, const char **argv, int fFlags, const char *pWorkingDir ) { CUtlString commandLine; for ( int i = 0; i < argc; ++i ) { commandLine += argv[i]; if ( i != argc-1 ) { commandLine += " "; } } return StartProcess( commandLine.Get(), fFlags, pWorkingDir ); }
int CProcessUtils::SimpleRunProcess( const char *pCommandLine, const char *pWorkingDir, CUtlString *pStdout ) { int nFlags = 0; if ( pStdout ) nFlags |= STARTPROCESS_CONNECTSTDPIPES;
IProcess *pProcess = StartProcess( pCommandLine, nFlags, pWorkingDir ); if ( !pProcess ) return -1;
int nExitCode = pProcess->WaitUntilComplete(); if ( pStdout ) { pProcess->GetStdout()->Read( *pStdout ); } pProcess->Release(); return nExitCode; }
// Translate '\r\n' to '\n'.
void TranslateLinefeedsToUnix( char *pBuffer ) { char *pOut = pBuffer; while ( *pBuffer ) { if ( pBuffer[0] == '\r' && pBuffer[1] == '\n' ) { *pOut = '\n'; ++pBuffer; } else { *pOut = *pBuffer; } ++pBuffer; ++pOut; } *pOut = 0; }
|