You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
473 lines
14 KiB
473 lines
14 KiB
//========= 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;
|
|
}
|
|
|
|
|
|
|