summaryrefslogtreecommitdiff
path: root/devtools/syncfrommirror/Redir.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/syncfrommirror/Redir.cpp')
-rw-r--r--devtools/syncfrommirror/Redir.cpp358
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;
+}