mirror of
https://github.com/nillerusr/source-engine.git
synced 2024-12-22 14:16:50 +00:00
359 lines
8.4 KiB
C++
359 lines
8.4 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//
|
|
//=============================================================================//
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Redirector - to redirect the input / output of a console
|
|
//
|
|
// Developer: Jeff Lee
|
|
// Dec 10, 2001
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include "stdafx.h"
|
|
#include "Redir.h"
|
|
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[]=__FILE__;
|
|
#define new DEBUG_NEW
|
|
#endif
|
|
|
|
//#define _TEST_REDIR
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Construction/Destruction
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
CRedirector::CRedirector() :
|
|
m_hStdinWrite(NULL),
|
|
m_hStdoutRead(NULL),
|
|
m_hChildProcess(NULL),
|
|
m_hThread(NULL),
|
|
m_hEvtStop(NULL),
|
|
m_dwThreadId(0),
|
|
m_dwWaitTime(1000)
|
|
{
|
|
}
|
|
|
|
CRedirector::~CRedirector()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// CRedirector implementation
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
BOOL CRedirector::Open(LPCTSTR pszCmdLine, LPCTSTR pszCurrentDirectory)
|
|
{
|
|
HANDLE hStdoutReadTmp; // parent stdout read handle
|
|
HANDLE hStdoutWrite, hStderrWrite; // child stdout write handle
|
|
HANDLE hStdinWriteTmp; // parent stdin write handle
|
|
HANDLE hStdinRead; // child stdin read handle
|
|
SECURITY_ATTRIBUTES sa;
|
|
|
|
Close();
|
|
hStdoutReadTmp = NULL;
|
|
hStdoutWrite = hStderrWrite = NULL;
|
|
hStdinWriteTmp = NULL;
|
|
hStdinRead = NULL;
|
|
|
|
// Set up the security attributes struct.
|
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
sa.lpSecurityDescriptor = NULL;
|
|
sa.bInheritHandle = TRUE;
|
|
|
|
BOOL bOK = FALSE;
|
|
__try
|
|
{
|
|
// Create a child stdout pipe.
|
|
if (!::CreatePipe(&hStdoutReadTmp, &hStdoutWrite, &sa, 0))
|
|
__leave;
|
|
|
|
// Create a duplicate of the stdout write handle for the std
|
|
// error write handle. This is necessary in case the child
|
|
// application closes one of its std output handles.
|
|
if (!::DuplicateHandle(
|
|
::GetCurrentProcess(),
|
|
hStdoutWrite,
|
|
::GetCurrentProcess(),
|
|
&hStderrWrite,
|
|
0, TRUE,
|
|
DUPLICATE_SAME_ACCESS))
|
|
__leave;
|
|
|
|
// Create a child stdin pipe.
|
|
if (!::CreatePipe(&hStdinRead, &hStdinWriteTmp, &sa, 0))
|
|
__leave;
|
|
|
|
// Create new stdout read handle and the stdin write handle.
|
|
// Set the inheritance properties to FALSE. Otherwise, the child
|
|
// inherits the these handles; resulting in non-closeable
|
|
// handles to the pipes being created.
|
|
if (!::DuplicateHandle(
|
|
::GetCurrentProcess(),
|
|
hStdoutReadTmp,
|
|
::GetCurrentProcess(),
|
|
&m_hStdoutRead,
|
|
0, FALSE, // make it uninheritable.
|
|
DUPLICATE_SAME_ACCESS))
|
|
__leave;
|
|
|
|
if (!::DuplicateHandle(
|
|
::GetCurrentProcess(),
|
|
hStdinWriteTmp,
|
|
::GetCurrentProcess(),
|
|
&m_hStdinWrite,
|
|
0, FALSE, // make it uninheritable.
|
|
DUPLICATE_SAME_ACCESS))
|
|
__leave;
|
|
|
|
// Close inheritable copies of the handles we do not want to
|
|
// be inherited.
|
|
DestroyHandle(hStdoutReadTmp);
|
|
DestroyHandle(hStdinWriteTmp);
|
|
|
|
// launch the child process
|
|
if (!LaunchChild(pszCmdLine, pszCurrentDirectory,
|
|
hStdoutWrite, hStdinRead, hStderrWrite))
|
|
__leave;
|
|
|
|
// Child is launched. Close the parents copy of those pipe
|
|
// handles that only the child should have open.
|
|
// Make sure that no handles to the write end of the stdout pipe
|
|
// are maintained in this process or else the pipe will not
|
|
// close when the child process exits and ReadFile will hang.
|
|
DestroyHandle(hStdoutWrite);
|
|
DestroyHandle(hStdinRead);
|
|
DestroyHandle(hStderrWrite);
|
|
|
|
// Launch a thread to receive output from the child process.
|
|
m_hEvtStop = ::CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
m_hThread = ::CreateThread(
|
|
NULL, 0,
|
|
OutputThread,
|
|
this,
|
|
0,
|
|
&m_dwThreadId);
|
|
if (!m_hThread)
|
|
__leave;
|
|
|
|
bOK = TRUE;
|
|
}
|
|
|
|
__finally
|
|
{
|
|
if (!bOK)
|
|
{
|
|
DWORD dwOsErr = ::GetLastError();
|
|
char szMsg[40];
|
|
::sprintf(szMsg, "Redirect console error: %x\r\n", dwOsErr);
|
|
WriteStdError(szMsg);
|
|
DestroyHandle(hStdoutReadTmp);
|
|
DestroyHandle(hStdoutWrite);
|
|
DestroyHandle(hStderrWrite);
|
|
DestroyHandle(hStdinWriteTmp);
|
|
DestroyHandle(hStdinRead);
|
|
Close();
|
|
::SetLastError(dwOsErr);
|
|
}
|
|
}
|
|
|
|
return bOK;
|
|
}
|
|
|
|
void CRedirector::Close()
|
|
{
|
|
if (m_hThread != NULL)
|
|
{
|
|
// this function might be called from redir thread
|
|
if (::GetCurrentThreadId() != m_dwThreadId)
|
|
{
|
|
ASSERT(m_hEvtStop != NULL);
|
|
::SetEvent(m_hEvtStop);
|
|
//::WaitForSingleObject(m_hThread, INFINITE);
|
|
if (::WaitForSingleObject(m_hThread, 5000) == WAIT_TIMEOUT)
|
|
{
|
|
WriteStdError(_T("The redir thread is dead\r\n"));
|
|
::TerminateThread(m_hThread, -2);
|
|
}
|
|
}
|
|
|
|
DestroyHandle(m_hThread);
|
|
}
|
|
|
|
DestroyHandle(m_hEvtStop);
|
|
DestroyHandle(m_hChildProcess);
|
|
DestroyHandle(m_hStdinWrite);
|
|
DestroyHandle(m_hStdoutRead);
|
|
m_dwThreadId = 0;
|
|
}
|
|
|
|
// write data to the child's stdin
|
|
BOOL CRedirector::Printf(LPCTSTR pszFormat, ...)
|
|
{
|
|
if (!m_hStdinWrite)
|
|
return FALSE;
|
|
|
|
CString strInput;
|
|
va_list argList;
|
|
|
|
va_start(argList, pszFormat);
|
|
strInput.FormatV(pszFormat, argList);
|
|
va_end(argList);
|
|
|
|
DWORD dwWritten;
|
|
return ::WriteFile(m_hStdinWrite, (LPCTSTR)strInput,
|
|
strInput.GetLength(), &dwWritten, NULL);
|
|
}
|
|
|
|
BOOL CRedirector::LaunchChild(LPCTSTR pszCmdLine,
|
|
LPCTSTR pszCurrentDirectory,
|
|
HANDLE hStdOut,
|
|
HANDLE hStdIn,
|
|
HANDLE hStdErr)
|
|
{
|
|
PROCESS_INFORMATION pi;
|
|
STARTUPINFO si;
|
|
|
|
ASSERT(::AfxIsValidString(pszCmdLine));
|
|
ASSERT(m_hChildProcess == NULL);
|
|
|
|
// Set up the start up info struct.
|
|
::ZeroMemory(&si, sizeof(STARTUPINFO));
|
|
si.cb = sizeof(STARTUPINFO);
|
|
si.hStdOutput = hStdOut;
|
|
si.hStdInput = hStdIn;
|
|
si.hStdError = hStdErr;
|
|
si.wShowWindow = SW_HIDE;
|
|
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
|
|
|
// Note that dwFlags must include STARTF_USESHOWWINDOW if we
|
|
// use the wShowWindow flags. This also assumes that the
|
|
// CreateProcess() call will use CREATE_NEW_CONSOLE.
|
|
|
|
// Launch the child process.
|
|
if (!::CreateProcess(
|
|
NULL,
|
|
(LPTSTR)pszCmdLine,
|
|
NULL, NULL,
|
|
TRUE,
|
|
CREATE_NEW_CONSOLE,
|
|
NULL, pszCurrentDirectory,
|
|
&si,
|
|
&pi))
|
|
return FALSE;
|
|
|
|
m_hChildProcess = pi.hProcess;
|
|
// Close any unuseful handles
|
|
::CloseHandle(pi.hThread);
|
|
return TRUE;
|
|
}
|
|
|
|
// redirect the child process's stdout:
|
|
// return: 1: no more data, 0: child terminated, -1: os error
|
|
int CRedirector::RedirectStdout()
|
|
{
|
|
ASSERT(m_hStdoutRead != NULL);
|
|
for (;;)
|
|
{
|
|
DWORD dwAvail = 0;
|
|
if (!::PeekNamedPipe(m_hStdoutRead, NULL, 0, NULL,
|
|
&dwAvail, NULL)) // error
|
|
break;
|
|
|
|
if (!dwAvail) // not data available
|
|
return 1;
|
|
|
|
char szOutput[16*1024 + 1];
|
|
DWORD dwRead = 0;
|
|
if (!::ReadFile(m_hStdoutRead, szOutput, min(16*1024, dwAvail),
|
|
&dwRead, NULL) || !dwRead) // error, the child might ended
|
|
break;
|
|
|
|
szOutput[dwRead] = 0;
|
|
WriteStdOut(szOutput);
|
|
}
|
|
|
|
DWORD dwError = ::GetLastError();
|
|
if (dwError == ERROR_BROKEN_PIPE || // pipe has been ended
|
|
dwError == ERROR_NO_DATA) // pipe closing in progress
|
|
{
|
|
#ifdef _TEST_REDIR
|
|
WriteStdOut("\r\n<TEST INFO>: Child process ended\r\n");
|
|
#endif
|
|
return 0; // child process ended
|
|
}
|
|
|
|
WriteStdError("Read stdout pipe error\r\n");
|
|
return -1; // os error
|
|
}
|
|
|
|
void CRedirector::DestroyHandle(HANDLE& rhObject)
|
|
{
|
|
if (rhObject != NULL)
|
|
{
|
|
::CloseHandle(rhObject);
|
|
rhObject = NULL;
|
|
}
|
|
}
|
|
|
|
void CRedirector::WriteStdOut(LPCSTR pszOutput)
|
|
{
|
|
TRACE("%s", pszOutput);
|
|
}
|
|
|
|
void CRedirector::WriteStdError(LPCSTR pszError)
|
|
{
|
|
TRACE("%s", pszError);
|
|
}
|
|
|
|
// thread to receive output of the child process
|
|
DWORD WINAPI CRedirector::OutputThread(LPVOID lpvThreadParam)
|
|
{
|
|
HANDLE aHandles[2];
|
|
int nRet;
|
|
CRedirector* pRedir = (CRedirector*) lpvThreadParam;
|
|
|
|
ASSERT(pRedir != NULL);
|
|
aHandles[0] = pRedir->m_hChildProcess;
|
|
aHandles[1] = pRedir->m_hEvtStop;
|
|
aHandles[2] = pRedir->m_hStdoutRead;
|
|
|
|
for (;;)
|
|
{
|
|
// redirect stdout till there's no more data.
|
|
nRet = pRedir->RedirectStdout();
|
|
if (nRet <= 0)
|
|
break;
|
|
|
|
// check if the child process has terminated.
|
|
DWORD dwRc = ::WaitForMultipleObjects(
|
|
3, aHandles, FALSE, pRedir->m_dwWaitTime);
|
|
if (WAIT_OBJECT_0 == dwRc || WAIT_FAILED == dwRc ) // the child process ended
|
|
{
|
|
nRet = pRedir->RedirectStdout();
|
|
if (nRet > 0)
|
|
nRet = 0;
|
|
break;
|
|
}
|
|
if (WAIT_OBJECT_0+1 == dwRc) // m_hEvtStop was signalled
|
|
{
|
|
nRet = 1; // cancelled
|
|
break;
|
|
}
|
|
|
|
// If we don't sleep here, then syncfrommirror will eat lots of CPU looping here.
|
|
Sleep( 20 );
|
|
}
|
|
|
|
// close handles
|
|
pRedir->Close();
|
|
return nRet;
|
|
}
|