xref: /reactos/win32ss/user/user32/misc/exit.c (revision 94431656)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS user32.dll
4  * FILE:            win32ss/user/user32/misc/exit.c
5  * PURPOSE:         Shutdown related functions
6  * PROGRAMMER:      Eric Kohl
7  */
8 
9 #include <user32.h>
10 
11 /*
12  * Sequence of events:
13  *
14  * - App (usually explorer) calls ExitWindowsEx()
15  * - ExitWindowsEx() sends a message to CSRSS
16  * - CSRSS impersonates the caller and sends a message to a hidden Winlogon window
17  * - Winlogon checks if the caller has the required privileges
18  * - Winlogon enters pending log-out state
19  * - Winlogon impersonates the interactive user and calls ExitWindowsEx() again,
20  *   passing some special internal flags
21  * - CSRSS loops over all processes of the interactive user (sorted by their
22  *   SetProcessShutdownParameters() level), sending WM_QUERYENDSESSION and
23  *   WM_ENDSESSION messages to its top-level windows. If the messages aren't
24  *   processed within the timeout period (registry key HKCU\Control Panel\Desktop\HungAppTimeout)
25  *   CSRSS will put up a dialog box asking if the process should be terminated.
26  *   Using the registry key HKCU\Control Panel\Desktop\AutoEndTask you can
27  *   specify that the dialog box shouldn't be shown and CSRSS should just
28  *   terminate the thread. If the the WM_ENDSESSION message is processed
29  *   but the thread doesn't terminate within the timeout specified by
30  *   HKCU\Control Panel\Desktop\WaitToKillAppTimeout CSRSS will terminate
31  *   the thread. When all the top-level windows have been destroyed CSRSS
32  *   will terminate the process.
33  *   If the process is a console process, CSRSS will send a CTRL_LOGOFF_EVENT
34  *   to the console control handler on logoff. No event is sent on shutdown.
35  *   If the handler doesn't respond in time the same activities as for GUI
36  *   apps (i.e. display dialog box etc) take place. This also happens if
37  *   the handler returns TRUE.
38  * - This ends the processing for the first ExitWindowsEx() call from Winlogon.
39  *   Execution continues in Winlogon, which calls ExitWindowsEx() again to
40  *   terminate COM processes in the interactive user's session.
41  * - Winlogon stops impersonating the interactive user (whose processes are
42  *   all dead by now). and enters log-out state
43  * - If the ExitWindowsEx() request was for a logoff, Winlogon sends a SAS
44  *   event (to display the "press ctrl+alt+del") to the GINA. Winlogon then
45  *   waits for the GINA to send a SAS event to login.
46  * - If the ExitWindowsEx() request was for shutdown/restart, Winlogon calls
47  *   ExitWindowsEx() again in the system process context.
48  * - CSRSS goes through the motions of sending WM_QUERYENDSESSION/WM_ENDSESSION
49  *   to GUI processes running in the system process context but won't display
50  *   dialog boxes or kill threads/processes. Same for console processes,
51  *   using the CTRL_SHUTDOWN_EVENT. The Service Control Manager is one of
52  *   these console processes and has a special timeout value WaitToKillServiceTimeout.
53  * - After CSRSS has finished its pass notifying processes that system is shutting down,
54  *   Winlogon finishes the shutdown process by calling the executive subsystem
55  *   function NtShutdownSystem.
56  */
57 
58 typedef struct
59 {
60     UINT  uFlags;
61     DWORD dwReserved;
62 } EXIT_REACTOS_DATA, *PEXIT_REACTOS_DATA;
63 
64 static BOOL
65 ExitWindowsWorker(UINT uFlags,
66                   DWORD dwReserved,
67                   BOOL bCalledFromThread);
68 
69 static DWORD
70 WINAPI
ExitWindowsThread(LPVOID Param)71 ExitWindowsThread(LPVOID Param)
72 {
73     DWORD dwExitCode;
74     PEXIT_REACTOS_DATA ExitData = (PEXIT_REACTOS_DATA)Param;
75 
76     /* Do the exit asynchronously */
77     if (ExitWindowsWorker(ExitData->uFlags, ExitData->dwReserved, TRUE))
78         dwExitCode = ERROR_SUCCESS;
79     else
80         dwExitCode = GetLastError();
81 
82     ExitThread(dwExitCode);
83     return ERROR_SUCCESS;
84 }
85 
86 static BOOL
ExitWindowsWorker(UINT uFlags,DWORD dwReserved,BOOL bCalledFromThread)87 ExitWindowsWorker(UINT uFlags,
88                   DWORD dwReserved,
89                   BOOL bCalledFromThread)
90 {
91     EXIT_REACTOS_DATA ExitData;
92     HANDLE hExitThread;
93     DWORD ExitCode;
94     MSG msg;
95 
96     USER_API_MESSAGE ApiMessage;
97     PUSER_EXIT_REACTOS ExitReactOSRequest = &ApiMessage.Data.ExitReactOSRequest;
98 
99     /*
100      * 1- FIXME: Call NtUserCallOneParam(uFlags, ONEPARAM_ROUTINE_PREPAREFORLOGOFF);
101      *    If success we can continue, otherwise we must fail.
102      */
103 
104     /*
105      * 2- Send the Exit request to CSRSS (and to Win32k indirectly).
106      *    We can shutdown synchronously or asynchronously.
107      */
108 
109     // ExitReactOSRequest->LastError = ERROR_SUCCESS;
110     ExitReactOSRequest->Flags = uFlags;
111 
112     CsrClientCallServer((PCSR_API_MESSAGE)&ApiMessage,
113                         NULL,
114                         CSR_CREATE_API_NUMBER(USERSRV_SERVERDLL_INDEX, UserpExitWindowsEx),
115                         sizeof(*ExitReactOSRequest));
116 
117     /* Set the last error accordingly */
118     if (NT_SUCCESS(ApiMessage.Status) || ApiMessage.Status == STATUS_CANT_WAIT)
119     {
120         if (ExitReactOSRequest->LastError != ERROR_SUCCESS)
121             UserSetLastError(ExitReactOSRequest->LastError);
122     }
123     else
124     {
125         UserSetLastNTError(ApiMessage.Status);
126         ExitReactOSRequest->Success = FALSE;
127     }
128 
129     /*
130      * In case CSR call succeeded and we did a synchronous exit
131      * (STATUS_CANT_WAIT is considered as a non-success status),
132      * return the real state of the operation now.
133      */
134     if (NT_SUCCESS(ApiMessage.Status))
135         return ExitReactOSRequest->Success;
136 
137     /*
138      * In case something failed: we have a non-success status and:
139      * - either we were doing a synchronous exit (Status != STATUS_CANT_WAIT), or
140      * - we were doing an asynchronous exit because we were called recursively via
141      *   another thread but we failed to exit,
142      * then bail out immediately, otherwise we would enter an infinite loop of exit requests.
143      *
144      * On the contrary if we need to do an asynchronous exit (Status == STATUS_CANT_WAIT
145      * and not called recursively via another thread), then continue and do the exit.
146      */
147     if (ApiMessage.Status != STATUS_CANT_WAIT || bCalledFromThread)
148     {
149         UserSetLastNTError(ApiMessage.Status);
150         return FALSE;
151     }
152 
153     /*
154      * 3- Win32k wants us to perform an asynchronous exit. Run the request in a thread.
155      * (ApiMessage.Status == STATUS_CANT_WAIT and not already called from a thread)
156      */
157     ExitData.uFlags     = uFlags;
158     ExitData.dwReserved = dwReserved;
159     hExitThread = CreateThread(NULL, 0, ExitWindowsThread, &ExitData, 0, NULL);
160     if (hExitThread == NULL)
161         return FALSE;
162 
163     /* Pump and discard any input events sent to the app(s) */
164     while (MsgWaitForMultipleObjectsEx(1, &hExitThread, INFINITE, QS_ALLINPUT, 0))
165     {
166         while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
167             DispatchMessageW(&msg);
168     }
169 
170     /* Finally, return to caller */
171     if (!GetExitCodeThread(hExitThread, &ExitCode))
172         ExitCode = GetLastError();
173 
174     CloseHandle(hExitThread);
175 
176     if (ExitCode != ERROR_SUCCESS)
177         UserSetLastError(ExitCode);
178 
179     return (ExitCode == ERROR_SUCCESS);
180 }
181 
182 /*
183  * @implemented
184  */
185 BOOL WINAPI
ExitWindowsEx(UINT uFlags,DWORD dwReserved)186 ExitWindowsEx(UINT uFlags,
187               DWORD dwReserved)
188 {
189     /*
190      * FIXME:
191      * 1- Calling the Exit worker must be done under certain conditions.
192      *    We may also need to warn the user if there are other people logged
193      *    on this computer (see http://pve.proxmox.com/wiki/Windows_2003_guest_best_practices )
194      * 2- Call SrvRecordShutdownReason.
195      */
196 
197     return ExitWindowsWorker(uFlags, dwReserved, FALSE);
198 
199     /* FIXME: Call SrvRecordShutdownReason if we failed */
200 }
201 
202 /*
203  * @implemented
204  */
205 BOOL
206 WINAPI
EndTask(HWND hWnd,BOOL fShutDown,BOOL fForce)207 EndTask(HWND hWnd,
208         BOOL fShutDown,
209         BOOL fForce)
210 {
211     USER_API_MESSAGE ApiMessage;
212     PUSER_END_TASK EndTaskRequest = &ApiMessage.Data.EndTaskRequest;
213 
214     UNREFERENCED_PARAMETER(fShutDown);
215 
216     // EndTaskRequest->LastError = ERROR_SUCCESS;
217     EndTaskRequest->WndHandle = hWnd;
218     EndTaskRequest->Force = fForce;
219 
220     CsrClientCallServer((PCSR_API_MESSAGE)&ApiMessage,
221                         NULL,
222                         CSR_CREATE_API_NUMBER(USERSRV_SERVERDLL_INDEX, UserpEndTask),
223                         sizeof(*EndTaskRequest));
224     if (!NT_SUCCESS(ApiMessage.Status))
225     {
226         UserSetLastNTError(ApiMessage.Status);
227         return FALSE;
228     }
229 
230     if (EndTaskRequest->LastError != ERROR_SUCCESS)
231         UserSetLastError(EndTaskRequest->LastError);
232 
233     return EndTaskRequest->Success;
234 }
235 
236 /* EOF */
237