|
|
//========= Copyright 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"
//-----------------------------------------------------------------------------
// At the moment, we can only run one process at a time
//-----------------------------------------------------------------------------
class CProcessUtils : public CTier1AppSystem< IProcessUtils > { typedef CTier1AppSystem< IProcessUtils > BaseClass;
public: CProcessUtils() : BaseClass( false ) {}
// Inherited from IAppSystem
virtual InitReturnVal_t Init(); virtual void Shutdown();
// Inherited from IProcessUtils
virtual ProcessHandle_t StartProcess( const char *pCommandLine, bool bConnectStdPipes ); virtual ProcessHandle_t StartProcess( int argc, const char **argv, bool bConnectStdPipes ); virtual void CloseProcess( ProcessHandle_t hProcess ); virtual void AbortProcess( ProcessHandle_t hProcess ); virtual bool IsProcessComplete( ProcessHandle_t hProcess ); virtual void WaitUntilProcessCompletes( ProcessHandle_t hProcess ); virtual int SendProcessInput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ); virtual int GetProcessOutputSize( ProcessHandle_t hProcess ); virtual int GetProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ); virtual int GetProcessExitCode( ProcessHandle_t hProcess );
private: struct ProcessInfo_t { HANDLE m_hChildStdinRd; HANDLE m_hChildStdinWr; HANDLE m_hChildStdoutRd; HANDLE m_hChildStdoutWr; HANDLE m_hChildStderrWr; HANDLE m_hProcess; CUtlString m_CommandLine; CUtlBuffer m_ProcessOutput; };
// Returns the last error that occurred
char *GetErrorString( char *pBuf, int nBufLen );
// creates the process, adds it to the list and writes the windows HANDLE into info.m_hProcess
ProcessHandle_t CreateProcess( ProcessInfo_t &info, bool bConnectStdPipes );
// Shuts down the process handle
void ShutdownProcess( ProcessHandle_t hProcess );
// Methods used to read output back from a process
int GetActualProcessOutputSize( ProcessHandle_t hProcess ); int GetActualProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen );
CUtlFixedLinkedList< ProcessInfo_t > m_Processes; ProcessHandle_t m_hCurrentProcess; bool m_bInitialized; };
//-----------------------------------------------------------------------------
// Purpose: singleton accessor
//-----------------------------------------------------------------------------
static CProcessUtils s_ProcessUtils; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CProcessUtils, IProcessUtils, PROCESS_UTILS_INTERFACE_VERSION, s_ProcessUtils );
//-----------------------------------------------------------------------------
// Initialize, shutdown process system
//-----------------------------------------------------------------------------
InitReturnVal_t CProcessUtils::Init() { InitReturnVal_t nRetVal = BaseClass::Init(); if ( nRetVal != INIT_OK ) return nRetVal;
m_bInitialized = true; m_hCurrentProcess = PROCESS_HANDLE_INVALID; return INIT_OK; }
void CProcessUtils::Shutdown() { Assert( m_bInitialized ); Assert( m_Processes.Count() == 0 ); if ( m_Processes.Count() != 0 ) { AbortProcess( m_hCurrentProcess ); } m_bInitialized = false; return BaseClass::Shutdown(); }
//-----------------------------------------------------------------------------
// Returns the last error that occurred
//-----------------------------------------------------------------------------
char *CProcessUtils::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; }
ProcessHandle_t CProcessUtils::CreateProcess( ProcessInfo_t &info, bool bConnectStdPipes ) { STARTUPINFO si; memset(&si, 0, sizeof si); si.cb = sizeof(si); if ( bConnectStdPipes ) { si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = info.m_hChildStdinRd; si.hStdError = info.m_hChildStderrWr; si.hStdOutput = info.m_hChildStdoutWr; }
PROCESS_INFORMATION pi; if ( ::CreateProcess( NULL, info.m_CommandLine.GetForModify(), NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi ) ) { info.m_hProcess = pi.hProcess; m_hCurrentProcess = m_Processes.AddToTail( info ); return m_hCurrentProcess; }
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 PROCESS_HANDLE_INVALID; }
//-----------------------------------------------------------------------------
// Options for compilation
//-----------------------------------------------------------------------------
ProcessHandle_t CProcessUtils::StartProcess( const char *pCommandLine, bool bConnectStdPipes ) { Assert( m_bInitialized );
// NOTE: For the moment, we can only run one process at a time
// although in the future, I expect to have a process queue.
if ( m_hCurrentProcess != PROCESS_HANDLE_INVALID ) { WaitUntilProcessCompletes( m_hCurrentProcess ); }
ProcessInfo_t info; info.m_CommandLine = pCommandLine;
if ( !bConnectStdPipes ) { 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, false ); }
SECURITY_ATTRIBUTES saAttr;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; // Create a pipe for the child's STDOUT.
if ( CreatePipe( &info.m_hChildStdoutRd, &info.m_hChildStdoutWr, &saAttr, 0 ) ) { if ( CreatePipe( &info.m_hChildStdinRd, &info.m_hChildStdinWr, &saAttr, 0 ) ) { if ( DuplicateHandle( GetCurrentProcess(), info.m_hChildStdoutWr, GetCurrentProcess(), &info.m_hChildStderrWr, 0, TRUE, DUPLICATE_SAME_ACCESS ) ) { // _setmode( info.m_hChildStdoutRd, _O_TEXT );
// _setmode( info.m_hChildStdoutWr, _O_TEXT );
// _setmode( info.m_hChildStderrWr, _O_TEXT );
ProcessHandle_t hProcess = CreateProcess( info, true ); if ( hProcess != PROCESS_HANDLE_INVALID ) return hProcess;
CloseHandle( info.m_hChildStderrWr ); } CloseHandle( info.m_hChildStdinRd ); CloseHandle( info.m_hChildStdinWr ); } CloseHandle( info.m_hChildStdoutRd ); CloseHandle( info.m_hChildStdoutWr ); } return PROCESS_HANDLE_INVALID; }
//-----------------------------------------------------------------------------
// Start up a process
//-----------------------------------------------------------------------------
ProcessHandle_t CProcessUtils::StartProcess( int argc, const char **argv, bool bConnectStdPipes ) { CUtlString commandLine; for ( int i = 0; i < argc; ++i ) { commandLine += argv[i]; if ( i != argc-1 ) { commandLine += " "; } } return StartProcess( commandLine.Get(), bConnectStdPipes ); }
//-----------------------------------------------------------------------------
// Shuts down the process handle
//-----------------------------------------------------------------------------
void CProcessUtils::ShutdownProcess( ProcessHandle_t hProcess ) { ProcessInfo_t& info = m_Processes[hProcess]; CloseHandle( info.m_hChildStderrWr ); CloseHandle( info.m_hChildStdinRd ); CloseHandle( info.m_hChildStdinWr ); CloseHandle( info.m_hChildStdoutRd ); CloseHandle( info.m_hChildStdoutWr );
m_Processes.Remove( hProcess ); }
//-----------------------------------------------------------------------------
// Closes the process
//-----------------------------------------------------------------------------
void CProcessUtils::CloseProcess( ProcessHandle_t hProcess ) { Assert( m_bInitialized ); if ( hProcess != PROCESS_HANDLE_INVALID ) { WaitUntilProcessCompletes( hProcess ); ShutdownProcess( hProcess ); } }
//-----------------------------------------------------------------------------
// Aborts the process
//-----------------------------------------------------------------------------
void CProcessUtils::AbortProcess( ProcessHandle_t hProcess ) { Assert( m_bInitialized ); if ( hProcess != PROCESS_HANDLE_INVALID ) { if ( !IsProcessComplete( hProcess ) ) { ProcessInfo_t& info = m_Processes[hProcess]; TerminateProcess( info.m_hProcess, 1 ); } ShutdownProcess( hProcess ); } }
//-----------------------------------------------------------------------------
// Returns true if the process is complete
//-----------------------------------------------------------------------------
bool CProcessUtils::IsProcessComplete( ProcessHandle_t hProcess ) { Assert( m_bInitialized ); Assert( hProcess != PROCESS_HANDLE_INVALID ); if ( m_hCurrentProcess != hProcess ) return true;
HANDLE h = m_Processes[hProcess].m_hProcess; return ( WaitForSingleObject( h, 0 ) != WAIT_TIMEOUT ); }
//-----------------------------------------------------------------------------
// Methods used to write input into a process
//-----------------------------------------------------------------------------
int CProcessUtils::SendProcessInput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ) { // Unimplemented yet
Assert( 0 ); return 0; }
//-----------------------------------------------------------------------------
// Methods used to read output back from a process
//-----------------------------------------------------------------------------
int CProcessUtils::GetActualProcessOutputSize( ProcessHandle_t hProcess ) { Assert( hProcess != PROCESS_HANDLE_INVALID );
ProcessInfo_t& info = m_Processes[ hProcess ]; if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE ) return 0;
DWORD dwCount = 0; if ( !PeekNamedPipe( info.m_hChildStdoutRd, 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", info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) ); return 0; }
// Add 1 for auto-NULL termination
return ( dwCount > 0 ) ? (int)dwCount + 1 : 0; }
int CProcessUtils::GetActualProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ) { ProcessInfo_t& info = m_Processes[ hProcess ]; if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE ) return 0;
DWORD dwCount = 0; DWORD dwRead = 0;
// FIXME: Is there a way of making pipes be text mode so we don't get /n/rs back?
char *pTempBuf = (char*)_alloca( nBufLen ); if ( !PeekNamedPipe( info.m_hChildStdoutRd, 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", info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) ); return 0; }
dwCount = min( dwCount, (DWORD)nBufLen - 1 ); ReadFile( info.m_hChildStdoutRd, pTempBuf, dwCount, &dwRead, NULL); // Convert /n/r -> /n
int nActualCountRead = 0; for ( unsigned int i = 0; i < dwRead; ++i ) { char c = pTempBuf[i]; if ( c == '\r' ) { if ( ( i+1 < dwRead ) && ( pTempBuf[i+1] == '\n' ) ) { pBuf[nActualCountRead++] = '\n'; ++i; continue; } }
pBuf[nActualCountRead++] = c; }
return nActualCountRead; }
int CProcessUtils::GetProcessOutputSize( ProcessHandle_t hProcess ) { Assert( m_bInitialized ); if ( hProcess == PROCESS_HANDLE_INVALID ) return 0;
return GetActualProcessOutputSize( hProcess ) + m_Processes[hProcess].m_ProcessOutput.TellPut(); }
int CProcessUtils::GetProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ) { Assert( m_bInitialized );
if ( hProcess == PROCESS_HANDLE_INVALID ) return 0;
ProcessInfo_t &info = m_Processes[hProcess]; int nCachedBytes = info.m_ProcessOutput.TellPut(); int nBytesRead = 0; if ( nCachedBytes ) { nBytesRead = min( nBufLen-1, nCachedBytes ); info.m_ProcessOutput.Get( pBuf, nBytesRead ); pBuf[ nBytesRead ] = 0; nBufLen -= nBytesRead; pBuf += nBytesRead; if ( info.m_ProcessOutput.GetBytesRemaining() == 0 ) { info.m_ProcessOutput.Purge(); }
if ( nBufLen <= 1 ) return nBytesRead; }
// Auto-NULL terminate
int nActualCountRead = GetActualProcessOutput( hProcess, pBuf, nBufLen ); pBuf[nActualCountRead] = 0; return nActualCountRead + nBytesRead + 1; }
//-----------------------------------------------------------------------------
// Returns the exit code for the process. Doesn't work unless the process is complete
//-----------------------------------------------------------------------------
int CProcessUtils::GetProcessExitCode( ProcessHandle_t hProcess ) { Assert( m_bInitialized ); ProcessInfo_t &info = m_Processes[hProcess]; DWORD nExitCode; BOOL bOk = GetExitCodeProcess( info.m_hProcess, &nExitCode ); if ( !bOk || nExitCode == STILL_ACTIVE ) return -1; return nExitCode; }
//-----------------------------------------------------------------------------
// Waits until a process is complete
//-----------------------------------------------------------------------------
void CProcessUtils::WaitUntilProcessCompletes( ProcessHandle_t hProcess ) { Assert( m_bInitialized );
// For the moment, we can only run one process at a time
if ( ( hProcess == PROCESS_HANDLE_INVALID ) || ( m_hCurrentProcess != hProcess ) ) return;
ProcessInfo_t &info = m_Processes[ hProcess ];
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, 100 ) == WAIT_TIMEOUT ) { int nLen = GetActualProcessOutputSize( hProcess ); if ( nLen > 0 ) { int nPut = info.m_ProcessOutput.TellPut(); info.m_ProcessOutput.EnsureCapacity( nPut + nLen ); int nBytesRead = GetActualProcessOutput( hProcess, (char*)info.m_ProcessOutput.PeekPut(), nLen ); info.m_ProcessOutput.SeekPut( CUtlBuffer::SEEK_HEAD, nPut + nBytesRead ); } } }
m_hCurrentProcess = PROCESS_HANDLE_INVALID; }
|