xref: /reactos/base/system/winlogon/shutdown.c (revision 0ff9b0ff)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS Winlogon
4  * FILE:            base/system/winlogon/shutdown.c
5  * PURPOSE:         System shutdown dialog
6  * PROGRAMMERS:     Edward Bronsten <tanki.alpha5056@gmail.com>
7  *                  Eric Kohl
8  *                  Hermes Belusca-Maito
9  */
10 
11 /* INCLUDES ******************************************************************/
12 
13 #include "winlogon.h"
14 
15 #include <rpc.h>
16 #include <winreg_s.h>
17 
18 /* DEFINES *******************************************************************/
19 
20 #define SHUTDOWN_TIMER_ID 2000
21 #define SECONDS_PER_DAY 86400
22 #define SECONDS_PER_DECADE 315360000
23 
24 /* STRUCTS *******************************************************************/
25 
26 typedef struct _SYS_SHUTDOWN_PARAMS
27 {
28     PWSTR pszMessage;
29     ULONG dwTimeout;
30 
31     HDESK hShutdownDesk;
32     WCHAR DesktopName[512];
33     WINDOWPLACEMENT wpPos;
34 
35     BOOLEAN bShuttingDown;
36     BOOLEAN bRebootAfterShutdown;
37     BOOLEAN bForceAppsClosed;
38     DWORD dwReason;
39 } SYS_SHUTDOWN_PARAMS, *PSYS_SHUTDOWN_PARAMS;
40 
41 /* GLOBALS *******************************************************************/
42 
43 SYS_SHUTDOWN_PARAMS g_ShutdownParams;
44 
45 /* FUNCTIONS *****************************************************************/
46 
47 static
48 BOOL
DoSystemShutdown(IN PSYS_SHUTDOWN_PARAMS pShutdownParams)49 DoSystemShutdown(
50     IN PSYS_SHUTDOWN_PARAMS pShutdownParams)
51 {
52     BOOL Success;
53 
54     /* If shutdown has been cancelled, bail out now */
55     if (!pShutdownParams->bShuttingDown)
56         return TRUE;
57 
58     Success = ExitWindowsEx((pShutdownParams->bRebootAfterShutdown ? EWX_REBOOT : EWX_SHUTDOWN) |
59                             (pShutdownParams->bForceAppsClosed ? EWX_FORCE : 0),
60                              pShutdownParams->dwReason);
61     if (!Success)
62     {
63         /* Something went wrong, cancel shutdown */
64         pShutdownParams->bShuttingDown = FALSE;
65     }
66 
67     return Success;
68 }
69 
70 static
71 VOID
OnTimer(IN HWND hwndDlg,IN PSYS_SHUTDOWN_PARAMS pShutdownParams)72 OnTimer(
73     IN HWND hwndDlg,
74     IN PSYS_SHUTDOWN_PARAMS pShutdownParams)
75 {
76     HDESK hInputDesktop;
77     BOOL bSuccess;
78     DWORD dwSize;
79     INT iSeconds, iMinutes, iHours, iDays;
80     WCHAR szFormatBuffer[32];
81     WCHAR szBuffer[32];
82     WCHAR DesktopName[512];
83 
84     if (!pShutdownParams->bShuttingDown)
85     {
86         /* Shutdown has been cancelled, close the dialog and bail out */
87         EndDialog(hwndDlg, IDABORT);
88         return;
89     }
90 
91     /*
92      * Check whether the input desktop has changed. If so, close the dialog,
93      * and let the shutdown thread recreate it on the new desktop.
94      */
95 
96     // TODO: Investigate: It would be great if we could also compare with
97     // our internally maintained desktop handles, before calling that heavy
98     // comparison.
99     // (Note that we cannot compare handles with arbitrary input desktop,
100     // since OpenInputDesktop() creates new handle instances everytime.)
101 
102     hInputDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL);
103     if (!hInputDesktop)
104     {
105         /* No input desktop but we have a dialog: kill it */
106         ERR("OpenInputDesktop() failed, error 0x%lx\n", GetLastError());
107         EndDialog(hwndDlg, 0);
108         return;
109     }
110     bSuccess = GetUserObjectInformationW(hInputDesktop,
111                                          UOI_NAME,
112                                          DesktopName,
113                                          sizeof(DesktopName),
114                                          &dwSize);
115     if (!bSuccess)
116     {
117         ERR("GetUserObjectInformationW(0x%p) failed, error 0x%lx\n",
118             hInputDesktop, GetLastError());
119     }
120     CloseDesktop(hInputDesktop);
121 
122     if (bSuccess && (wcscmp(DesktopName, pShutdownParams->DesktopName) != 0))
123     {
124         TRACE("Input desktop has changed: '%S' --> '%S'\n",
125               pShutdownParams->DesktopName, DesktopName);
126 
127         /* Save the original dialog position to be restored later */
128         pShutdownParams->wpPos.length = sizeof(pShutdownParams->wpPos);
129         GetWindowPlacement(hwndDlg, &pShutdownParams->wpPos);
130 
131         /* Close the dialog */
132         EndDialog(hwndDlg, IDCANCEL);
133         return;
134     }
135 
136     /* Update the shutdown timeout */
137     if (pShutdownParams->dwTimeout < SECONDS_PER_DAY)
138     {
139         iSeconds = (INT)pShutdownParams->dwTimeout;
140         iHours = iSeconds / 3600;
141         iSeconds -= iHours * 3600;
142         iMinutes = iSeconds / 60;
143         iSeconds -= iMinutes * 60;
144 
145         LoadStringW(hAppInstance, IDS_TIMEOUTSHORTFORMAT, szFormatBuffer, ARRAYSIZE(szFormatBuffer));
146         swprintf(szBuffer, szFormatBuffer, iHours, iMinutes, iSeconds);
147     }
148     else
149     {
150         iDays = (INT)(pShutdownParams->dwTimeout / SECONDS_PER_DAY);
151 
152         LoadStringW(hAppInstance, IDS_TIMEOUTLONGFORMAT, szFormatBuffer, ARRAYSIZE(szFormatBuffer));
153         swprintf(szBuffer, szFormatBuffer, iDays);
154     }
155 
156     SetDlgItemTextW(hwndDlg, IDC_SYSSHUTDOWNTIMELEFT, szBuffer);
157 
158     if (pShutdownParams->dwTimeout == 0)
159     {
160         /* Close the dialog and let the shutdown thread perform the system shutdown */
161         EndDialog(hwndDlg, 0);
162         return;
163     }
164 
165     pShutdownParams->dwTimeout--;
166 }
167 
168 static
169 INT_PTR
170 CALLBACK
ShutdownDialogProc(IN HWND hwndDlg,IN UINT uMsg,IN WPARAM wParam,IN LPARAM lParam)171 ShutdownDialogProc(
172     IN HWND hwndDlg,
173     IN UINT uMsg,
174     IN WPARAM wParam,
175     IN LPARAM lParam)
176 {
177     PSYS_SHUTDOWN_PARAMS pShutdownParams;
178 
179     pShutdownParams = (PSYS_SHUTDOWN_PARAMS)GetWindowLongPtrW(hwndDlg, DWLP_USER);
180 
181     switch (uMsg)
182     {
183         case WM_INITDIALOG:
184         {
185             pShutdownParams = (PSYS_SHUTDOWN_PARAMS)lParam;
186             SetWindowLongPtrW(hwndDlg, DWLP_USER, (LONG_PTR)pShutdownParams);
187 
188             /* Display the shutdown message */
189             if (pShutdownParams->pszMessage)
190             {
191                 SetDlgItemTextW(hwndDlg,
192                                 IDC_SYSSHUTDOWNMESSAGE,
193                                 pShutdownParams->pszMessage);
194             }
195 
196             /* Remove the Close menu item */
197             DeleteMenu(GetSystemMenu(hwndDlg, FALSE), SC_CLOSE, MF_BYCOMMAND);
198 
199             /* Position the window (initial position, or restore from old) */
200             if (pShutdownParams->wpPos.length == sizeof(pShutdownParams->wpPos))
201                 SetWindowPlacement(hwndDlg, &pShutdownParams->wpPos);
202 
203             SetWindowPos(hwndDlg, HWND_TOPMOST, 0, 0, 0, 0,
204                          SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
205 
206             /* Initialize the timer */
207             PostMessageW(hwndDlg, WM_TIMER, 0, 0);
208             SetTimer(hwndDlg, SHUTDOWN_TIMER_ID, 1000, NULL);
209             break;
210         }
211 
212         /* NOTE: Do not handle WM_CLOSE */
213         case WM_DESTROY:
214             KillTimer(hwndDlg, SHUTDOWN_TIMER_ID);
215             break;
216 
217         case WM_TIMER:
218             OnTimer(hwndDlg, pShutdownParams);
219             break;
220 
221         default:
222             return FALSE;
223     }
224 
225     return TRUE;
226 }
227 
228 static
229 DWORD
230 WINAPI
InitiateSystemShutdownThread(IN LPVOID lpParameter)231 InitiateSystemShutdownThread(
232     IN LPVOID lpParameter)
233 {
234     PSYS_SHUTDOWN_PARAMS pShutdownParams;
235     HDESK hInputDesktop;
236     DWORD dwSize;
237     INT_PTR res;
238 
239     pShutdownParams = (PSYS_SHUTDOWN_PARAMS)lpParameter;
240 
241     /* Default to initial dialog position */
242     pShutdownParams->wpPos.length = 0;
243 
244     /* Continuously display the shutdown dialog on the current input desktop */
245     while (TRUE)
246     {
247         /* Retrieve the current input desktop */
248         hInputDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL);
249         if (!hInputDesktop)
250         {
251             /* No input desktop on the current WinSta0, just shut down */
252             ERR("OpenInputDesktop() failed, error 0x%lx\n", GetLastError());
253             break;
254         }
255 
256         /* Remember it for checking desktop changes later */
257         pShutdownParams->hShutdownDesk = hInputDesktop;
258         if (!GetUserObjectInformationW(pShutdownParams->hShutdownDesk,
259                                        UOI_NAME,
260                                        pShutdownParams->DesktopName,
261                                        sizeof(pShutdownParams->DesktopName),
262                                        &dwSize))
263         {
264             ERR("GetUserObjectInformationW(0x%p) failed, error 0x%lx\n",
265                 pShutdownParams->hShutdownDesk, GetLastError());
266         }
267 
268         /* Assign the desktop to the current thread */
269         SetThreadDesktop(hInputDesktop);
270 
271         /* Display the shutdown dialog on the current input desktop */
272         res = DialogBoxParamW(hAppInstance,
273                               MAKEINTRESOURCEW(IDD_SYSSHUTDOWN),
274                               NULL,
275                               ShutdownDialogProc,
276                               (LPARAM)pShutdownParams);
277 
278         /* Close the desktop */
279         CloseDesktop(hInputDesktop);
280 
281         /*
282          * Check why the dialog has been closed.
283          *
284          * - If it failed to be created (returned -1), don't care about
285          *   re-creating it, and proceed directly to shutdown.
286          *
287          * - If it closed unexpectedly (returned != 1), check whether a
288          *   shutdown is in progress. If the shutdown has been cancelled,
289          *   just bail out; if a shutdown is in progress and the timeout
290          *   is 0, bail out and proceed to shutdown.
291          *
292          * - If the dialog has closed because the input desktop changed,
293          *   loop again and recreate it on the new desktop.
294          */
295         if ((res == -1) || (res != IDCANCEL) ||
296             !(pShutdownParams->bShuttingDown && (pShutdownParams->dwTimeout > 0)))
297         {
298             break;
299         }
300     }
301 
302     /* Reset dialog information */
303     pShutdownParams->hShutdownDesk = NULL;
304     ZeroMemory(&pShutdownParams->DesktopName, sizeof(pShutdownParams->DesktopName));
305     ZeroMemory(&pShutdownParams->wpPos, sizeof(pShutdownParams->wpPos));
306 
307     if (pShutdownParams->pszMessage)
308     {
309         HeapFree(GetProcessHeap(), 0, pShutdownParams->pszMessage);
310         pShutdownParams->pszMessage = NULL;
311     }
312 
313     if (pShutdownParams->bShuttingDown)
314     {
315         /* Perform the system shutdown */
316         if (DoSystemShutdown(pShutdownParams))
317             return ERROR_SUCCESS;
318         else
319             return GetLastError();
320     }
321 
322     pShutdownParams->bShuttingDown = FALSE;
323     return ERROR_SUCCESS;
324 }
325 
326 
327 DWORD
TerminateSystemShutdown(VOID)328 TerminateSystemShutdown(VOID)
329 {
330     if (_InterlockedCompareExchange8((volatile char*)&g_ShutdownParams.bShuttingDown, FALSE, TRUE) == FALSE)
331         return ERROR_NO_SHUTDOWN_IN_PROGRESS;
332 
333     return ERROR_SUCCESS;
334 }
335 
336 DWORD
StartSystemShutdown(IN PUNICODE_STRING pMessage,IN ULONG dwTimeout,IN BOOLEAN bForceAppsClosed,IN BOOLEAN bRebootAfterShutdown,IN ULONG dwReason)337 StartSystemShutdown(
338     IN PUNICODE_STRING pMessage,
339     IN ULONG dwTimeout,
340     IN BOOLEAN bForceAppsClosed,
341     IN BOOLEAN bRebootAfterShutdown,
342     IN ULONG dwReason)
343 {
344     HANDLE hThread;
345 
346     /* Fail if the timeout is 10 years or more */
347     if (dwTimeout >= SECONDS_PER_DECADE)
348         return ERROR_INVALID_PARAMETER;
349 
350     if (_InterlockedCompareExchange8((volatile char*)&g_ShutdownParams.bShuttingDown, TRUE, FALSE) == TRUE)
351         return ERROR_SHUTDOWN_IN_PROGRESS;
352 
353     if ((dwTimeout != 0) && pMessage && pMessage->Length && pMessage->Buffer)
354     {
355         g_ShutdownParams.pszMessage = HeapAlloc(GetProcessHeap(),
356                                                 HEAP_ZERO_MEMORY,
357                                                 pMessage->Length + sizeof(UNICODE_NULL));
358         if (g_ShutdownParams.pszMessage == NULL)
359         {
360             g_ShutdownParams.bShuttingDown = FALSE;
361             return GetLastError();
362         }
363 
364         wcsncpy(g_ShutdownParams.pszMessage,
365                 pMessage->Buffer,
366                 pMessage->Length / sizeof(WCHAR));
367     }
368     else
369     {
370         g_ShutdownParams.pszMessage = NULL;
371     }
372 
373     g_ShutdownParams.dwTimeout = dwTimeout;
374     g_ShutdownParams.bForceAppsClosed = bForceAppsClosed;
375     g_ShutdownParams.bRebootAfterShutdown = bRebootAfterShutdown;
376     g_ShutdownParams.dwReason = dwReason;
377 
378     /* If dwTimeout is zero perform an immediate system shutdown,
379      * otherwise display the countdown shutdown dialog. */
380     if (g_ShutdownParams.dwTimeout == 0)
381     {
382         if (DoSystemShutdown(&g_ShutdownParams))
383             return ERROR_SUCCESS;
384     }
385     else
386     {
387         hThread = CreateThread(NULL, 0, InitiateSystemShutdownThread,
388                                &g_ShutdownParams, 0, NULL);
389         if (hThread)
390         {
391             CloseHandle(hThread);
392             return ERROR_SUCCESS;
393         }
394     }
395 
396     if (g_ShutdownParams.pszMessage)
397     {
398         HeapFree(GetProcessHeap(), 0, g_ShutdownParams.pszMessage);
399         g_ShutdownParams.pszMessage = NULL;
400     }
401 
402     g_ShutdownParams.bShuttingDown = FALSE;
403     return GetLastError();
404 }
405 
406 /* EOF */
407