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.

473 lines
14 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #if !defined( _X360 )
  7. #include <windows.h>
  8. #endif
  9. #include "vstdlib/iprocessutils.h"
  10. #include "tier1/utllinkedlist.h"
  11. #include "tier1/utlstring.h"
  12. #include "tier1/utlbuffer.h"
  13. #include "tier1/tier1.h"
  14. //-----------------------------------------------------------------------------
  15. // At the moment, we can only run one process at a time
  16. //-----------------------------------------------------------------------------
  17. class CProcessUtils : public CTier1AppSystem< IProcessUtils >
  18. {
  19. typedef CTier1AppSystem< IProcessUtils > BaseClass;
  20. public:
  21. CProcessUtils() : BaseClass( false ) {}
  22. // Inherited from IAppSystem
  23. virtual InitReturnVal_t Init();
  24. virtual void Shutdown();
  25. // Inherited from IProcessUtils
  26. virtual ProcessHandle_t StartProcess( const char *pCommandLine, bool bConnectStdPipes );
  27. virtual ProcessHandle_t StartProcess( int argc, const char **argv, bool bConnectStdPipes );
  28. virtual void CloseProcess( ProcessHandle_t hProcess );
  29. virtual void AbortProcess( ProcessHandle_t hProcess );
  30. virtual bool IsProcessComplete( ProcessHandle_t hProcess );
  31. virtual void WaitUntilProcessCompletes( ProcessHandle_t hProcess );
  32. virtual int SendProcessInput( ProcessHandle_t hProcess, char *pBuf, int nBufLen );
  33. virtual int GetProcessOutputSize( ProcessHandle_t hProcess );
  34. virtual int GetProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen );
  35. virtual int GetProcessExitCode( ProcessHandle_t hProcess );
  36. private:
  37. struct ProcessInfo_t
  38. {
  39. HANDLE m_hChildStdinRd;
  40. HANDLE m_hChildStdinWr;
  41. HANDLE m_hChildStdoutRd;
  42. HANDLE m_hChildStdoutWr;
  43. HANDLE m_hChildStderrWr;
  44. HANDLE m_hProcess;
  45. CUtlString m_CommandLine;
  46. CUtlBuffer m_ProcessOutput;
  47. };
  48. // Returns the last error that occurred
  49. char *GetErrorString( char *pBuf, int nBufLen );
  50. // creates the process, adds it to the list and writes the windows HANDLE into info.m_hProcess
  51. ProcessHandle_t CreateProcess( ProcessInfo_t &info, bool bConnectStdPipes );
  52. // Shuts down the process handle
  53. void ShutdownProcess( ProcessHandle_t hProcess );
  54. // Methods used to read output back from a process
  55. int GetActualProcessOutputSize( ProcessHandle_t hProcess );
  56. int GetActualProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen );
  57. CUtlFixedLinkedList< ProcessInfo_t > m_Processes;
  58. ProcessHandle_t m_hCurrentProcess;
  59. bool m_bInitialized;
  60. };
  61. //-----------------------------------------------------------------------------
  62. // Purpose: singleton accessor
  63. //-----------------------------------------------------------------------------
  64. static CProcessUtils s_ProcessUtils;
  65. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CProcessUtils, IProcessUtils, PROCESS_UTILS_INTERFACE_VERSION, s_ProcessUtils );
  66. //-----------------------------------------------------------------------------
  67. // Initialize, shutdown process system
  68. //-----------------------------------------------------------------------------
  69. InitReturnVal_t CProcessUtils::Init()
  70. {
  71. InitReturnVal_t nRetVal = BaseClass::Init();
  72. if ( nRetVal != INIT_OK )
  73. return nRetVal;
  74. m_bInitialized = true;
  75. m_hCurrentProcess = PROCESS_HANDLE_INVALID;
  76. return INIT_OK;
  77. }
  78. void CProcessUtils::Shutdown()
  79. {
  80. Assert( m_bInitialized );
  81. Assert( m_Processes.Count() == 0 );
  82. if ( m_Processes.Count() != 0 )
  83. {
  84. AbortProcess( m_hCurrentProcess );
  85. }
  86. m_bInitialized = false;
  87. return BaseClass::Shutdown();
  88. }
  89. //-----------------------------------------------------------------------------
  90. // Returns the last error that occurred
  91. //-----------------------------------------------------------------------------
  92. char *CProcessUtils::GetErrorString( char *pBuf, int nBufLen )
  93. {
  94. FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, pBuf, nBufLen, NULL );
  95. char *p = strchr(pBuf, '\r'); // get rid of \r\n
  96. if(p)
  97. {
  98. p[0] = 0;
  99. }
  100. return pBuf;
  101. }
  102. ProcessHandle_t CProcessUtils::CreateProcess( ProcessInfo_t &info, bool bConnectStdPipes )
  103. {
  104. STARTUPINFO si;
  105. memset(&si, 0, sizeof si);
  106. si.cb = sizeof(si);
  107. if ( bConnectStdPipes )
  108. {
  109. si.dwFlags = STARTF_USESTDHANDLES;
  110. si.hStdInput = info.m_hChildStdinRd;
  111. si.hStdError = info.m_hChildStderrWr;
  112. si.hStdOutput = info.m_hChildStdoutWr;
  113. }
  114. PROCESS_INFORMATION pi;
  115. if ( ::CreateProcess( NULL, info.m_CommandLine.GetForModify(), NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi ) )
  116. {
  117. info.m_hProcess = pi.hProcess;
  118. m_hCurrentProcess = m_Processes.AddToTail( info );
  119. return m_hCurrentProcess;
  120. }
  121. char buf[ 512 ];
  122. Warning( "Could not execute the command:\n %s\n"
  123. "Windows gave the error message:\n \"%s\"\n",
  124. info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) );
  125. return PROCESS_HANDLE_INVALID;
  126. }
  127. //-----------------------------------------------------------------------------
  128. // Options for compilation
  129. //-----------------------------------------------------------------------------
  130. ProcessHandle_t CProcessUtils::StartProcess( const char *pCommandLine, bool bConnectStdPipes )
  131. {
  132. Assert( m_bInitialized );
  133. // NOTE: For the moment, we can only run one process at a time
  134. // although in the future, I expect to have a process queue.
  135. if ( m_hCurrentProcess != PROCESS_HANDLE_INVALID )
  136. {
  137. WaitUntilProcessCompletes( m_hCurrentProcess );
  138. }
  139. ProcessInfo_t info;
  140. info.m_CommandLine = pCommandLine;
  141. if ( !bConnectStdPipes )
  142. {
  143. info.m_hChildStderrWr = INVALID_HANDLE_VALUE;
  144. info.m_hChildStdinRd = info.m_hChildStdinWr = INVALID_HANDLE_VALUE;
  145. info.m_hChildStdoutRd = info.m_hChildStdoutWr = INVALID_HANDLE_VALUE;
  146. return CreateProcess( info, false );
  147. }
  148. SECURITY_ATTRIBUTES saAttr;
  149. // Set the bInheritHandle flag so pipe handles are inherited.
  150. saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
  151. saAttr.bInheritHandle = TRUE;
  152. saAttr.lpSecurityDescriptor = NULL;
  153. // Create a pipe for the child's STDOUT.
  154. if ( CreatePipe( &info.m_hChildStdoutRd, &info.m_hChildStdoutWr, &saAttr, 0 ) )
  155. {
  156. if ( CreatePipe( &info.m_hChildStdinRd, &info.m_hChildStdinWr, &saAttr, 0 ) )
  157. {
  158. if ( DuplicateHandle( GetCurrentProcess(), info.m_hChildStdoutWr, GetCurrentProcess(),
  159. &info.m_hChildStderrWr, 0, TRUE, DUPLICATE_SAME_ACCESS ) )
  160. {
  161. // _setmode( info.m_hChildStdoutRd, _O_TEXT );
  162. // _setmode( info.m_hChildStdoutWr, _O_TEXT );
  163. // _setmode( info.m_hChildStderrWr, _O_TEXT );
  164. ProcessHandle_t hProcess = CreateProcess( info, true );
  165. if ( hProcess != PROCESS_HANDLE_INVALID )
  166. return hProcess;
  167. CloseHandle( info.m_hChildStderrWr );
  168. }
  169. CloseHandle( info.m_hChildStdinRd );
  170. CloseHandle( info.m_hChildStdinWr );
  171. }
  172. CloseHandle( info.m_hChildStdoutRd );
  173. CloseHandle( info.m_hChildStdoutWr );
  174. }
  175. return PROCESS_HANDLE_INVALID;
  176. }
  177. //-----------------------------------------------------------------------------
  178. // Start up a process
  179. //-----------------------------------------------------------------------------
  180. ProcessHandle_t CProcessUtils::StartProcess( int argc, const char **argv, bool bConnectStdPipes )
  181. {
  182. CUtlString commandLine;
  183. for ( int i = 0; i < argc; ++i )
  184. {
  185. commandLine += argv[i];
  186. if ( i != argc-1 )
  187. {
  188. commandLine += " ";
  189. }
  190. }
  191. return StartProcess( commandLine.Get(), bConnectStdPipes );
  192. }
  193. //-----------------------------------------------------------------------------
  194. // Shuts down the process handle
  195. //-----------------------------------------------------------------------------
  196. void CProcessUtils::ShutdownProcess( ProcessHandle_t hProcess )
  197. {
  198. ProcessInfo_t& info = m_Processes[hProcess];
  199. CloseHandle( info.m_hChildStderrWr );
  200. CloseHandle( info.m_hChildStdinRd );
  201. CloseHandle( info.m_hChildStdinWr );
  202. CloseHandle( info.m_hChildStdoutRd );
  203. CloseHandle( info.m_hChildStdoutWr );
  204. m_Processes.Remove( hProcess );
  205. }
  206. //-----------------------------------------------------------------------------
  207. // Closes the process
  208. //-----------------------------------------------------------------------------
  209. void CProcessUtils::CloseProcess( ProcessHandle_t hProcess )
  210. {
  211. Assert( m_bInitialized );
  212. if ( hProcess != PROCESS_HANDLE_INVALID )
  213. {
  214. WaitUntilProcessCompletes( hProcess );
  215. ShutdownProcess( hProcess );
  216. }
  217. }
  218. //-----------------------------------------------------------------------------
  219. // Aborts the process
  220. //-----------------------------------------------------------------------------
  221. void CProcessUtils::AbortProcess( ProcessHandle_t hProcess )
  222. {
  223. Assert( m_bInitialized );
  224. if ( hProcess != PROCESS_HANDLE_INVALID )
  225. {
  226. if ( !IsProcessComplete( hProcess ) )
  227. {
  228. ProcessInfo_t& info = m_Processes[hProcess];
  229. TerminateProcess( info.m_hProcess, 1 );
  230. }
  231. ShutdownProcess( hProcess );
  232. }
  233. }
  234. //-----------------------------------------------------------------------------
  235. // Returns true if the process is complete
  236. //-----------------------------------------------------------------------------
  237. bool CProcessUtils::IsProcessComplete( ProcessHandle_t hProcess )
  238. {
  239. Assert( m_bInitialized );
  240. Assert( hProcess != PROCESS_HANDLE_INVALID );
  241. if ( m_hCurrentProcess != hProcess )
  242. return true;
  243. HANDLE h = m_Processes[hProcess].m_hProcess;
  244. return ( WaitForSingleObject( h, 0 ) != WAIT_TIMEOUT );
  245. }
  246. //-----------------------------------------------------------------------------
  247. // Methods used to write input into a process
  248. //-----------------------------------------------------------------------------
  249. int CProcessUtils::SendProcessInput( ProcessHandle_t hProcess, char *pBuf, int nBufLen )
  250. {
  251. // Unimplemented yet
  252. Assert( 0 );
  253. return 0;
  254. }
  255. //-----------------------------------------------------------------------------
  256. // Methods used to read output back from a process
  257. //-----------------------------------------------------------------------------
  258. int CProcessUtils::GetActualProcessOutputSize( ProcessHandle_t hProcess )
  259. {
  260. Assert( hProcess != PROCESS_HANDLE_INVALID );
  261. ProcessInfo_t& info = m_Processes[ hProcess ];
  262. if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE )
  263. return 0;
  264. DWORD dwCount = 0;
  265. if ( !PeekNamedPipe( info.m_hChildStdoutRd, NULL, NULL, NULL, &dwCount, NULL ) )
  266. {
  267. char buf[ 512 ];
  268. Warning( "Could not read from pipe associated with command %s\n"
  269. "Windows gave the error message:\n \"%s\"\n",
  270. info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) );
  271. return 0;
  272. }
  273. // Add 1 for auto-NULL termination
  274. return ( dwCount > 0 ) ? (int)dwCount + 1 : 0;
  275. }
  276. int CProcessUtils::GetActualProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen )
  277. {
  278. ProcessInfo_t& info = m_Processes[ hProcess ];
  279. if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE )
  280. return 0;
  281. DWORD dwCount = 0;
  282. DWORD dwRead = 0;
  283. // FIXME: Is there a way of making pipes be text mode so we don't get /n/rs back?
  284. char *pTempBuf = (char*)_alloca( nBufLen );
  285. if ( !PeekNamedPipe( info.m_hChildStdoutRd, NULL, NULL, NULL, &dwCount, NULL ) )
  286. {
  287. char buf[ 512 ];
  288. Warning( "Could not read from pipe associated with command %s\n"
  289. "Windows gave the error message:\n \"%s\"\n",
  290. info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) );
  291. return 0;
  292. }
  293. dwCount = min( dwCount, (DWORD)nBufLen - 1 );
  294. ReadFile( info.m_hChildStdoutRd, pTempBuf, dwCount, &dwRead, NULL);
  295. // Convert /n/r -> /n
  296. int nActualCountRead = 0;
  297. for ( unsigned int i = 0; i < dwRead; ++i )
  298. {
  299. char c = pTempBuf[i];
  300. if ( c == '\r' )
  301. {
  302. if ( ( i+1 < dwRead ) && ( pTempBuf[i+1] == '\n' ) )
  303. {
  304. pBuf[nActualCountRead++] = '\n';
  305. ++i;
  306. continue;
  307. }
  308. }
  309. pBuf[nActualCountRead++] = c;
  310. }
  311. return nActualCountRead;
  312. }
  313. int CProcessUtils::GetProcessOutputSize( ProcessHandle_t hProcess )
  314. {
  315. Assert( m_bInitialized );
  316. if ( hProcess == PROCESS_HANDLE_INVALID )
  317. return 0;
  318. return GetActualProcessOutputSize( hProcess ) + m_Processes[hProcess].m_ProcessOutput.TellPut();
  319. }
  320. int CProcessUtils::GetProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen )
  321. {
  322. Assert( m_bInitialized );
  323. if ( hProcess == PROCESS_HANDLE_INVALID )
  324. return 0;
  325. ProcessInfo_t &info = m_Processes[hProcess];
  326. int nCachedBytes = info.m_ProcessOutput.TellPut();
  327. int nBytesRead = 0;
  328. if ( nCachedBytes )
  329. {
  330. nBytesRead = min( nBufLen-1, nCachedBytes );
  331. info.m_ProcessOutput.Get( pBuf, nBytesRead );
  332. pBuf[ nBytesRead ] = 0;
  333. nBufLen -= nBytesRead;
  334. pBuf += nBytesRead;
  335. if ( info.m_ProcessOutput.GetBytesRemaining() == 0 )
  336. {
  337. info.m_ProcessOutput.Purge();
  338. }
  339. if ( nBufLen <= 1 )
  340. return nBytesRead;
  341. }
  342. // Auto-NULL terminate
  343. int nActualCountRead = GetActualProcessOutput( hProcess, pBuf, nBufLen );
  344. pBuf[nActualCountRead] = 0;
  345. return nActualCountRead + nBytesRead + 1;
  346. }
  347. //-----------------------------------------------------------------------------
  348. // Returns the exit code for the process. Doesn't work unless the process is complete
  349. //-----------------------------------------------------------------------------
  350. int CProcessUtils::GetProcessExitCode( ProcessHandle_t hProcess )
  351. {
  352. Assert( m_bInitialized );
  353. ProcessInfo_t &info = m_Processes[hProcess];
  354. DWORD nExitCode;
  355. BOOL bOk = GetExitCodeProcess( info.m_hProcess, &nExitCode );
  356. if ( !bOk || nExitCode == STILL_ACTIVE )
  357. return -1;
  358. return nExitCode;
  359. }
  360. //-----------------------------------------------------------------------------
  361. // Waits until a process is complete
  362. //-----------------------------------------------------------------------------
  363. void CProcessUtils::WaitUntilProcessCompletes( ProcessHandle_t hProcess )
  364. {
  365. Assert( m_bInitialized );
  366. // For the moment, we can only run one process at a time
  367. if ( ( hProcess == PROCESS_HANDLE_INVALID ) || ( m_hCurrentProcess != hProcess ) )
  368. return;
  369. ProcessInfo_t &info = m_Processes[ hProcess ];
  370. if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE )
  371. {
  372. WaitForSingleObject( info.m_hProcess, INFINITE );
  373. }
  374. else
  375. {
  376. // NOTE: The called process can block during writes to stderr + stdout
  377. // if the pipe buffer is empty. Therefore, waiting INFINITE is not
  378. // possible here. We must queue up messages received to allow the
  379. // process to continue
  380. while ( WaitForSingleObject( info.m_hProcess, 100 ) == WAIT_TIMEOUT )
  381. {
  382. int nLen = GetActualProcessOutputSize( hProcess );
  383. if ( nLen > 0 )
  384. {
  385. int nPut = info.m_ProcessOutput.TellPut();
  386. info.m_ProcessOutput.EnsureCapacity( nPut + nLen );
  387. int nBytesRead = GetActualProcessOutput( hProcess, (char*)info.m_ProcessOutput.PeekPut(), nLen );
  388. info.m_ProcessOutput.SeekPut( CUtlBuffer::SEEK_HEAD, nPut + nBytesRead );
  389. }
  390. }
  391. }
  392. m_hCurrentProcess = PROCESS_HANDLE_INVALID;
  393. }