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.

531 lines
20 KiB

  1. // --------------------------------------------------------------------------
  2. // Module Name: GracefulTerminateApplication.cpp
  3. //
  4. // Copyright (c) 2000, Microsoft Corporation
  5. //
  6. // Class to manager terminating applications gracefully.
  7. //
  8. // History: 2000-10-27 vtan created
  9. // 2000-11-04 vtan split into separate file
  10. // --------------------------------------------------------------------------
  11. #ifdef _X86_
  12. #include "StandardHeader.h"
  13. #include "GracefulTerminateApplication.h"
  14. #include "KernelResources.h"
  15. #include "Thread.h"
  16. #include "WarningDialog.h"
  17. // --------------------------------------------------------------------------
  18. // CProgressDialog
  19. //
  20. // Purpose: A class to manage displaying a progress dialog on a separate
  21. // thread if a certain period of time elapses. This is so that
  22. // in case the process doesn't terminate in a period of time
  23. // a dialog indicating wait is shown so the user is not left
  24. // staring at a blank screen.
  25. //
  26. // History: 2000-11-04 vtan created
  27. // --------------------------------------------------------------------------
  28. class CProgressDialog : public CThread
  29. {
  30. private:
  31. CProgressDialog (void);
  32. public:
  33. CProgressDialog (CWarningDialog *pWarningDialog);
  34. virtual ~CProgressDialog (void);
  35. void SignalTerminated (void);
  36. protected:
  37. virtual DWORD Entry (void);
  38. private:
  39. CWarningDialog* _pWarningDialog;
  40. CEvent _event;
  41. };
  42. // --------------------------------------------------------------------------
  43. // CProgressDialog::CProgressDialog
  44. //
  45. // Arguments: <none>
  46. //
  47. // Returns: <none>
  48. //
  49. // Purpose: Constructor for CProgressDialog. Keeps a reference to the
  50. // given CWarningDialog.
  51. //
  52. // History: 2000-11-04 vtan created
  53. // --------------------------------------------------------------------------
  54. CProgressDialog::CProgressDialog (CWarningDialog *pWarningDialog) :
  55. _pWarningDialog(NULL),
  56. _event(NULL)
  57. {
  58. if (IsCreated())
  59. {
  60. pWarningDialog->AddRef();
  61. _pWarningDialog = pWarningDialog;
  62. Resume();
  63. }
  64. }
  65. // --------------------------------------------------------------------------
  66. // CProgressDialog::~CProgressDialog
  67. //
  68. // Arguments: <none>
  69. //
  70. // Returns: <none>
  71. //
  72. // Purpose: Releases the CWarningDialog reference.
  73. //
  74. // History: 2000-11-04 vtan created
  75. // --------------------------------------------------------------------------
  76. CProgressDialog::~CProgressDialog (void)
  77. {
  78. _pWarningDialog->Release();
  79. _pWarningDialog = NULL;
  80. }
  81. // --------------------------------------------------------------------------
  82. // CProgressDialog::SignalTerminated
  83. //
  84. // Arguments: <none>
  85. //
  86. // Returns: <none>
  87. //
  88. // Purpose: Signals the internal event that the process being watched is
  89. // termination. This is necessary because there is no handle to
  90. // the actual process to watch as it's kept on the server side
  91. // and not given to us the client. However, the result of the
  92. // termination is. Signaling this object will release the waiting
  93. // thread and cause it to exit.
  94. //
  95. // History: 2000-11-04 vtan created
  96. // --------------------------------------------------------------------------
  97. void CProgressDialog::SignalTerminated (void)
  98. {
  99. TSTATUS(_event.Set());
  100. }
  101. // --------------------------------------------------------------------------
  102. // CProgressDialog::Entry
  103. //
  104. // Arguments: <none>
  105. //
  106. // Returns: DWORD
  107. //
  108. // Purpose: Thread which waits for the internal event to be signaled. If
  109. // the event is signaled it will "cancel" the 3 second wait and
  110. // the thread will exit. Otherwise the wait times out and the
  111. // progress dialog is shown - waiting for termination.
  112. //
  113. // History: 2000-11-04 vtan created
  114. // --------------------------------------------------------------------------
  115. DWORD CProgressDialog::Entry (void)
  116. {
  117. DWORD dwWaitResult;
  118. // Wait for the event to be signaled or for it to timeout. If signaled
  119. // then the process is terminated and no progress is required. Otherwise
  120. // prepare to show progress while the process is being terminated.
  121. if (NT_SUCCESS(_event.Wait(2000, &dwWaitResult)) && (WAIT_TIMEOUT == dwWaitResult))
  122. {
  123. _pWarningDialog->ShowProgress(100, 7500);
  124. }
  125. return(0);
  126. }
  127. // --------------------------------------------------------------------------
  128. // CGracefulTerminateApplication::CGracefulTerminateApplication
  129. //
  130. // Arguments: <none>
  131. //
  132. // Returns: <none>
  133. //
  134. // Purpose: Constructor for CGracefulTerminateApplication.
  135. //
  136. // History: 2000-10-27 vtan created
  137. // --------------------------------------------------------------------------
  138. CGracefulTerminateApplication::CGracefulTerminateApplication (void) :
  139. _dwProcessID(0),
  140. _fFoundWindow(false)
  141. {
  142. }
  143. // --------------------------------------------------------------------------
  144. // CGracefulTerminateApplication::~CGracefulTerminateApplication
  145. //
  146. // Arguments: <none>
  147. //
  148. // Returns: <none>
  149. //
  150. // Purpose: Destructor for CGracefulTerminateApplication.
  151. //
  152. // History: 2000-10-27 vtan created
  153. // --------------------------------------------------------------------------
  154. CGracefulTerminateApplication::~CGracefulTerminateApplication (void)
  155. {
  156. }
  157. // --------------------------------------------------------------------------
  158. // CGracefulTerminateApplication::Terminate
  159. //
  160. // Arguments: dwProcessID = Process ID of process to terminate.
  161. //
  162. // Returns: <none>
  163. //
  164. // Purpose: Walk the window list for top level windows that correspond
  165. // to this process ID and are visible. Close them. The callback
  166. // handles the work and this function returns the result in the
  167. // process exit code which the server examines.
  168. //
  169. // History: 2000-10-27 vtan created
  170. // --------------------------------------------------------------------------
  171. void CGracefulTerminateApplication::Terminate (DWORD dwProcessID)
  172. {
  173. DWORD dwExitCode;
  174. _dwProcessID = dwProcessID;
  175. TBOOL(EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(this)));
  176. if (_fFoundWindow)
  177. {
  178. dwExitCode = WAIT_WINDOWS_FOUND;
  179. }
  180. else
  181. {
  182. dwExitCode = NO_WINDOWS_FOUND;
  183. }
  184. ExitProcess(dwExitCode);
  185. }
  186. // --------------------------------------------------------------------------
  187. // CGracefulTerminateApplication::Prompt
  188. //
  189. // Arguments: hInstance = HINSTANCE of this DLL.
  190. // hProcess = Inherited handle to parent process.
  191. //
  192. // Returns: <none>
  193. //
  194. // Purpose: Shows a prompt that handles termination of the parent of this
  195. // process. The parent is assumed to be a bad application type 1
  196. // that caused this stub to be executed via the
  197. // "rundll32 shsvcs.dll,FUSCompatibilityEntry prompt" command.
  198. // Because there can only be a single instance of the type 1
  199. // application running and the parent of this process hasn't
  200. // registered yet querying for this process by image name will
  201. // always find the correct process.
  202. //
  203. // History: 2000-11-03 vtan created
  204. // --------------------------------------------------------------------------
  205. void CGracefulTerminateApplication::Prompt (HINSTANCE hInstance, HANDLE hProcess)
  206. {
  207. bool fTerminated;
  208. ULONG ulReturnLength;
  209. PROCESS_BASIC_INFORMATION processBasicInformation;
  210. // Read the parent's image name from the RTL_USER_PROCESS_PARAMETERS.
  211. fTerminated = false;
  212. if (hProcess != NULL)
  213. {
  214. if (NT_SUCCESS(NtQueryInformationProcess(hProcess,
  215. ProcessBasicInformation,
  216. &processBasicInformation,
  217. sizeof(processBasicInformation),
  218. &ulReturnLength)))
  219. {
  220. SIZE_T dwNumberOfBytesRead;
  221. PEB peb;
  222. if (ReadProcessMemory(hProcess,
  223. processBasicInformation.PebBaseAddress,
  224. &peb,
  225. sizeof(peb),
  226. &dwNumberOfBytesRead) != FALSE)
  227. {
  228. RTL_USER_PROCESS_PARAMETERS processParameters;
  229. if (ReadProcessMemory(hProcess,
  230. peb.ProcessParameters,
  231. &processParameters,
  232. sizeof(processParameters),
  233. &dwNumberOfBytesRead) != FALSE)
  234. {
  235. WCHAR *pszImageName;
  236. pszImageName = static_cast<WCHAR*>(LocalAlloc(LMEM_FIXED, processParameters.ImagePathName.Length + (sizeof('\0') * sizeof(WCHAR))));
  237. if (pszImageName != NULL)
  238. {
  239. if (ReadProcessMemory(hProcess,
  240. processParameters.ImagePathName.Buffer,
  241. pszImageName,
  242. processParameters.ImagePathName.Length,
  243. &dwNumberOfBytesRead) != FALSE)
  244. {
  245. pszImageName[processParameters.ImagePathName.Length / sizeof(WCHAR)] = L'\0';
  246. // And show a prompt for this process.
  247. fTerminated = ShowPrompt(hInstance, pszImageName);
  248. }
  249. (HLOCAL)LocalFree(pszImageName);
  250. }
  251. }
  252. }
  253. }
  254. TBOOL(CloseHandle(hProcess));
  255. }
  256. ExitProcess(fTerminated);
  257. }
  258. // --------------------------------------------------------------------------
  259. // CGracefulTerminateApplication::ShowPrompt
  260. //
  261. // Arguments: hInstance = HINSTANCE of this DLL.
  262. // pszImagename = Image name of process to terminate.
  263. //
  264. // Returns: bool
  265. //
  266. // Purpose: Shows the appropriate prompt for termination of the first
  267. // instance of a BAM type 1 process. If the current user does
  268. // not have administrator privileges then a "STOP" dialog is
  269. // shown that the user must get the other user to close the
  270. // program. Otherwise the "PROMPT" dialog is shown which gives
  271. // the user the option to terminate the process.
  272. //
  273. // If termination is requested and the termatinion fails the
  274. // another warning dialog to that effect is shown.
  275. //
  276. // History: 2000-11-03 vtan created
  277. // --------------------------------------------------------------------------
  278. bool CGracefulTerminateApplication::ShowPrompt (HINSTANCE hInstance, const WCHAR *pszImageName)
  279. {
  280. bool fTerminated;
  281. ULONG ulConnectionInfoLength;
  282. HANDLE hPort;
  283. UNICODE_STRING portName;
  284. SECURITY_QUALITY_OF_SERVICE sqos;
  285. WCHAR szConnectionInfo[32];
  286. fTerminated = false;
  287. RtlInitUnicodeString(&portName, FUS_PORT_NAME);
  288. sqos.Length = sizeof(sqos);
  289. sqos.ImpersonationLevel = SecurityImpersonation;
  290. sqos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
  291. sqos.EffectiveOnly = TRUE;
  292. lstrcpyW(szConnectionInfo, FUS_CONNECTION_REQUEST);
  293. ulConnectionInfoLength = sizeof(szConnectionInfo);
  294. if (NT_SUCCESS(NtConnectPort(&hPort,
  295. &portName,
  296. &sqos,
  297. NULL,
  298. NULL,
  299. NULL,
  300. szConnectionInfo,
  301. &ulConnectionInfoLength)))
  302. {
  303. bool fCanTerminateFirstInstance;
  304. CWarningDialog *pWarningDialog;
  305. WCHAR szUser[256];
  306. // Get the user's privilege level for this operation. This API also
  307. // returns the current user for the BAM type 1 process.
  308. fCanTerminateFirstInstance = CanTerminateFirstInstance(hPort, pszImageName, szUser, ARRAYSIZE(szUser));
  309. pWarningDialog = new CWarningDialog(hInstance, NULL, pszImageName, szUser);
  310. if (pWarningDialog != NULL)
  311. {
  312. // Show the appropriate dialog based on the privilege level.
  313. if (pWarningDialog->ShowPrompt(fCanTerminateFirstInstance) == IDOK)
  314. {
  315. CProgressDialog *pProgressDialog;
  316. // Create a progress dialog object in case of delayed termination.
  317. // This will create the watcher thread.
  318. pProgressDialog = new CProgressDialog(pWarningDialog);
  319. if ((pProgressDialog != NULL) && !pProgressDialog->IsCreated())
  320. {
  321. pProgressDialog->Release();
  322. pProgressDialog = NULL;
  323. }
  324. // Attempt to terminate the process if requested by the user.
  325. fTerminated = TerminatedFirstInstance(hPort, pszImageName);
  326. // Once this function returns signal the event (in case the
  327. // thread is still waiting). If the thread is still waiting this
  328. // effectively cancels the dialog. Either way if the dialog is
  329. // shown then close it, wait for the thread to exit and release
  330. // the reference to destroy the object.
  331. if (pProgressDialog != NULL)
  332. {
  333. pProgressDialog->SignalTerminated();
  334. pWarningDialog->CloseDialog();
  335. pProgressDialog->WaitForCompletion(INFINITE);
  336. pProgressDialog->Release();
  337. }
  338. // If there was some failure then let the user know.
  339. if (!fTerminated)
  340. {
  341. pWarningDialog->ShowFailure();
  342. }
  343. }
  344. pWarningDialog->Release();
  345. }
  346. TBOOL(CloseHandle(hPort));
  347. }
  348. return(fTerminated);
  349. }
  350. // --------------------------------------------------------------------------
  351. // CGracefulTerminateApplication::CanTerminateFirstInstance
  352. //
  353. // Arguments: hPort = Port to server.
  354. // pszImageName = Image name of process to terminate.
  355. // pszUser = User of process (returned).
  356. // cchUser = Count of characters for buffer.
  357. //
  358. // Returns: bool
  359. //
  360. // Purpose: Asks the server whether the current user has privileges to
  361. // terminate the BAM type 1 process of the given image name
  362. // which is known to be running. The API returns whether the
  363. // operation is allowed and who the current user of the process
  364. // is.
  365. //
  366. // History: 2000-11-03 vtan created
  367. // --------------------------------------------------------------------------
  368. bool CGracefulTerminateApplication::CanTerminateFirstInstance (HANDLE hPort, const WCHAR *pszImageName, WCHAR *pszUser, int cchUser)
  369. {
  370. bool fCanTerminate;
  371. fCanTerminate = false;
  372. if ((hPort != NULL) && (pszImageName != NULL))
  373. {
  374. FUSAPI_PORT_MESSAGE portMessageIn, portMessageOut;
  375. ZeroMemory(&portMessageIn, sizeof(portMessageIn));
  376. ZeroMemory(&portMessageOut, sizeof(portMessageOut));
  377. portMessageIn.apiBAM.apiGeneric.ulAPINumber = API_BAM_QUERYUSERPERMISSION;
  378. portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.pszImageName = pszImageName;
  379. portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.cchImageName = lstrlen(pszImageName) + sizeof('\0');
  380. portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.pszUser = pszUser;
  381. portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.cchUser = cchUser;
  382. portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_BAM);
  383. portMessageIn.portMessage.u1.s1.TotalLength = static_cast<CSHORT>(sizeof(FUSAPI_PORT_MESSAGE));
  384. if (NT_SUCCESS(NtRequestWaitReplyPort(hPort, &portMessageIn.portMessage, &portMessageOut.portMessage)) &&
  385. NT_SUCCESS(portMessageOut.apiBAM.apiGeneric.status))
  386. {
  387. fCanTerminate = portMessageOut.apiBAM.apiSpecific.apiQueryUserPermission.out.fCanShutdownApplication;
  388. pszUser[cchUser - sizeof('\0')] = L'\0';
  389. }
  390. else
  391. {
  392. pszUser[0] = L'\0';
  393. }
  394. }
  395. return(fCanTerminate);
  396. }
  397. // --------------------------------------------------------------------------
  398. // CGracefulTerminateApplication::TerminatedFirstInstance
  399. //
  400. // Arguments: hPort = Port to server.
  401. // pszImageName = Image name to terminate.
  402. //
  403. // Returns: bool
  404. //
  405. // Purpose: Asks the server to terminate the first running instance of the
  406. // BAM type 1 process.
  407. //
  408. // History: 2000-11-03 vtan created
  409. // --------------------------------------------------------------------------
  410. bool CGracefulTerminateApplication::TerminatedFirstInstance (HANDLE hPort, const WCHAR *pszImageName)
  411. {
  412. bool fTerminated;
  413. fTerminated = false;
  414. if (hPort != NULL)
  415. {
  416. FUSAPI_PORT_MESSAGE portMessageIn, portMessageOut;
  417. ZeroMemory(&portMessageIn, sizeof(portMessageIn));
  418. ZeroMemory(&portMessageOut, sizeof(portMessageOut));
  419. portMessageIn.apiBAM.apiGeneric.ulAPINumber = API_BAM_TERMINATERUNNING;
  420. portMessageIn.apiBAM.apiSpecific.apiTerminateRunning.in.pszImageName = pszImageName;
  421. portMessageIn.apiBAM.apiSpecific.apiTerminateRunning.in.cchImageName = lstrlen(pszImageName) + sizeof('\0');
  422. portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_BAM);
  423. portMessageIn.portMessage.u1.s1.TotalLength = static_cast<CSHORT>(sizeof(FUSAPI_PORT_MESSAGE));
  424. if (NT_SUCCESS(NtRequestWaitReplyPort(hPort, &portMessageIn.portMessage, &portMessageOut.portMessage)) &&
  425. NT_SUCCESS(portMessageOut.apiBAM.apiGeneric.status))
  426. {
  427. fTerminated = portMessageOut.apiBAM.apiSpecific.apiTerminateRunning.out.fResult;
  428. }
  429. }
  430. return(fTerminated);
  431. }
  432. // --------------------------------------------------------------------------
  433. // CGracefulTerminateApplication::EnumWindowsProc
  434. //
  435. // Arguments: See the platform SDK under EnumWindowsProc.
  436. //
  437. // Returns: See the platform SDK under EnumWindowsProc.
  438. //
  439. // Purpose: Top level window enumerator callback which compares the
  440. // process IDs of the windows and whether they are visible. If
  441. // there is a match on BOTH counts the a WM_CLOSE message is
  442. // posted to the window to allow a graceful termination.
  443. //
  444. // History: 2000-10-27 vtan created
  445. // --------------------------------------------------------------------------
  446. BOOL CALLBACK CGracefulTerminateApplication::EnumWindowsProc (HWND hwnd, LPARAM lParam)
  447. {
  448. DWORD dwThreadID, dwProcessID;
  449. CGracefulTerminateApplication *pThis;
  450. pThis = reinterpret_cast<CGracefulTerminateApplication*>(lParam);
  451. dwThreadID = GetWindowThreadProcessId(hwnd, &dwProcessID);
  452. if ((dwProcessID == pThis->_dwProcessID) && IsWindowVisible(hwnd))
  453. {
  454. pThis->_fFoundWindow = true;
  455. TBOOL(PostMessage(hwnd, WM_CLOSE, 0, 0));
  456. }
  457. return(TRUE);
  458. }
  459. #endif /* _X86_ */