main
cpp 252 lines 7.7 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 #include "precomp.h"
4
5 #define BURN_UITHREAD_CLASS_WINDOW L"WixBurnMessageWindow"
6
7
8 // structs
9
10 struct UITHREAD_CONTEXT
11 {
12 HANDLE hInitializedEvent;
13 HINSTANCE hInstance;
14 BURN_ENGINE_STATE* pEngineState;
15 };
16
17 struct UITHREAD_INFO
18 {
19 BOOL fElevatedEngine;
20 BURN_ENGINE_STATE* pEngineState;
21 };
22
23
24 // internal function declarations
25
26 static DWORD WINAPI ThreadProc(
27 __in LPVOID pvContext
28 );
29
30 static LRESULT CALLBACK WndProc(
31 __in HWND hWnd,
32 __in UINT uMsg,
33 __in WPARAM wParam,
34 __in LPARAM lParam
35 );
36
37
38 // function definitions
39
40 HRESULT UiCreateMessageWindow(
41 __in HINSTANCE hInstance,
42 __in BURN_ENGINE_STATE* pEngineState
43 )
44 {
45 HRESULT hr = S_OK;
46 HANDLE rgWaitHandles[2] = { };
47 UITHREAD_CONTEXT context = { };
48
49 // Try to make this process the first one to receive WM_QUERYENDSESSION.
50 // When blocking shutdown during Apply, this prevents other applications from being closed even though the restart will be blocked.
51 // When initiating a restart, this makes it reasonable to assume WM_QUERYENDSESSION will be received quickly because otherwise other applications could delay indefinitely.
52 ::SetProcessShutdownParameters(0x3FF, 0);
53
54 // Create event to signal after the UI thread / window is initialized.
55 rgWaitHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL);
56 ExitOnNullWithLastError(rgWaitHandles[0], hr, "Failed to create initialization event.");
57
58 // Pass necessary information to create the window.
59 context.hInitializedEvent = rgWaitHandles[0];
60 context.hInstance = hInstance;
61 context.pEngineState = pEngineState;
62
63 // Create our separate UI thread.
64 rgWaitHandles[1] = ::CreateThread(NULL, 0, ThreadProc, &context, 0, NULL);
65 ExitOnNullWithLastError(rgWaitHandles[1], hr, "Failed to create the UI thread.");
66
67 // Wait for either the thread to be initialized or the window to exit / fail prematurely.
68 ::WaitForMultipleObjects(countof(rgWaitHandles), rgWaitHandles, FALSE, INFINITE);
69
70 pEngineState->hMessageWindowThread = rgWaitHandles[1];
71 rgWaitHandles[1] = NULL;
72
73 LExit:
74 ReleaseHandle(rgWaitHandles[1]);
75 ReleaseHandle(rgWaitHandles[0]);
76
77 return hr;
78 }
79
80 void UiCloseMessageWindow(
81 __in BURN_ENGINE_STATE* pEngineState
82 )
83 {
84 if (::IsWindow(pEngineState->hMessageWindow))
85 {
86 ::PostMessageW(pEngineState->hMessageWindow, WM_CLOSE, 0, 0);
87 }
88 }
89
90
91 // internal function definitions
92
93 static DWORD WINAPI ThreadProc(
94 __in LPVOID pvContext
95 )
96 {
97 HRESULT hr = S_OK;
98 UITHREAD_CONTEXT* pContext = static_cast<UITHREAD_CONTEXT*>(pvContext);
99 UITHREAD_INFO info = { };
100
101 WNDCLASSW wc = { };
102 BOOL fRegistered = FALSE;
103 HWND hWnd = NULL;
104
105 BOOL fRet = FALSE;
106 MSG msg = { };
107
108 BURN_ENGINE_STATE* pEngineState = pContext->pEngineState;
109 BOOL fElevatedEngine = BURN_MODE_ELEVATED == pContext->pEngineState->internalCommand.mode;
110
111 wc.lpfnWndProc = WndProc;
112 wc.hInstance = pContext->hInstance;
113 wc.lpszClassName = BURN_UITHREAD_CLASS_WINDOW;
114
115 if (!::RegisterClassW(&wc))
116 {
117 ExitWithLastError(hr, "Failed to register window.");
118 }
119
120 fRegistered = TRUE;
121
122 info.fElevatedEngine = fElevatedEngine;
123 info.pEngineState = pEngineState;
124
125 // Create the window to handle reboots without activating it.
126 hWnd = ::CreateWindowExW(WS_EX_NOACTIVATE, wc.lpszClassName, NULL, WS_POPUP, 0, 0, 0, 0, HWND_DESKTOP, NULL, pContext->hInstance, &info);
127 ExitOnNullWithLastError(hWnd, hr, "Failed to create Burn UI thread window.");
128
129 ::ShowWindow(hWnd, SW_SHOWNA);
130
131 // Persist the window handle and let the caller know we've initialized.
132 pEngineState->hMessageWindow = hWnd;
133 ::SetEvent(pContext->hInitializedEvent);
134
135 // Pump messages until the window is closed.
136 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
137 {
138 if (-1 == fRet)
139 {
140 hr = E_UNEXPECTED;
141 ExitOnFailure(hr, "Unexpected return value from message pump.");
142 }
143 else if (!::IsDialogMessageW(msg.hwnd, &msg))
144 {
145 ::TranslateMessage(&msg);
146 ::DispatchMessageW(&msg);
147 }
148 }
149
150 LExit:
151 if (fRegistered)
152 {
153 ::UnregisterClassW(BURN_UITHREAD_CLASS_WINDOW, pContext->hInstance);
154 }
155
156 return hr;
157 }
158
159 static LRESULT CALLBACK WndProc(
160 __in HWND hWnd,
161 __in UINT uMsg,
162 __in WPARAM wParam,
163 __in LPARAM lParam
164 )
165 {
166 switch (uMsg)
167 {
168 case WM_NCCREATE:
169 {
170 LPCREATESTRUCTW lpcs = reinterpret_cast<LPCREATESTRUCTW>(lParam);
171 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(lpcs->lpCreateParams));
172 break;
173 }
174
175 case WM_NCDESTROY:
176 {
177 LRESULT lRes = ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
178 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
179 return lRes;
180 }
181
182 case WM_QUERYENDSESSION:
183 {
184 BOOL fCritical = ENDSESSION_CRITICAL & lParam;
185 BOOL fAllowed = FALSE;
186
187 UITHREAD_INFO* pInfo = reinterpret_cast<UITHREAD_INFO*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
188 if (!pInfo->pEngineState->plan.fApplying && // always block shutdown during apply.
189 !fCritical) // always block critical shutdowns to receive the WM_ENDSESSION message.
190 {
191 fAllowed = TRUE;
192 }
193
194 CoreUpdateRestartState(pInfo->pEngineState, BURN_RESTART_STATE_INITIATING);
195 pInfo->pEngineState->fCriticalShutdownInitiated |= fCritical;
196
197 LogId(REPORT_STANDARD, MSG_SYSTEM_SHUTDOWN_REQUEST, LoggingBoolToString(fAllowed), LoggingBoolToString(pInfo->fElevatedEngine), LoggingBoolToString(fCritical), LoggingBoolToString(lParam & ENDSESSION_LOGOFF), LoggingBoolToString(lParam & ENDSESSION_CLOSEAPP));
198 LogFlush();
199 return fAllowed;
200 }
201
202 case WM_ENDSESSION:
203 {
204 UITHREAD_INFO* pInfo = reinterpret_cast<UITHREAD_INFO*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
205 BOOL fAllowed = 0 != wParam;
206
207 LogId(REPORT_STANDARD, MSG_SYSTEM_SHUTDOWN_RESULT, LoggingBoolToString(fAllowed), LoggingBoolToString(pInfo->fElevatedEngine), LoggingBoolToString(lParam & ENDSESSION_CRITICAL), LoggingBoolToString(lParam & ENDSESSION_LOGOFF), LoggingBoolToString(lParam & ENDSESSION_CLOSEAPP));
208
209 if (fAllowed)
210 {
211 // Windows will shutdown the process as soon as we return from this message.
212 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms700677(v=vs.85)
213
214 // Give Apply approximately 20 seconds to complete.
215 for (DWORD i = 0; i < 80; ++i)
216 {
217 if (!pInfo->pEngineState->plan.fApplying)
218 {
219 break;
220 }
221
222 ::Sleep(250);
223 }
224
225 // If this is the per-machine process then close the logging pipe with the parent process.
226 if (pInfo->fElevatedEngine)
227 {
228 CoreCloseElevatedLoggingThread(pInfo->pEngineState);
229 }
230 else
231 {
232 CoreWaitForUnelevatedLoggingThread(pInfo->pEngineState->hUnelevatedLoggingThread);
233 }
234
235 LogStringWorkRaw("=======================================\r\n");
236
237 // Close the log to try to make sure everything is flushed to disk.
238 LogClose(FALSE);
239 }
240
241 CoreUpdateRestartState(pInfo->pEngineState, fAllowed ? BURN_RESTART_STATE_INITIATED : BURN_RESTART_STATE_BLOCKED);
242
243 return 0;
244 }
245
246 case WM_DESTROY:
247 ::PostQuitMessage(0);
248 return 0;
249 }
250
251 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
252 }