//========= 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: 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; }