diff options
Diffstat (limited to 'devtools/syncfrommirror/Redir.cpp')
| -rw-r--r-- | devtools/syncfrommirror/Redir.cpp | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/devtools/syncfrommirror/Redir.cpp b/devtools/syncfrommirror/Redir.cpp new file mode 100644 index 0000000..cbb6203 --- /dev/null +++ b/devtools/syncfrommirror/Redir.cpp @@ -0,0 +1,358 @@ +//========= 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; +} |