Source code of Windows XP (NT5)
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.

1111 lines
35 KiB

  1. // --------------------------------------------------------------------------
  2. // Module Name: ExternalProcess.cpp
  3. //
  4. // Copyright (c) 1999-2000, Microsoft Corporation
  5. //
  6. // Class to handle premature termination of external processes or signaling
  7. // of termination of an external process.
  8. //
  9. // History: 1999-09-20 vtan created
  10. // 2000-02-01 vtan moved from Neptune to Whistler
  11. // 2001-02-21 vtan add PRERELEASE to DBG condition
  12. // --------------------------------------------------------------------------
  13. #include "StandardHeader.h"
  14. #include "ExternalProcess.h"
  15. #include "RegistryResources.h"
  16. #include "StatusCode.h"
  17. #include "Thread.h"
  18. #include "TokenGroups.h"
  19. #if (defined(DBG) || defined(PRERELEASE))
  20. static const TCHAR kNTSD[] = TEXT("ntsd");
  21. #endif /* (defined(DBG) || defined(PRERELEASE)) */
  22. // --------------------------------------------------------------------------
  23. // CJobCompletionWatcher
  24. //
  25. // Purpose: This is a private class (declared only by name in the header
  26. // file which implements the watcher thread) for the IO
  27. // completion port related to the job object for the external
  28. // process.
  29. //
  30. // History: 1999-10-07 vtan created
  31. // --------------------------------------------------------------------------
  32. class CJobCompletionWatcher : public CThread
  33. {
  34. private:
  35. CJobCompletionWatcher (void);
  36. CJobCompletionWatcher (const CJobCompletionWatcher& copyObject);
  37. const CJobCompletionWatcher& operator = (const CJobCompletionWatcher& assignObject);
  38. public:
  39. CJobCompletionWatcher (CExternalProcess* pExternalProcess, CJob& job, HANDLE hEvent);
  40. ~CJobCompletionWatcher (void);
  41. void ForceExit (void);
  42. protected:
  43. virtual DWORD Entry (void);
  44. virtual void Exit (void);
  45. private:
  46. CExternalProcess *_pExternalProcess;
  47. HANDLE _hEvent;
  48. HANDLE _hPortJobCompletion;
  49. bool _fExitLoop;
  50. };
  51. // --------------------------------------------------------------------------
  52. // CJobCompletionWatcher::CJobCompletionWatcher
  53. //
  54. // Arguments: pExternalProcess = CExternalProcess owner of this object.
  55. // job = CJob containing the job object.
  56. //
  57. // Returns: <none>
  58. //
  59. // Purpose: Constructs the CJobCompletionWatcher object. Creates the IO
  60. // completion port and assigns the port into the job object.
  61. //
  62. // History: 1999-10-07 vtan created
  63. // --------------------------------------------------------------------------
  64. CJobCompletionWatcher::CJobCompletionWatcher (CExternalProcess *pExternalProcess, CJob& job, HANDLE hEvent) :
  65. CThread(),
  66. _pExternalProcess(pExternalProcess),
  67. _hEvent(hEvent),
  68. _hPortJobCompletion(CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 1)),
  69. _fExitLoop(false)
  70. {
  71. pExternalProcess->AddRef();
  72. if (_hPortJobCompletion != NULL)
  73. {
  74. if (!NT_SUCCESS(job.SetCompletionPort(_hPortJobCompletion)))
  75. {
  76. ReleaseHandle(_hPortJobCompletion);
  77. }
  78. }
  79. Resume();
  80. }
  81. // --------------------------------------------------------------------------
  82. // CJobCompletionWatcher::~CJobCompletionWatcher
  83. //
  84. // Arguments: <none>
  85. //
  86. // Returns: <none>
  87. //
  88. // Purpose: Release the IO completion port used.
  89. //
  90. // History: 1999-10-07 vtan created
  91. // --------------------------------------------------------------------------
  92. CJobCompletionWatcher::~CJobCompletionWatcher (void)
  93. {
  94. ReleaseHandle(_hPortJobCompletion);
  95. }
  96. // --------------------------------------------------------------------------
  97. // CJobCompletionWatcher::ForceExit
  98. //
  99. // Arguments: <none>
  100. //
  101. // Returns: <none>
  102. //
  103. // Purpose: Sets the internal member variable telling the watcher loop
  104. // to exit. This allows the context to be invalidated while the
  105. // thread is still active. When detected the thread will exit.
  106. //
  107. // History: 1999-10-07 vtan created
  108. // --------------------------------------------------------------------------
  109. void CJobCompletionWatcher::ForceExit (void)
  110. {
  111. _fExitLoop = true;
  112. if (_pExternalProcess != NULL)
  113. {
  114. _pExternalProcess->Release();
  115. _pExternalProcess = NULL;
  116. }
  117. TBOOL(PostQueuedCompletionStatus(_hPortJobCompletion,
  118. 0,
  119. NULL,
  120. NULL));
  121. }
  122. // --------------------------------------------------------------------------
  123. // CJobCompletionWatcher::Entry
  124. //
  125. // Arguments: <none>
  126. //
  127. // Returns: DWORD
  128. //
  129. // Purpose: Continually poll the IO completion port waiting for process
  130. // exit messages. There are other messages that are ignored.
  131. // When the process has exited call the CExternalProcess which
  132. // allows it to make a decision and/or restart the external
  133. // process which will cause us to wait on that process.
  134. //
  135. // History: 1999-10-07 vtan created
  136. // --------------------------------------------------------------------------
  137. DWORD CJobCompletionWatcher::Entry (void)
  138. {
  139. // Must have an IO completion port to work with.
  140. if (_hPortJobCompletion != NULL)
  141. {
  142. DWORD dwCompletionCode;
  143. ULONG_PTR pCompletionKey;
  144. LPOVERLAPPED pOverlapped;
  145. do
  146. {
  147. if (_hEvent != NULL)
  148. {
  149. TBOOL(SetEvent(_hEvent));
  150. _hEvent = NULL;
  151. }
  152. // Get the completion status on the IO waiting forever.
  153. // Exit the loop if an error condition occurred.
  154. if ((GetQueuedCompletionStatus(_hPortJobCompletion,
  155. &dwCompletionCode,
  156. &pCompletionKey,
  157. &pOverlapped,
  158. INFINITE) != FALSE) &&
  159. !_fExitLoop)
  160. {
  161. switch (dwCompletionCode)
  162. {
  163. case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT:
  164. DISPLAYMSG("JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT\r\n");
  165. break;
  166. case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
  167. _fExitLoop = _pExternalProcess->HandleNoProcess();
  168. break;
  169. case JOB_OBJECT_MSG_NEW_PROCESS:
  170. _pExternalProcess->HandleNewProcess(PtrToUlong(pOverlapped));
  171. break;
  172. case JOB_OBJECT_MSG_EXIT_PROCESS:
  173. case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS:
  174. _pExternalProcess->HandleTermination(PtrToUlong(pOverlapped));
  175. break;
  176. default:
  177. break;
  178. }
  179. }
  180. else
  181. {
  182. _fExitLoop = true;
  183. }
  184. } while (!_fExitLoop);
  185. }
  186. return(0);
  187. }
  188. // --------------------------------------------------------------------------
  189. // CJobCompletionWatcher::Exit
  190. //
  191. // Arguments: <none>
  192. //
  193. // Returns: <none>
  194. //
  195. // Purpose: Release the CExternalProcess given in the constructor so that
  196. // the object can actually be released (reference count drops to
  197. // zero).
  198. //
  199. // History: 2000-05-01 vtan created
  200. // --------------------------------------------------------------------------
  201. void CJobCompletionWatcher::Exit (void)
  202. {
  203. if (_pExternalProcess != NULL)
  204. {
  205. _pExternalProcess->Release();
  206. _pExternalProcess = NULL;
  207. }
  208. CThread::Exit();
  209. }
  210. // --------------------------------------------------------------------------
  211. // IExternalProcess::Start
  212. //
  213. // Arguments: pszCommandLine = Command line to process.
  214. // dwCreateFlags = Flags when creating process.
  215. // startupInfo = STARTUPINFO struct.
  216. // processInformation = PROCESS_INFORMATION struct.
  217. //
  218. // Returns: NTSTATUS
  219. //
  220. // Purpose: This function is the default implementation of
  221. // IExternalProcess::Start which starts the process in the SYSTEM
  222. // context of a restricted user.
  223. //
  224. // History: 1999-09-20 vtan created
  225. // --------------------------------------------------------------------------
  226. NTSTATUS IExternalProcess::Start (const TCHAR *pszCommandLine,
  227. DWORD dwCreateFlags,
  228. const STARTUPINFO& startupInfo,
  229. PROCESS_INFORMATION& processInformation)
  230. {
  231. NTSTATUS status;
  232. HANDLE hTokenProcess;
  233. TCHAR szCommandLine[MAX_PATH * 2];
  234. // A user token is not allowed for this function. This function ALWAYS
  235. // starts the process as a restricted SYSTEM context process. To start
  236. // in a user context override this implementation with your own (or
  237. // impersonate the user before instantiating CExternalProcess).
  238. lstrcpyn(szCommandLine, pszCommandLine, ARRAYSIZE(szCommandLine));
  239. if (OpenProcessToken(GetCurrentProcess(), TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY, &hTokenProcess) != FALSE)
  240. {
  241. HANDLE hTokenRestricted;
  242. status = RemoveTokenSIDsAndPrivileges(hTokenProcess, hTokenRestricted);
  243. if (NT_SUCCESS(status))
  244. {
  245. TCHAR szCommandLine[MAX_PATH];
  246. AllowSetForegroundWindow(ASFW_ANY);
  247. (TCHAR*)lstrcpyn(szCommandLine, pszCommandLine, ARRAYSIZE(szCommandLine));
  248. if (dwCreateFlags == 0)
  249. {
  250. dwCreateFlags = NORMAL_PRIORITY_CLASS;
  251. }
  252. if (CreateProcessAsUser(hTokenRestricted,
  253. NULL,
  254. szCommandLine,
  255. NULL,
  256. NULL,
  257. FALSE,
  258. dwCreateFlags,
  259. NULL,
  260. NULL,
  261. const_cast<STARTUPINFO*>(&startupInfo),
  262. &processInformation) != FALSE)
  263. {
  264. status = STATUS_SUCCESS;
  265. }
  266. else
  267. {
  268. status = CStatusCode::StatusCodeOfLastError();
  269. }
  270. ReleaseHandle(hTokenRestricted);
  271. }
  272. ReleaseHandle(hTokenProcess);
  273. }
  274. else
  275. {
  276. status = CStatusCode::StatusCodeOfLastError();
  277. }
  278. return(status);
  279. }
  280. // --------------------------------------------------------------------------
  281. // IExternalProcess::AllowTermination
  282. //
  283. // Arguments: dwExitCode = Exit code of process.
  284. //
  285. // Returns: bool
  286. //
  287. // Purpose: This function returns whether external process termination is
  288. // allowed.
  289. //
  290. // History: 2000-05-01 vtan created
  291. // --------------------------------------------------------------------------
  292. bool IExternalProcess::AllowTermination (DWORD dwExitCode)
  293. {
  294. UNREFERENCED_PARAMETER(dwExitCode);
  295. return(true);
  296. }
  297. // --------------------------------------------------------------------------
  298. // IExternalProcess::SignalTermination
  299. //
  300. // Arguments: <none>
  301. //
  302. // Returns: NTSTATUS
  303. //
  304. // Purpose: This function is invoked by the external process handler
  305. // when the external process terminates normally.
  306. //
  307. // History: 1999-09-21 vtan created
  308. // --------------------------------------------------------------------------
  309. NTSTATUS IExternalProcess::SignalTermination (void)
  310. {
  311. return(STATUS_SUCCESS);
  312. }
  313. // --------------------------------------------------------------------------
  314. // IExternalProcess::SignalAbnormalTermination
  315. //
  316. // Arguments: <none>
  317. //
  318. // Returns: NTSTATUS
  319. //
  320. // Purpose: This function is invoked by the external process handler
  321. // when the external process terminates and cannot be restarted.
  322. // This indicates a serious condition from which this function
  323. // can attempt to recover.
  324. //
  325. // History: 1999-09-21 vtan created
  326. // --------------------------------------------------------------------------
  327. NTSTATUS IExternalProcess::SignalAbnormalTermination (void)
  328. {
  329. return(STATUS_SUCCESS);
  330. }
  331. // --------------------------------------------------------------------------
  332. // IExternalProcess::SignalRestart
  333. //
  334. // Arguments: <none>
  335. //
  336. // Returns: NTSTATUS
  337. //
  338. // Purpose: Signals restart of the external process. This allows a derived
  339. // implementation to do something when this happens.
  340. //
  341. // History: 2001-01-09 vtan created
  342. // --------------------------------------------------------------------------
  343. NTSTATUS IExternalProcess::SignalRestart (void)
  344. {
  345. return(STATUS_SUCCESS);
  346. }
  347. // --------------------------------------------------------------------------
  348. // IExternalProcess::RemoveTokenSIDsAndPrivileges
  349. //
  350. // Arguments: hTokenIn = Token to remove SIDs and privileges from.
  351. // hTokenOut = Generated token returned.
  352. //
  353. // Returns: NTSTATUS
  354. //
  355. // Purpose: Remove designated SIDs and privileges from the given token.
  356. // Currently this removes the local administrators SID and all
  357. // all privileges except SE_RESTORE_NAME. On checked builds
  358. // SE_DEBUG_NAME is also not removed.
  359. //
  360. // History: 2000-06-21 vtan created
  361. // --------------------------------------------------------------------------
  362. NTSTATUS IExternalProcess::RemoveTokenSIDsAndPrivileges (HANDLE hTokenIn, HANDLE& hTokenOut)
  363. {
  364. NTSTATUS status;
  365. DWORD dwFlags = 0, dwReturnLength;
  366. TOKEN_PRIVILEGES *pTokenPrivileges;
  367. CTokenGroups tokenGroup;
  368. hTokenOut = NULL;
  369. TSTATUS(tokenGroup.CreateAdministratorGroup());
  370. (BOOL)GetTokenInformation(hTokenIn, TokenPrivileges, NULL, 0, &dwReturnLength);
  371. pTokenPrivileges = static_cast<TOKEN_PRIVILEGES*>(LocalAlloc(LMEM_FIXED, dwReturnLength));
  372. if (pTokenPrivileges != NULL)
  373. {
  374. if (GetTokenInformation(hTokenIn, TokenPrivileges, pTokenPrivileges, dwReturnLength, &dwReturnLength) != FALSE)
  375. {
  376. bool fKeepPrivilege;
  377. ULONG ulCount;
  378. LUID luidRestorePrivilege;
  379. LUID luidChangeNotifyPrivilege;
  380. #if (defined(DBG) || defined(PRERELEASE))
  381. LUID luidDebugPrivilege;
  382. #endif /* (defined(DBG) || defined(PRERELEASE)) */
  383. luidRestorePrivilege.LowPart = SE_RESTORE_PRIVILEGE;
  384. luidRestorePrivilege.HighPart = 0;
  385. luidChangeNotifyPrivilege.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE;
  386. luidChangeNotifyPrivilege.HighPart = 0;
  387. #if (defined(DBG) || defined(PRERELEASE))
  388. luidDebugPrivilege.LowPart = SE_DEBUG_PRIVILEGE;
  389. luidDebugPrivilege.HighPart = 0;
  390. #endif /* (defined(DBG) || defined(PRERELEASE)) */
  391. // Privileges kept are actually removed from the privilege array.
  392. // This is because NtFilterToken will REMOVE the privileges passed
  393. // in the array. Keep SE_DEBUG_NAME on checked builds.
  394. ulCount = 0;
  395. while (ulCount < pTokenPrivileges->PrivilegeCount)
  396. {
  397. fKeepPrivilege = ((RtlEqualLuid(&pTokenPrivileges->Privileges[ulCount].Luid, &luidRestorePrivilege) != FALSE) ||
  398. (RtlEqualLuid(&pTokenPrivileges->Privileges[ulCount].Luid, &luidChangeNotifyPrivilege) != FALSE));
  399. #if (defined(DBG) || defined(PRERELEASE))
  400. fKeepPrivilege = fKeepPrivilege || (RtlEqualLuid(&pTokenPrivileges->Privileges[ulCount].Luid, &luidDebugPrivilege) != FALSE);
  401. #endif /* (defined(DBG) || defined(PRERELEASE)) */
  402. if (fKeepPrivilege)
  403. {
  404. MoveMemory(&pTokenPrivileges->Privileges[ulCount], &pTokenPrivileges->Privileges[ulCount + 1], pTokenPrivileges->PrivilegeCount - ulCount - 1);
  405. --pTokenPrivileges->PrivilegeCount;
  406. }
  407. else
  408. {
  409. ++ulCount;
  410. }
  411. }
  412. }
  413. else
  414. {
  415. ReleaseMemory(pTokenPrivileges);
  416. }
  417. }
  418. if (pTokenPrivileges == NULL)
  419. {
  420. dwFlags = DISABLE_MAX_PRIVILEGE;
  421. }
  422. status = NtFilterToken(hTokenIn,
  423. dwFlags,
  424. const_cast<TOKEN_GROUPS*>(tokenGroup.Get()),
  425. pTokenPrivileges,
  426. NULL,
  427. &hTokenOut);
  428. ReleaseMemory(pTokenPrivileges);
  429. return(status);
  430. }
  431. // --------------------------------------------------------------------------
  432. // CExternalProcess::CExternalProcess
  433. //
  434. // Arguments: <none>
  435. //
  436. // Returns: <none>
  437. //
  438. // Purpose: Constructor for CExternalProcess.
  439. //
  440. // History: 1999-09-14 vtan created
  441. // --------------------------------------------------------------------------
  442. CExternalProcess::CExternalProcess (void) :
  443. _hProcess(NULL),
  444. _dwProcessID(0),
  445. _dwProcessExitCode(STILL_ACTIVE),
  446. _dwCreateFlags(NORMAL_PRIORITY_CLASS),
  447. _dwStartFlags(STARTF_USESHOWWINDOW),
  448. _wShowFlags(SW_SHOW),
  449. _iRestartCount(0),
  450. _pIExternalProcess(NULL),
  451. _jobCompletionWatcher(NULL)
  452. {
  453. _szCommandLine[0] = _szParameter[0] = TEXT('\0');
  454. // Configure our job object. Only allow a single process to execute
  455. // for this job. Restriction of UI is done by subclassing. The UIHost
  456. // does not restrict UI but the screen saver does.
  457. TSTATUS(_job.SetActiveProcessLimit(1));
  458. }
  459. // --------------------------------------------------------------------------
  460. // CExternalProcess::~CExternalProcess
  461. //
  462. // Arguments: <none>
  463. //
  464. // Returns: <none>
  465. //
  466. // Purpose: Destructor for CExternalProcess.
  467. //
  468. // History: 1999-09-14 vtan created
  469. // --------------------------------------------------------------------------
  470. CExternalProcess::~CExternalProcess (void)
  471. {
  472. // Force the watcher thread to exit regardless of any job object
  473. // messages that come in. This will prevent it using its reference
  474. // to CExternalProcess which is now being destructed. It will also
  475. // prevent the external process from being started up again now
  476. // that we know the external process should go away.
  477. if (_jobCompletionWatcher != NULL)
  478. {
  479. _jobCompletionWatcher->ForceExit();
  480. }
  481. // If the process is still alive here then give it 100 milliseconds to
  482. // terminate before forcibly terminating it.
  483. if (_hProcess != NULL)
  484. {
  485. DWORD dwExitCode;
  486. if ((GetExitCodeProcess(_hProcess, &dwExitCode) == FALSE) || (STILL_ACTIVE == dwExitCode))
  487. {
  488. if (WaitForSingleObject(_hProcess, 100) == WAIT_TIMEOUT)
  489. {
  490. NTSTATUS status;
  491. status = Terminate();
  492. #if (defined(DBG) || defined(PRERELEASE))
  493. if (ERROR_ACCESS_DENIED == GetLastError())
  494. {
  495. status = NtCurrentTeb()->LastStatusValue;
  496. if (STATUS_PROCESS_IS_TERMINATING == status)
  497. {
  498. status = STATUS_SUCCESS;
  499. }
  500. }
  501. TSTATUS(status);
  502. #endif /* (defined(DBG) || defined(PRERELEASE)) */
  503. }
  504. }
  505. }
  506. ReleaseHandle(_hProcess);
  507. _dwProcessID = 0;
  508. if (_jobCompletionWatcher != NULL)
  509. {
  510. _jobCompletionWatcher->Release();
  511. _jobCompletionWatcher = NULL;
  512. }
  513. if (_pIExternalProcess != NULL)
  514. {
  515. _pIExternalProcess->Release();
  516. _pIExternalProcess = NULL;
  517. }
  518. }
  519. // --------------------------------------------------------------------------
  520. // CExternalProcess::SetInterface
  521. //
  522. // Arguments: pIExternalProcess = IExternalProcess interface pointer.
  523. //
  524. // Returns: <none>
  525. //
  526. // Purpose: Store the IExternalProcess interface pointer.
  527. //
  528. // History: 1999-09-14 vtan created
  529. // --------------------------------------------------------------------------
  530. void CExternalProcess::SetInterface (IExternalProcess *pIExternalProcess)
  531. {
  532. if (_pIExternalProcess != NULL)
  533. {
  534. _pIExternalProcess->Release();
  535. _pIExternalProcess = NULL;
  536. }
  537. if (pIExternalProcess != NULL)
  538. {
  539. pIExternalProcess->AddRef();
  540. }
  541. _pIExternalProcess = pIExternalProcess;
  542. }
  543. // --------------------------------------------------------------------------
  544. // CExternalProcess::GetInterface
  545. //
  546. // Arguments: <none>
  547. //
  548. // Returns: IExternalProcess*
  549. //
  550. // Purpose: Returns the IExternalProcess interface pointer. Not that the
  551. // caller gets a reference.
  552. //
  553. // History: 2001-01-09 vtan created
  554. // --------------------------------------------------------------------------
  555. IExternalProcess* CExternalProcess::GetInterface (void) const
  556. {
  557. IExternalProcess *pIResult;
  558. if (_pIExternalProcess != NULL)
  559. {
  560. pIResult = _pIExternalProcess;
  561. pIResult->AddRef();
  562. }
  563. else
  564. {
  565. pIResult = NULL;
  566. }
  567. return(pIResult);
  568. }
  569. // --------------------------------------------------------------------------
  570. // CExternalProcess::SetParameter
  571. //
  572. // Arguments: pszParameter = String of parameter to append.
  573. //
  574. // Returns: <none>
  575. //
  576. // Purpose: Sets the parameter to append to each invokation of the
  577. // external process.
  578. //
  579. // History: 1999-09-20 vtan created
  580. // --------------------------------------------------------------------------
  581. void CExternalProcess::SetParameter (const TCHAR* pszParameter)
  582. {
  583. if (pszParameter != NULL)
  584. {
  585. lstrcpyn(_szParameter, pszParameter, ARRAYSIZE(_szParameter));
  586. }
  587. else
  588. {
  589. _szParameter[0] = TEXT('\0');
  590. }
  591. }
  592. // --------------------------------------------------------------------------
  593. // CExternalProcess::Start
  594. //
  595. // Arguments: <none>
  596. //
  597. // Returns: NTSTATUS
  598. //
  599. // Purpose: If the external process is specified start it. If it starts
  600. // successfully then register a wait callback in case it
  601. // terminates unexpectedly so we can restart the process. This
  602. // ensures that the external process is always available if
  603. // required. If the external process cannot be started return
  604. // with an error.
  605. //
  606. // History: 1999-09-20 vtan created
  607. // --------------------------------------------------------------------------
  608. NTSTATUS CExternalProcess::Start (void)
  609. {
  610. NTSTATUS status;
  611. ASSERTMSG(_pIExternalProcess != NULL, "Must call CExternalProcess::SetInterface before using CExternalProcess::Start");
  612. if (_szCommandLine[0] != TEXT('\0'))
  613. {
  614. STARTUPINFO startupInfo;
  615. PROCESS_INFORMATION processInformation;
  616. TCHAR szCommandLine[MAX_PATH * 2];
  617. lstrcpy(szCommandLine, _szCommandLine);
  618. lstrcat(szCommandLine, _szParameter);
  619. // Start the process on Winlogon's desktop.
  620. ZeroMemory(&startupInfo, sizeof(startupInfo));
  621. startupInfo.cb = sizeof(startupInfo);
  622. startupInfo.lpDesktop = TEXT("WinSta0\\Winlogon");
  623. startupInfo.dwFlags = _dwStartFlags;
  624. startupInfo.wShowWindow = _wShowFlags;
  625. status = _pIExternalProcess->Start(szCommandLine, _dwCreateFlags | CREATE_SUSPENDED, startupInfo, processInformation);
  626. if (NT_SUCCESS(status))
  627. {
  628. // The process is created suspended so that it can
  629. // assigned to the job object for this object.
  630. TSTATUS(_job.AddProcess(processInformation.hProcess));
  631. // The process is still suspended so resume the
  632. // primary thread.
  633. if (processInformation.hThread != NULL)
  634. {
  635. (DWORD)ResumeThread(processInformation.hThread);
  636. TBOOL(CloseHandle(processInformation.hThread));
  637. }
  638. // Keep the handle to the process so that we can kill
  639. // it when our object goes out of scope.
  640. _hProcess = processInformation.hProcess;
  641. _dwProcessID = processInformation.dwProcessId;
  642. // Don't reallocate another CJobCompletionWatcher if
  643. // one already exists. Just ignore this case.
  644. if (_jobCompletionWatcher == NULL)
  645. {
  646. HANDLE hEvent;
  647. hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
  648. _jobCompletionWatcher = new CJobCompletionWatcher(this, _job, hEvent);
  649. if ((_jobCompletionWatcher != NULL) && _jobCompletionWatcher->IsCreated() && (hEvent != NULL))
  650. {
  651. (DWORD)WaitForSingleObject(hEvent, INFINITE);
  652. }
  653. if (hEvent != NULL)
  654. {
  655. TBOOL(CloseHandle(hEvent));
  656. }
  657. }
  658. }
  659. }
  660. else
  661. {
  662. DISPLAYMSG("No external process to start in CExternalProcess::Start");
  663. status = STATUS_UNSUCCESSFUL;
  664. }
  665. return(status);
  666. }
  667. // --------------------------------------------------------------------------
  668. // CExternalProcess::End
  669. //
  670. // Arguments: <none>
  671. //
  672. // Returns: NTSTATUS
  673. //
  674. // Purpose: End the process. Ends the watcher thread as well to release
  675. // all held references.
  676. //
  677. // History: 2000-05-05 vtan created
  678. // --------------------------------------------------------------------------
  679. NTSTATUS CExternalProcess::End (void)
  680. {
  681. if (_jobCompletionWatcher != NULL)
  682. {
  683. _jobCompletionWatcher->ForceExit();
  684. }
  685. return(STATUS_SUCCESS);
  686. }
  687. // --------------------------------------------------------------------------
  688. // CExternalProcess::Terminate
  689. //
  690. // Arguments: <none>
  691. //
  692. // Returns: NTSTATUS
  693. //
  694. // Purpose: Terminate the process unconditionally.
  695. //
  696. // History: 1999-10-14 vtan created
  697. // --------------------------------------------------------------------------
  698. NTSTATUS CExternalProcess::Terminate (void)
  699. {
  700. NTSTATUS status;
  701. if (TerminateProcess(_hProcess, 0) != FALSE)
  702. {
  703. status = STATUS_SUCCESS;
  704. }
  705. else
  706. {
  707. status = CStatusCode::StatusCodeOfLastError();
  708. }
  709. return(status);
  710. }
  711. // --------------------------------------------------------------------------
  712. // CExternalProcess::HandleNoProcess
  713. //
  714. // Arguments: <none>
  715. //
  716. // Returns: bool
  717. //
  718. // Purpose: This function restarts the external process if required. It
  719. // uses the IExternalProcess to communicate with the external
  720. // process controller to make the decisions. This function is
  721. // only called when the active process count drops to zero. If
  722. // the external process is being debugged then this will happen
  723. // when the debugger quits as well.
  724. //
  725. // History: 1999-11-30 vtan created
  726. // --------------------------------------------------------------------------
  727. bool CExternalProcess::HandleNoProcess (void)
  728. {
  729. bool fResult;
  730. fResult = true;
  731. NotifyNoProcess();
  732. if (_pIExternalProcess != NULL)
  733. {
  734. if (_pIExternalProcess->AllowTermination(_dwProcessExitCode))
  735. {
  736. TSTATUS(_pIExternalProcess->SignalTermination());
  737. }
  738. else
  739. {
  740. // Only try to start the external process 10 times (restart
  741. // it 9 times). Give up and signal abnormal termination if exceeded.
  742. if ((++_iRestartCount <= 9) && NT_SUCCESS(Start()))
  743. {
  744. TSTATUS(_pIExternalProcess->SignalRestart());
  745. fResult = false;
  746. }
  747. else
  748. {
  749. TSTATUS(_pIExternalProcess->SignalAbnormalTermination());
  750. }
  751. }
  752. }
  753. return(fResult);
  754. }
  755. // --------------------------------------------------------------------------
  756. // CExternalProcess::HandleNewProcess
  757. //
  758. // Arguments: dwProcessID = Process ID of new process.
  759. //
  760. // Returns: <none>
  761. //
  762. // Purpose: This function is called by the Job object watcher when a new
  763. // process is added to the job. Normally this will fail because
  764. // of the quota limit. However, when debugging is enabled this
  765. // will be allowed.
  766. //
  767. // History: 1999-10-27 vtan created
  768. // --------------------------------------------------------------------------
  769. void CExternalProcess::HandleNewProcess (DWORD dwProcessID)
  770. {
  771. if (_dwProcessID != dwProcessID)
  772. {
  773. ReleaseHandle(_hProcess);
  774. _hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
  775. _dwProcessID = dwProcessID;
  776. }
  777. }
  778. // --------------------------------------------------------------------------
  779. // CExternalProcess::HandleTermination
  780. //
  781. // Arguments: <none>
  782. //
  783. // Returns: <none>
  784. //
  785. // Purpose: If the external process terminates unexpectedly this function
  786. // will be invoked by the wait callback when the process HANDLE
  787. // becomes signaled. It's acceptable for the process to terminate
  788. // if there is a dialog result so ignore this case. Otherwise
  789. // close the handle to the process that died and wait for the
  790. // job object signal that zero processes are actually running.
  791. // That signal will restart the process.
  792. //
  793. // History: 1999-08-24 vtan created
  794. // 1999-09-14 vtan factored
  795. // --------------------------------------------------------------------------
  796. void CExternalProcess::HandleTermination (DWORD dwProcessID)
  797. {
  798. // Make sure the process that is exiting is the process we are tracking.
  799. // In every case other than debugging this will be true because the job
  800. // object limits the active process count. In the case of debugging make
  801. // sure we don't restart two processes because ntsd quit as well as the
  802. // external process itself!
  803. if (_dwProcessID == dwProcessID)
  804. {
  805. if (GetExitCodeProcess(_hProcess, &_dwProcessExitCode) == FALSE)
  806. {
  807. _dwProcessExitCode = STILL_ACTIVE;
  808. }
  809. ReleaseHandle(_hProcess);
  810. _dwProcessID = 0;
  811. }
  812. }
  813. // --------------------------------------------------------------------------
  814. // CExternalProcess::IsStarted
  815. //
  816. // Arguments: <none>
  817. //
  818. // Returns: bool
  819. //
  820. // Purpose: Returns whether there is an external process that has been
  821. // started.
  822. //
  823. // History: 1999-09-14 vtan created
  824. // --------------------------------------------------------------------------
  825. bool CExternalProcess::IsStarted (void) const
  826. {
  827. return(_hProcess != NULL);
  828. }
  829. // --------------------------------------------------------------------------
  830. // CExternalProcess::NotifyNoProcess
  831. //
  832. // Arguments: <none>
  833. //
  834. // Returns: <none>
  835. //
  836. // Purpose: Derivable function for notification of process termination.
  837. //
  838. // History: 2001-01-09 vtan created
  839. // --------------------------------------------------------------------------
  840. void CExternalProcess::NotifyNoProcess (void)
  841. {
  842. }
  843. // --------------------------------------------------------------------------
  844. // CExternalProcess::AdjustForDebugging
  845. //
  846. // Arguments: <none>
  847. //
  848. // Returns: <none>
  849. //
  850. // Purpose: Adjusts the job object to allow debugging of the external
  851. // process.
  852. //
  853. // History: 1999-10-22 vtan created
  854. // --------------------------------------------------------------------------
  855. void CExternalProcess::AdjustForDebugging (void)
  856. {
  857. #if (defined(DBG) || defined(PRERELEASE))
  858. // If it looks like the external process is being debugged
  859. // then lift the process restriction to allow it to be debugged.
  860. if (IsBeingDebugged())
  861. {
  862. _job.SetActiveProcessLimit(0);
  863. }
  864. #endif /* (defined(DBG) || defined(PRERELEASE)) */
  865. }
  866. #if (defined(DBG) || defined(PRERELEASE))
  867. // --------------------------------------------------------------------------
  868. // CExternalProcess::IsBeingDebugged
  869. //
  870. // Arguments: <none>
  871. //
  872. // Returns: bool
  873. //
  874. // Purpose: Returns whether the external process will end up being started
  875. // under a debugger.
  876. //
  877. // History: 2000-10-04 vtan created
  878. // --------------------------------------------------------------------------
  879. bool CExternalProcess::IsBeingDebugged (void) const
  880. {
  881. return(IsPrefixedWithNTSD() || IsImageFileExecutionDebugging());
  882. }
  883. // --------------------------------------------------------------------------
  884. // CExternalProcess::IsPrefixedWithNTSD
  885. //
  886. // Arguments: <none>
  887. //
  888. // Returns: bool
  889. //
  890. // Purpose: Returns whether the command line starts with "ntsd".
  891. //
  892. // History: 1999-10-25 vtan created
  893. // --------------------------------------------------------------------------
  894. bool CExternalProcess::IsPrefixedWithNTSD (void) const
  895. {
  896. // Is the command line prefixed with "ntsd"?
  897. return(CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, _szCommandLine, 4, kNTSD, 4) == CSTR_EQUAL);
  898. }
  899. // --------------------------------------------------------------------------
  900. // CExternalProcess::IsImageFileExecutionDebugging
  901. //
  902. // Arguments: <none>
  903. //
  904. // Returns: bool
  905. //
  906. // Purpose: Returns whether the system is set to debug this particular
  907. // executable file via "Image File Execution Options".
  908. //
  909. // History: 1999-10-25 vtan created
  910. // --------------------------------------------------------------------------
  911. bool CExternalProcess::IsImageFileExecutionDebugging (void) const
  912. {
  913. bool fResult;
  914. TCHAR *pC, *pszFilePart;
  915. TCHAR szCommandLine[MAX_PATH], szExecutablePath[MAX_PATH];
  916. fResult = false;
  917. // Make a copy of the command line. Find the first space character
  918. // or the end of the string and NULL terminate it. This does NOT
  919. // check for quotes!
  920. lstrcpy(szCommandLine, _szCommandLine);
  921. pC = szCommandLine;
  922. while ((*pC != TEXT(' ')) && (*pC != TEXT('\0')))
  923. {
  924. ++pC;
  925. }
  926. *pC++ = TEXT('\0');
  927. if (SearchPath(NULL, szCommandLine, TEXT(".exe"), ARRAYSIZE(szExecutablePath), szExecutablePath, &pszFilePart) != 0)
  928. {
  929. LONG errorCode;
  930. TCHAR szImageKey[MAX_PATH];
  931. CRegKey regKey;
  932. // Open the associated "Image File Execution Options" key.
  933. lstrcpy(szImageKey, TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\"));
  934. lstrcat(szImageKey, pszFilePart);
  935. errorCode = regKey.Open(HKEY_LOCAL_MACHINE, szImageKey, KEY_READ);
  936. if (ERROR_SUCCESS == errorCode)
  937. {
  938. // Read the "Debugger" value.
  939. errorCode = regKey.GetString(TEXT("Debugger"), szCommandLine, ARRAYSIZE(szCommandLine));
  940. if (ERROR_SUCCESS == errorCode)
  941. {
  942. // Look for "ntsd".
  943. fResult = (CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, szCommandLine, 4, kNTSD, 4) == CSTR_EQUAL);
  944. }
  945. }
  946. }
  947. return(fResult);
  948. }
  949. #endif /* (defined(DBG) || defined(PRERELEASE)) */