Team Fortress 2 Source Code as on 22/4/2020
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.

358 lines
8.4 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. //////////////////////////////////////////////////////////////////////
  9. //
  10. // Redirector - to redirect the input / output of a console
  11. //
  12. // Developer: Jeff Lee
  13. // Dec 10, 2001
  14. //
  15. //////////////////////////////////////////////////////////////////////
  16. #include "stdafx.h"
  17. #include "Redir.h"
  18. #ifdef _DEBUG
  19. #undef THIS_FILE
  20. static char THIS_FILE[]=__FILE__;
  21. #define new DEBUG_NEW
  22. #endif
  23. //#define _TEST_REDIR
  24. //////////////////////////////////////////////////////////////////////
  25. // Construction/Destruction
  26. //////////////////////////////////////////////////////////////////////
  27. CRedirector::CRedirector() :
  28. m_hStdinWrite(NULL),
  29. m_hStdoutRead(NULL),
  30. m_hChildProcess(NULL),
  31. m_hThread(NULL),
  32. m_hEvtStop(NULL),
  33. m_dwThreadId(0),
  34. m_dwWaitTime(1000)
  35. {
  36. }
  37. CRedirector::~CRedirector()
  38. {
  39. Close();
  40. }
  41. //////////////////////////////////////////////////////////////////////
  42. // CRedirector implementation
  43. //////////////////////////////////////////////////////////////////////
  44. BOOL CRedirector::Open(LPCTSTR pszCmdLine, LPCTSTR pszCurrentDirectory)
  45. {
  46. HANDLE hStdoutReadTmp; // parent stdout read handle
  47. HANDLE hStdoutWrite, hStderrWrite; // child stdout write handle
  48. HANDLE hStdinWriteTmp; // parent stdin write handle
  49. HANDLE hStdinRead; // child stdin read handle
  50. SECURITY_ATTRIBUTES sa;
  51. Close();
  52. hStdoutReadTmp = NULL;
  53. hStdoutWrite = hStderrWrite = NULL;
  54. hStdinWriteTmp = NULL;
  55. hStdinRead = NULL;
  56. // Set up the security attributes struct.
  57. sa.nLength = sizeof(SECURITY_ATTRIBUTES);
  58. sa.lpSecurityDescriptor = NULL;
  59. sa.bInheritHandle = TRUE;
  60. BOOL bOK = FALSE;
  61. __try
  62. {
  63. // Create a child stdout pipe.
  64. if (!::CreatePipe(&hStdoutReadTmp, &hStdoutWrite, &sa, 0))
  65. __leave;
  66. // Create a duplicate of the stdout write handle for the std
  67. // error write handle. This is necessary in case the child
  68. // application closes one of its std output handles.
  69. if (!::DuplicateHandle(
  70. ::GetCurrentProcess(),
  71. hStdoutWrite,
  72. ::GetCurrentProcess(),
  73. &hStderrWrite,
  74. 0, TRUE,
  75. DUPLICATE_SAME_ACCESS))
  76. __leave;
  77. // Create a child stdin pipe.
  78. if (!::CreatePipe(&hStdinRead, &hStdinWriteTmp, &sa, 0))
  79. __leave;
  80. // Create new stdout read handle and the stdin write handle.
  81. // Set the inheritance properties to FALSE. Otherwise, the child
  82. // inherits the these handles; resulting in non-closeable
  83. // handles to the pipes being created.
  84. if (!::DuplicateHandle(
  85. ::GetCurrentProcess(),
  86. hStdoutReadTmp,
  87. ::GetCurrentProcess(),
  88. &m_hStdoutRead,
  89. 0, FALSE, // make it uninheritable.
  90. DUPLICATE_SAME_ACCESS))
  91. __leave;
  92. if (!::DuplicateHandle(
  93. ::GetCurrentProcess(),
  94. hStdinWriteTmp,
  95. ::GetCurrentProcess(),
  96. &m_hStdinWrite,
  97. 0, FALSE, // make it uninheritable.
  98. DUPLICATE_SAME_ACCESS))
  99. __leave;
  100. // Close inheritable copies of the handles we do not want to
  101. // be inherited.
  102. DestroyHandle(hStdoutReadTmp);
  103. DestroyHandle(hStdinWriteTmp);
  104. // launch the child process
  105. if (!LaunchChild(pszCmdLine, pszCurrentDirectory,
  106. hStdoutWrite, hStdinRead, hStderrWrite))
  107. __leave;
  108. // Child is launched. Close the parents copy of those pipe
  109. // handles that only the child should have open.
  110. // Make sure that no handles to the write end of the stdout pipe
  111. // are maintained in this process or else the pipe will not
  112. // close when the child process exits and ReadFile will hang.
  113. DestroyHandle(hStdoutWrite);
  114. DestroyHandle(hStdinRead);
  115. DestroyHandle(hStderrWrite);
  116. // Launch a thread to receive output from the child process.
  117. m_hEvtStop = ::CreateEvent(NULL, TRUE, FALSE, NULL);
  118. m_hThread = ::CreateThread(
  119. NULL, 0,
  120. OutputThread,
  121. this,
  122. 0,
  123. &m_dwThreadId);
  124. if (!m_hThread)
  125. __leave;
  126. bOK = TRUE;
  127. }
  128. __finally
  129. {
  130. if (!bOK)
  131. {
  132. DWORD dwOsErr = ::GetLastError();
  133. char szMsg[40];
  134. ::sprintf(szMsg, "Redirect console error: %x\r\n", dwOsErr);
  135. WriteStdError(szMsg);
  136. DestroyHandle(hStdoutReadTmp);
  137. DestroyHandle(hStdoutWrite);
  138. DestroyHandle(hStderrWrite);
  139. DestroyHandle(hStdinWriteTmp);
  140. DestroyHandle(hStdinRead);
  141. Close();
  142. ::SetLastError(dwOsErr);
  143. }
  144. }
  145. return bOK;
  146. }
  147. void CRedirector::Close()
  148. {
  149. if (m_hThread != NULL)
  150. {
  151. // this function might be called from redir thread
  152. if (::GetCurrentThreadId() != m_dwThreadId)
  153. {
  154. ASSERT(m_hEvtStop != NULL);
  155. ::SetEvent(m_hEvtStop);
  156. //::WaitForSingleObject(m_hThread, INFINITE);
  157. if (::WaitForSingleObject(m_hThread, 5000) == WAIT_TIMEOUT)
  158. {
  159. WriteStdError(_T("The redir thread is dead\r\n"));
  160. ::TerminateThread(m_hThread, -2);
  161. }
  162. }
  163. DestroyHandle(m_hThread);
  164. }
  165. DestroyHandle(m_hEvtStop);
  166. DestroyHandle(m_hChildProcess);
  167. DestroyHandle(m_hStdinWrite);
  168. DestroyHandle(m_hStdoutRead);
  169. m_dwThreadId = 0;
  170. }
  171. // write data to the child's stdin
  172. BOOL CRedirector::Printf(LPCTSTR pszFormat, ...)
  173. {
  174. if (!m_hStdinWrite)
  175. return FALSE;
  176. CString strInput;
  177. va_list argList;
  178. va_start(argList, pszFormat);
  179. strInput.FormatV(pszFormat, argList);
  180. va_end(argList);
  181. DWORD dwWritten;
  182. return ::WriteFile(m_hStdinWrite, (LPCTSTR)strInput,
  183. strInput.GetLength(), &dwWritten, NULL);
  184. }
  185. BOOL CRedirector::LaunchChild(LPCTSTR pszCmdLine,
  186. LPCTSTR pszCurrentDirectory,
  187. HANDLE hStdOut,
  188. HANDLE hStdIn,
  189. HANDLE hStdErr)
  190. {
  191. PROCESS_INFORMATION pi;
  192. STARTUPINFO si;
  193. ASSERT(::AfxIsValidString(pszCmdLine));
  194. ASSERT(m_hChildProcess == NULL);
  195. // Set up the start up info struct.
  196. ::ZeroMemory(&si, sizeof(STARTUPINFO));
  197. si.cb = sizeof(STARTUPINFO);
  198. si.hStdOutput = hStdOut;
  199. si.hStdInput = hStdIn;
  200. si.hStdError = hStdErr;
  201. si.wShowWindow = SW_HIDE;
  202. si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
  203. // Note that dwFlags must include STARTF_USESHOWWINDOW if we
  204. // use the wShowWindow flags. This also assumes that the
  205. // CreateProcess() call will use CREATE_NEW_CONSOLE.
  206. // Launch the child process.
  207. if (!::CreateProcess(
  208. NULL,
  209. (LPTSTR)pszCmdLine,
  210. NULL, NULL,
  211. TRUE,
  212. CREATE_NEW_CONSOLE,
  213. NULL, pszCurrentDirectory,
  214. &si,
  215. &pi))
  216. return FALSE;
  217. m_hChildProcess = pi.hProcess;
  218. // Close any unuseful handles
  219. ::CloseHandle(pi.hThread);
  220. return TRUE;
  221. }
  222. // redirect the child process's stdout:
  223. // return: 1: no more data, 0: child terminated, -1: os error
  224. int CRedirector::RedirectStdout()
  225. {
  226. ASSERT(m_hStdoutRead != NULL);
  227. for (;;)
  228. {
  229. DWORD dwAvail = 0;
  230. if (!::PeekNamedPipe(m_hStdoutRead, NULL, 0, NULL,
  231. &dwAvail, NULL)) // error
  232. break;
  233. if (!dwAvail) // not data available
  234. return 1;
  235. char szOutput[16*1024 + 1];
  236. DWORD dwRead = 0;
  237. if (!::ReadFile(m_hStdoutRead, szOutput, min(16*1024, dwAvail),
  238. &dwRead, NULL) || !dwRead) // error, the child might ended
  239. break;
  240. szOutput[dwRead] = 0;
  241. WriteStdOut(szOutput);
  242. }
  243. DWORD dwError = ::GetLastError();
  244. if (dwError == ERROR_BROKEN_PIPE || // pipe has been ended
  245. dwError == ERROR_NO_DATA) // pipe closing in progress
  246. {
  247. #ifdef _TEST_REDIR
  248. WriteStdOut("\r\n<TEST INFO>: Child process ended\r\n");
  249. #endif
  250. return 0; // child process ended
  251. }
  252. WriteStdError("Read stdout pipe error\r\n");
  253. return -1; // os error
  254. }
  255. void CRedirector::DestroyHandle(HANDLE& rhObject)
  256. {
  257. if (rhObject != NULL)
  258. {
  259. ::CloseHandle(rhObject);
  260. rhObject = NULL;
  261. }
  262. }
  263. void CRedirector::WriteStdOut(LPCSTR pszOutput)
  264. {
  265. TRACE("%s", pszOutput);
  266. }
  267. void CRedirector::WriteStdError(LPCSTR pszError)
  268. {
  269. TRACE("%s", pszError);
  270. }
  271. // thread to receive output of the child process
  272. DWORD WINAPI CRedirector::OutputThread(LPVOID lpvThreadParam)
  273. {
  274. HANDLE aHandles[2];
  275. int nRet;
  276. CRedirector* pRedir = (CRedirector*) lpvThreadParam;
  277. ASSERT(pRedir != NULL);
  278. aHandles[0] = pRedir->m_hChildProcess;
  279. aHandles[1] = pRedir->m_hEvtStop;
  280. aHandles[2] = pRedir->m_hStdoutRead;
  281. for (;;)
  282. {
  283. // redirect stdout till there's no more data.
  284. nRet = pRedir->RedirectStdout();
  285. if (nRet <= 0)
  286. break;
  287. // check if the child process has terminated.
  288. DWORD dwRc = ::WaitForMultipleObjects(
  289. 3, aHandles, FALSE, pRedir->m_dwWaitTime);
  290. if (WAIT_OBJECT_0 == dwRc || WAIT_FAILED == dwRc ) // the child process ended
  291. {
  292. nRet = pRedir->RedirectStdout();
  293. if (nRet > 0)
  294. nRet = 0;
  295. break;
  296. }
  297. if (WAIT_OBJECT_0+1 == dwRc) // m_hEvtStop was signalled
  298. {
  299. nRet = 1; // cancelled
  300. break;
  301. }
  302. // If we don't sleep here, then syncfrommirror will eat lots of CPU looping here.
  303. Sleep( 20 );
  304. }
  305. // close handles
  306. pRedir->Close();
  307. return nRet;
  308. }