xref: /reactos/win32ss/user/winsrv/usersrv/shutdown.c (revision 40462c92)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS User API Server DLL
4  * FILE:            win32ss/user/winsrv/usersrv/shutdown.c
5  * PURPOSE:         Logout/shutdown
6  * PROGRAMMERS:
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "usersrv.h"
12 
13 #include <commctrl.h>
14 #include <psapi.h>
15 
16 #include "resource.h"
17 
18 #define NDEBUG
19 #include <debug.h>
20 
21 /* GLOBALS ********************************************************************/
22 
23 // Those flags (that are used for CsrProcess->ShutdownFlags) are named
24 // in accordance to the only public one: SHUTDOWN_NORETRY used for the
25 // SetProcessShutdownParameters API.
26 #if !defined(SHUTDOWN_SYSTEMCONTEXT) && !defined(SHUTDOWN_OTHERCONTEXT)
27 #define SHUTDOWN_SYSTEMCONTEXT  CsrShutdownSystem
28 #define SHUTDOWN_OTHERCONTEXT   CsrShutdownOther
29 #endif
30 
31 // The DPRINTs that need to really be removed as soon as everything works.
32 #define MY_DPRINT  DPRINT1
33 #define MY_DPRINT2 DPRINT
34 
35 typedef struct tagNOTIFY_CONTEXT
36 {
37     UINT Msg;
38     WPARAM wParam;
39     LPARAM lParam;
40     // HDESK Desktop;
41     // HDESK OldDesktop;
42     DWORD StartTime;
43     DWORD QueryResult;
44     HWND Dlg;
45     DWORD EndNowResult;
46     BOOL ShowUI;
47     HANDLE UIThread;
48     HWND WndClient;
49     PSHUTDOWN_SETTINGS ShutdownSettings;
50 } NOTIFY_CONTEXT, *PNOTIFY_CONTEXT;
51 
52 #define QUERY_RESULT_ABORT    0
53 #define QUERY_RESULT_CONTINUE 1
54 #define QUERY_RESULT_TIMEOUT  2
55 #define QUERY_RESULT_ERROR    3
56 #define QUERY_RESULT_FORCE    4
57 
58 typedef void (WINAPI *INITCOMMONCONTROLS_PROC)(void);
59 
60 typedef struct tagMESSAGE_CONTEXT
61 {
62     HWND Wnd;
63     UINT Msg;
64     WPARAM wParam;
65     LPARAM lParam;
66     DWORD Timeout;
67 } MESSAGE_CONTEXT, *PMESSAGE_CONTEXT;
68 
69 
70 /* FUNCTIONS ******************************************************************/
71 
72 static HMODULE hComCtl32Lib = NULL;
73 
74 static VOID
75 CallInitCommonControls(VOID)
76 {
77     static BOOL Initialized = FALSE;
78     INITCOMMONCONTROLS_PROC InitProc;
79 
80     if (Initialized) return;
81 
82     hComCtl32Lib = LoadLibraryW(L"COMCTL32.DLL");
83     if (hComCtl32Lib == NULL) return;
84 
85     InitProc = (INITCOMMONCONTROLS_PROC)GetProcAddress(hComCtl32Lib, "InitCommonControls");
86     if (InitProc == NULL) return;
87 
88     (*InitProc)();
89 
90     Initialized = TRUE;
91 }
92 
93 static VOID FASTCALL
94 UpdateProgressBar(HWND ProgressBar, PNOTIFY_CONTEXT NotifyContext)
95 {
96     DWORD Passed;
97 
98     Passed = GetTickCount() - NotifyContext->StartTime;
99     Passed -= NotifyContext->ShutdownSettings->HungAppTimeout;
100     if (NotifyContext->ShutdownSettings->WaitToKillAppTimeout < Passed)
101     {
102         Passed = NotifyContext->ShutdownSettings->WaitToKillAppTimeout;
103     }
104     SendMessageW(ProgressBar, PBM_SETPOS, Passed / 2, 0);
105 }
106 
107 static INT_PTR CALLBACK
108 EndNowDlgProc(HWND Dlg, UINT Msg, WPARAM wParam, LPARAM lParam)
109 {
110     INT_PTR Result;
111     PNOTIFY_CONTEXT NotifyContext;
112     HWND ProgressBar;
113     DWORD TitleLength;
114     int Len;
115     LPWSTR Title;
116 
117     switch(Msg)
118     {
119     case WM_INITDIALOG:
120         NotifyContext = (PNOTIFY_CONTEXT)lParam;
121         NotifyContext->EndNowResult = QUERY_RESULT_ABORT;
122         SetWindowLongPtrW(Dlg, DWLP_USER, (LONG_PTR)lParam);
123         TitleLength = SendMessageW(NotifyContext->WndClient, WM_GETTEXTLENGTH,
124                                    0, 0) +
125                       GetWindowTextLengthW(Dlg);
126         Title = HeapAlloc(UserServerHeap, 0, (TitleLength + 1) * sizeof(WCHAR));
127         if (Title)
128         {
129             Len = GetWindowTextW(Dlg, Title, TitleLength + 1);
130             SendMessageW(NotifyContext->WndClient, WM_GETTEXT,
131                          TitleLength + 1 - Len, (LPARAM)(Title + Len));
132             SetWindowTextW(Dlg, Title);
133             HeapFree(UserServerHeap, 0, Title);
134         }
135         ProgressBar = GetDlgItem(Dlg, IDC_PROGRESS);
136         SendMessageW(ProgressBar, PBM_SETRANGE32, 0,
137                      NotifyContext->ShutdownSettings->WaitToKillAppTimeout / 2);
138         UpdateProgressBar(ProgressBar, NotifyContext);
139         SetTimer(Dlg, 0, 200, NULL);
140         Result = FALSE;
141         break;
142 
143     case WM_TIMER:
144         NotifyContext = (PNOTIFY_CONTEXT)GetWindowLongPtrW(Dlg, DWLP_USER);
145         ProgressBar = GetDlgItem(Dlg, IDC_PROGRESS);
146         UpdateProgressBar(ProgressBar, NotifyContext);
147         Result = TRUE;
148         break;
149 
150     case WM_COMMAND:
151         if (BN_CLICKED == HIWORD(wParam) && IDC_END_NOW == LOWORD(wParam))
152         {
153             NotifyContext = (PNOTIFY_CONTEXT)GetWindowLongPtrW(Dlg, DWLP_USER);
154             NotifyContext->EndNowResult = QUERY_RESULT_FORCE;
155             MY_DPRINT("Closing progress dlg by hand\n");
156             SendMessageW(Dlg, WM_CLOSE, 0, 0);
157             Result = TRUE;
158         }
159         else
160         {
161             Result = FALSE;
162         }
163         break;
164 
165     case WM_CLOSE:
166         MY_DPRINT("WM_CLOSE\n");
167         DestroyWindow(Dlg);
168         Result = TRUE;
169         break;
170 
171     case WM_DESTROY:
172         MY_DPRINT("WM_DESTROY\n");
173         NotifyContext = (PNOTIFY_CONTEXT)GetWindowLongPtrW(Dlg, DWLP_USER);
174         NotifyContext->Dlg = NULL;
175         KillTimer(Dlg, 0);
176         PostQuitMessage(NotifyContext->EndNowResult);
177         Result = TRUE;
178         break;
179 
180     default:
181         Result = FALSE;
182         break;
183     }
184 
185     return Result;
186 }
187 
188 static DWORD WINAPI
189 EndNowThreadProc(LPVOID Parameter)
190 {
191     PNOTIFY_CONTEXT NotifyContext = (PNOTIFY_CONTEXT)Parameter;
192     MSG Msg;
193 
194 #if 0
195     SetThreadDesktop(NotifyContext->Desktop);
196     SwitchDesktop(NotifyContext->Desktop);
197 #else
198     /* For now show the end task dialog in the active desktop */
199     NtUserSetInformationThread(NtCurrentThread(),
200                                UserThreadUseActiveDesktop,
201                                NULL,
202                                0);
203 #endif
204 
205     CallInitCommonControls();
206     NotifyContext->Dlg = CreateDialogParam(UserServerDllInstance,
207                                            MAKEINTRESOURCE(IDD_END_NOW), NULL,
208                                            EndNowDlgProc, (LPARAM)NotifyContext);
209     if (NotifyContext->Dlg == NULL)
210         return 0;
211 
212     ShowWindow(NotifyContext->Dlg, SW_SHOWNORMAL);
213 
214     while (GetMessageW(&Msg, NULL, 0, 0))
215     {
216         if (!IsDialogMessage(NotifyContext->Dlg, &Msg))
217         {
218             TranslateMessage(&Msg);
219             DispatchMessageW(&Msg);
220         }
221     }
222 
223     return Msg.wParam;
224 }
225 
226 static DWORD WINAPI
227 SendClientShutdown(LPVOID Parameter)
228 {
229     PMESSAGE_CONTEXT Context = (PMESSAGE_CONTEXT)Parameter;
230     DWORD_PTR Result;
231 
232     /* If the shutdown is aborted, just notify the process, there is no need to wait */
233     if ((Context->wParam & (MCS_QUERYENDSESSION | MCS_ENDSESSION)) == 0)
234     {
235         DPRINT("Called WM_CLIENTSHUTDOWN with wParam == 0 ...\n");
236         SendNotifyMessageW(Context->Wnd, WM_CLIENTSHUTDOWN,
237                            Context->wParam, Context->lParam);
238         return QUERY_RESULT_CONTINUE;
239     }
240 
241     if (SendMessageTimeoutW(Context->Wnd, WM_CLIENTSHUTDOWN,
242                             Context->wParam, Context->lParam,
243                             SMTO_NORMAL, Context->Timeout, &Result))
244     {
245         DWORD Ret;
246 
247         if (Context->wParam & MCS_QUERYENDSESSION)
248         {
249             /* WM_QUERYENDSESSION case */
250             switch (Result)
251             {
252                 case MCSR_DONOTSHUTDOWN:
253                     Ret = QUERY_RESULT_ABORT;
254                     break;
255 
256                 case MCSR_GOODFORSHUTDOWN:
257                 case MCSR_SHUTDOWNFINISHED:
258                 default:
259                     Ret = QUERY_RESULT_CONTINUE;
260             }
261         }
262         else
263         {
264             /* WM_ENDSESSION case */
265             Ret = QUERY_RESULT_CONTINUE;
266         }
267 
268         DPRINT("SendClientShutdown -- Return == %s\n",
269                   Ret == QUERY_RESULT_CONTINUE ? "Continue" : "Abort");
270         return Ret;
271     }
272 
273     DPRINT1("SendClientShutdown -- Error == %s\n",
274               GetLastError() == 0 ? "Timeout" : "error");
275 
276     return (GetLastError() == 0 ? QUERY_RESULT_TIMEOUT : QUERY_RESULT_ERROR);
277 }
278 
279 static BOOL
280 NotifyTopLevelWindow(HWND Wnd, PNOTIFY_CONTEXT NotifyContext)
281 {
282     MESSAGE_CONTEXT MessageContext;
283     DWORD Now, Passed;
284     DWORD Timeout, WaitStatus;
285     HANDLE MessageThread;
286     HANDLE Threads[2];
287 
288     SetForegroundWindow(Wnd);
289 
290     Now = GetTickCount();
291     if (NotifyContext->StartTime == 0)
292         NotifyContext->StartTime = Now;
293 
294     /*
295      * Note: Passed is computed correctly even when GetTickCount()
296      * wraps due to unsigned arithmetic.
297      */
298     Passed = Now - NotifyContext->StartTime;
299     MessageContext.Wnd = Wnd;
300     MessageContext.Msg = NotifyContext->Msg;
301     MessageContext.wParam = NotifyContext->wParam;
302     MessageContext.lParam = NotifyContext->lParam;
303     MessageContext.Timeout = NotifyContext->ShutdownSettings->HungAppTimeout;
304     if (!NotifyContext->ShutdownSettings->AutoEndTasks)
305     {
306         MessageContext.Timeout += NotifyContext->ShutdownSettings->WaitToKillAppTimeout;
307     }
308     if (Passed < MessageContext.Timeout)
309     {
310         MessageContext.Timeout -= Passed;
311         MessageThread = CreateThread(NULL, 0, SendClientShutdown,
312                                      (LPVOID)&MessageContext, 0, NULL);
313         if (MessageThread == NULL)
314         {
315             NotifyContext->QueryResult = QUERY_RESULT_ERROR;
316             return FALSE;
317         }
318         Timeout = NotifyContext->ShutdownSettings->HungAppTimeout;
319         if (Passed < Timeout)
320         {
321             Timeout -= Passed;
322             WaitStatus = WaitForSingleObjectEx(MessageThread, Timeout, FALSE);
323         }
324         else
325         {
326             WaitStatus = WAIT_TIMEOUT;
327         }
328         if (WAIT_TIMEOUT == WaitStatus)
329         {
330             NotifyContext->WndClient = Wnd;
331             if (NotifyContext->UIThread == NULL && NotifyContext->ShowUI)
332             {
333                 NotifyContext->UIThread = CreateThread(NULL, 0,
334                                                        EndNowThreadProc,
335                                                        (LPVOID)NotifyContext,
336                                                        0, NULL);
337             }
338             Threads[0] = MessageThread;
339             Threads[1] = NotifyContext->UIThread;
340             WaitStatus = WaitForMultipleObjectsEx(NotifyContext->UIThread == NULL ?
341                                                   1 : 2,
342                                                   Threads, FALSE, INFINITE,
343                                                   FALSE);
344             if (WaitStatus == WAIT_OBJECT_0)
345             {
346                 if (!GetExitCodeThread(MessageThread, &NotifyContext->QueryResult))
347                 {
348                     NotifyContext->QueryResult = QUERY_RESULT_ERROR;
349                 }
350             }
351             else if (WaitStatus == WAIT_OBJECT_0 + 1)
352             {
353                 if (!GetExitCodeThread(NotifyContext->UIThread,
354                                        &NotifyContext->QueryResult))
355                 {
356                     NotifyContext->QueryResult = QUERY_RESULT_ERROR;
357                 }
358             }
359             else
360             {
361                 NotifyContext->QueryResult = QUERY_RESULT_ERROR;
362             }
363             if (WaitStatus != WAIT_OBJECT_0)
364             {
365                 TerminateThread(MessageThread, QUERY_RESULT_TIMEOUT);
366             }
367         }
368         else if (WaitStatus == WAIT_OBJECT_0)
369         {
370             if (!GetExitCodeThread(MessageThread,
371                                    &NotifyContext->QueryResult))
372             {
373                 NotifyContext->QueryResult = QUERY_RESULT_ERROR;
374             }
375         }
376         else
377         {
378             NotifyContext->QueryResult = QUERY_RESULT_ERROR;
379         }
380         CloseHandle(MessageThread);
381     }
382     else
383     {
384         NotifyContext->QueryResult = QUERY_RESULT_TIMEOUT;
385     }
386 
387     DPRINT("NotifyContext->QueryResult == %d\n", NotifyContext->QueryResult);
388     return (NotifyContext->QueryResult == QUERY_RESULT_CONTINUE);
389 }
390 
391 static BOOLEAN
392 IsConsoleMode(VOID)
393 {
394     return (BOOLEAN)NtUserCallNoParam(NOPARAM_ROUTINE_ISCONSOLEMODE);
395 }
396 
397 /************************************************/
398 
399 
400 static BOOL
401 ThreadShutdownNotify(IN PCSR_THREAD CsrThread,
402                      IN ULONG Flags,
403                      IN ULONG Flags2,
404                      IN PNOTIFY_CONTEXT Context)
405 {
406     HWND TopWnd = NULL;
407 
408     EnumThreadWindows(HandleToUlong(CsrThread->ClientId.UniqueThread),
409                       FindTopLevelWnd, (LPARAM)&TopWnd);
410     if (TopWnd)
411     {
412         HWND hWndOwner;
413 
414         /*** FOR TESTING PURPOSES ONLY!! ***/
415         HWND tmpWnd;
416         tmpWnd = TopWnd;
417         /***********************************/
418 
419         while ((hWndOwner = GetWindow(TopWnd, GW_OWNER)) != NULL)
420         {
421             MY_DPRINT("GetWindow(TopWnd, GW_OWNER) not returned NULL...\n");
422             TopWnd = hWndOwner;
423         }
424         if (TopWnd != tmpWnd) MY_DPRINT("(TopWnd = %x) != (tmpWnd = %x)\n", TopWnd, tmpWnd);
425     }
426     else
427     {
428         return FALSE;
429     }
430 
431     Context->wParam = Flags2;
432     Context->lParam = (0 != (Flags & EWX_CALLER_WINLOGON_LOGOFF) ?
433                        ENDSESSION_LOGOFF : 0);
434 
435     Context->StartTime = 0;
436     Context->UIThread = NULL;
437     Context->ShowUI = !IsConsoleMode() && (Flags2 & (MCS_QUERYENDSESSION | MCS_ENDSESSION));
438     Context->Dlg = NULL;
439 
440 #if 0 // Obviously, switching desktops like that from within WINSRV doesn't work...
441     {
442     BOOL Success;
443     Context->OldDesktop = GetThreadDesktop(GetCurrentThreadId());
444     // Context->Desktop    = GetThreadDesktop(HandleToUlong(CsrThread->ClientId.UniqueThread));
445     Context->Desktop    = GetThreadDesktop(GetWindowThreadProcessId(TopWnd, NULL));
446     MY_DPRINT("Last error = %d\n", GetLastError());
447     MY_DPRINT("Before switching to desktop 0x%x\n", Context->Desktop);
448     Success = SwitchDesktop(Context->Desktop);
449     MY_DPRINT("After switching to desktop (Success = %s ; last error = %d); going to notify top-level...\n",
450             Success ? "TRUE" : "FALSE", GetLastError());
451     }
452 #endif
453 
454     NotifyTopLevelWindow(TopWnd, Context);
455 
456 /******************************************************************************/
457 #if 1
458     if (Context->UIThread)
459     {
460         MY_DPRINT("Context->UIThread != NULL\n");
461         if (Context->Dlg)
462         {
463             MY_DPRINT("Sending WM_CLOSE because Dlg is != NULL\n");
464             SendMessageW(Context->Dlg, WM_CLOSE, 0, 0);
465         }
466         else
467         {
468             MY_DPRINT("Terminating UIThread thread with QUERY_RESULT_ERROR\n");
469             TerminateThread(Context->UIThread, QUERY_RESULT_ERROR);
470         }
471         CloseHandle(Context->UIThread);
472         /**/Context->UIThread = NULL;/**/
473         /**/Context->Dlg = NULL;/**/
474     }
475 #endif
476 /******************************************************************************/
477 
478 #if 0
479     MY_DPRINT("Switch back to old desktop 0x%x\n", Context->OldDesktop);
480     SwitchDesktop(Context->OldDesktop);
481     MY_DPRINT("Switched back ok\n");
482 #endif
483 
484     return TRUE;
485 }
486 
487 static ULONG
488 NotifyUserProcessForShutdown(PCSR_PROCESS CsrProcess,
489                              PSHUTDOWN_SETTINGS ShutdownSettings,
490                              UINT Flags)
491 {
492     DWORD QueryResult = QUERY_RESULT_CONTINUE;
493     PCSR_PROCESS Process;
494     PCSR_THREAD Thread;
495     PLIST_ENTRY NextEntry;
496     NOTIFY_CONTEXT Context;
497     BOOL FoundWindows = FALSE;
498 
499     /* In case we make a forced shutdown, just kill the process */
500     if (Flags & EWX_FORCE)
501         return CsrShutdownCsrProcess;
502 
503     Context.ShutdownSettings = ShutdownSettings;
504     Context.QueryResult = QUERY_RESULT_CONTINUE; // We continue shutdown by default.
505 
506     /* Lock the process */
507     CsrLockProcessByClientId(CsrProcess->ClientId.UniqueProcess, &Process);
508 
509     /* Send first the QUERYENDSESSION messages to all the threads of the process */
510     MY_DPRINT2("Sending the QUERYENDSESSION messages...\n");
511 
512     NextEntry = CsrProcess->ThreadList.Flink;
513     while (NextEntry != &CsrProcess->ThreadList)
514     {
515         /* Get the current thread entry */
516         Thread = CONTAINING_RECORD(NextEntry, CSR_THREAD, Link);
517 
518         /* Move to the next entry */
519         NextEntry = NextEntry->Flink;
520 
521         /* If the thread is being terminated, just skip it */
522         if (Thread->Flags & CsrThreadTerminated) continue;
523 
524         /* Reference the thread and temporarily unlock the process */
525         CsrReferenceThread(Thread);
526         CsrUnlockProcess(Process);
527 
528         Context.QueryResult = QUERY_RESULT_CONTINUE;
529         if (ThreadShutdownNotify(Thread, Flags, MCS_QUERYENDSESSION, &Context))
530         {
531             FoundWindows = TRUE;
532         }
533 
534         /* Lock the process again and dereference the thread */
535         CsrLockProcessByClientId(CsrProcess->ClientId.UniqueProcess, &Process);
536         CsrDereferenceThread(Thread);
537 
538         // FIXME: Analyze Context.QueryResult !!
539         /**/if (Context.QueryResult == QUERY_RESULT_ABORT) goto Quit;/**/
540     }
541 
542     if (!FoundWindows)
543     {
544         /* We looped all threads but no top level window was found so we didn't send any message */
545         /* Let the console server run the generic process shutdown handler */
546         CsrUnlockProcess(Process);
547         return CsrShutdownNonCsrProcess;
548     }
549 
550     QueryResult = Context.QueryResult;
551     MY_DPRINT2("QueryResult = %s\n",
552                QueryResult == QUERY_RESULT_ABORT ? "Abort" : "Continue");
553 
554     /* Now send the ENDSESSION messages to the threads */
555     MY_DPRINT2("Now sending the ENDSESSION messages...\n");
556 
557     NextEntry = CsrProcess->ThreadList.Flink;
558     while (NextEntry != &CsrProcess->ThreadList)
559     {
560         /* Get the current thread entry */
561         Thread = CONTAINING_RECORD(NextEntry, CSR_THREAD, Link);
562 
563         /* Move to the next entry */
564         NextEntry = NextEntry->Flink;
565 
566         /* If the thread is being terminated, just skip it */
567         if (Thread->Flags & CsrThreadTerminated) continue;
568 
569         /* Reference the thread and temporarily unlock the process */
570         CsrReferenceThread(Thread);
571         CsrUnlockProcess(Process);
572 
573         Context.QueryResult = QUERY_RESULT_CONTINUE;
574         ThreadShutdownNotify(Thread, Flags,
575                              (QUERY_RESULT_ABORT != QueryResult) ? MCS_ENDSESSION : 0,
576                              &Context);
577 
578         /* Lock the process again and dereference the thread */
579         CsrLockProcessByClientId(CsrProcess->ClientId.UniqueProcess, &Process);
580         CsrDereferenceThread(Thread);
581     }
582 
583 Quit:
584     /* Unlock the process */
585     CsrUnlockProcess(Process);
586 
587 #if 0
588     if (Context.UIThread)
589     {
590         if (Context.Dlg)
591         {
592             SendMessageW(Context.Dlg, WM_CLOSE, 0, 0);
593         }
594         else
595         {
596             TerminateThread(Context.UIThread, QUERY_RESULT_ERROR);
597         }
598         CloseHandle(Context.UIThread);
599     }
600 #endif
601 
602     /* Kill the process unless we abort shutdown */
603     if (QueryResult == QUERY_RESULT_ABORT)
604         return CsrShutdownCancelled;
605 
606     return CsrShutdownCsrProcess;
607 }
608 
609 static NTSTATUS FASTCALL
610 UserExitReactOS(PCSR_THREAD CsrThread, UINT Flags)
611 {
612     NTSTATUS Status;
613     LUID CallerLuid;
614 
615     DWORD ProcessId = HandleToUlong(CsrThread->ClientId.UniqueProcess);
616     DWORD ThreadId  = HandleToUlong(CsrThread->ClientId.UniqueThread);
617 
618     DPRINT1("SrvExitWindowsEx(ClientId: %lx.%lx, Flags: 0x%x)\n",
619             ProcessId, ThreadId, Flags);
620 
621     /*
622      * Check for flags validity
623      */
624 
625     if (Flags & EWX_CALLER_WINLOGON)
626     {
627         /* Only Winlogon can call this */
628         if (ProcessId != LogonProcessId)
629         {
630             DPRINT1("SrvExitWindowsEx call not from Winlogon\n");
631             return STATUS_ACCESS_DENIED;
632         }
633     }
634 
635     /* Implicitely add the shutdown flag when we poweroff or reboot */
636     if (Flags & (EWX_POWEROFF | EWX_REBOOT))
637         Flags |= EWX_SHUTDOWN;
638 
639     /*
640      * Impersonate and retrieve the caller's LUID so that
641      * we can only shutdown processes in its context.
642      */
643     if (!CsrImpersonateClient(NULL))
644         return STATUS_BAD_IMPERSONATION_LEVEL;
645 
646     Status = CsrGetProcessLuid(NULL, &CallerLuid);
647     if (!NT_SUCCESS(Status))
648     {
649         DPRINT1("Unable to get caller LUID, Status = 0x%08x\n", Status);
650         goto Quit;
651     }
652 
653     DPRINT("Caller LUID is: %lx.%lx\n", CallerLuid.HighPart, CallerLuid.LowPart);
654 
655     /* Shutdown loop */
656     while (TRUE)
657     {
658         /* Notify Win32k and potentially Winlogon of the shutdown */
659         Status = NtUserSetInformationThread(CsrThread->ThreadHandle,
660                                             UserThreadInitiateShutdown,
661                                             &Flags, sizeof(Flags));
662         DPRINT("Win32k says: %lx\n", Status);
663         switch (Status)
664         {
665             /* We cannot wait here, the caller should start a new thread */
666             case STATUS_CANT_WAIT:
667                 DPRINT1("NtUserSetInformationThread returned STATUS_CANT_WAIT\n");
668                 goto Quit;
669 
670             /* Shutdown is in progress */
671             case STATUS_PENDING:
672                 DPRINT1("NtUserSetInformationThread returned STATUS_PENDING\n");
673                 goto Quit;
674 
675             /* Abort */
676             case STATUS_RETRY:
677             {
678                 DPRINT1("NtUserSetInformationThread returned STATUS_RETRY\n");
679                 UNIMPLEMENTED;
680                 continue;
681             }
682 
683             default:
684             {
685                 if (!NT_SUCCESS(Status))
686                 {
687                     // FIXME: Use some UserSetLastNTError or SetLastNtError
688                     // that we have defined for user32 or win32k usage only...
689                     SetLastError(RtlNtStatusToDosError(Status));
690                     goto Quit;
691                 }
692             }
693         }
694 
695         /* All good */
696         break;
697     }
698 
699     /*
700      * OK we can continue. Now magic happens:
701      *
702      * Terminate all Win32 processes, stop if we find one kicking
703      * and screaming it doesn't want to die.
704      *
705      * This function calls the ShutdownProcessCallback callback of
706      * each CSR server for each Win32 process.
707      */
708     Status = CsrShutdownProcesses(&CallerLuid, Flags);
709     if (!NT_SUCCESS(Status))
710     {
711         DPRINT1("Failed to shutdown processes, Status = 0x%08x\n", Status);
712     }
713 
714     // FIXME: If Status == STATUS_CANCELLED, call RecordShutdownReason
715 
716     /* Tell Win32k and potentially Winlogon that we're done */
717     NtUserSetInformationThread(CsrThread->ThreadHandle,
718                                UserThreadEndShutdown,
719                                &Status, sizeof(Status));
720 
721     DPRINT("SrvExitWindowsEx returned 0x%08x\n", Status);
722 
723 Quit:
724     /* We are done */
725     CsrRevertToSelf();
726     return Status;
727 }
728 
729 
730 ULONG
731 NTAPI
732 UserClientShutdown(IN PCSR_PROCESS CsrProcess,
733                    IN ULONG Flags,
734                    IN BOOLEAN FirstPhase)
735 {
736     ULONG result;
737 
738     DPRINT("UserClientShutdown(0x%p, 0x%x, %s) - [0x%x, 0x%x], ShutdownFlags: %lu\n",
739             CsrProcess, Flags, FirstPhase ? "FirstPhase" : "LastPhase",
740             CsrProcess->ClientId.UniqueProcess, CsrProcess->ClientId.UniqueThread,
741             CsrProcess->ShutdownFlags);
742 
743     /*
744      * Check for process validity
745      */
746 
747     /* Do not kill system processes when a user is logging off */
748     if ((Flags & EWX_SHUTDOWN) == EWX_LOGOFF &&
749         (CsrProcess->ShutdownFlags & (SHUTDOWN_OTHERCONTEXT | SHUTDOWN_SYSTEMCONTEXT)))
750     {
751         DPRINT("Do not kill a system process in a logoff request!\n");
752         return CsrShutdownNonCsrProcess;
753     }
754 
755     /* Do not kill Winlogon */
756     if (CsrProcess->ClientId.UniqueProcess == UlongToHandle(LogonProcessId))
757     {
758         DPRINT("Not killing Winlogon; CsrProcess->ShutdownFlags = %lu\n",
759                CsrProcess->ShutdownFlags);
760 
761         /* Returning CsrShutdownCsrProcess means that we handled this process by doing nothing */
762         /* This will mark winlogon as processed so consrv won't be notified again for it */
763         CsrDereferenceProcess(CsrProcess);
764         return CsrShutdownCsrProcess;
765     }
766 
767     /* Notify the process for shutdown if needed */
768     result = NotifyUserProcessForShutdown(CsrProcess, &ShutdownSettings, Flags);
769     if (result == CsrShutdownCancelled || result == CsrShutdownNonCsrProcess)
770     {
771         if (result == CsrShutdownCancelled)
772             DPRINT1("Process 0x%x aborted shutdown\n", CsrProcess->ClientId.UniqueProcess);
773         return result;
774     }
775 
776     /* Terminate this process */
777 #if DBG
778     {
779         WCHAR buffer[MAX_PATH];
780         if (!GetProcessImageFileNameW(CsrProcess->ProcessHandle, buffer, MAX_PATH))
781         {
782             DPRINT1("Terminating process %x\n", CsrProcess->ClientId.UniqueProcess);
783         }
784         else
785         {
786             DPRINT1("Terminating process %x (%S)\n", CsrProcess->ClientId.UniqueProcess, buffer);
787         }
788     }
789 #endif
790     NtTerminateProcess(CsrProcess->ProcessHandle, 0);
791 
792     WaitForSingleObject(CsrProcess->ProcessHandle, ShutdownSettings.ProcessTerminateTimeout);
793 
794     /* We are done */
795     CsrDereferenceProcess(CsrProcess);
796     return CsrShutdownCsrProcess;
797 }
798 
799 
800 /* PUBLIC SERVER APIS *********************************************************/
801 
802 /* API_NUMBER: UserpExitWindowsEx */
803 CSR_API(SrvExitWindowsEx)
804 {
805     NTSTATUS Status;
806     PUSER_EXIT_REACTOS ExitReactOSRequest = &((PUSER_API_MESSAGE)ApiMessage)->Data.ExitReactOSRequest;
807 
808     Status = NtUserSetInformationThread(NtCurrentThread(),
809                                         UserThreadUseActiveDesktop,
810                                         NULL,
811                                         0);
812     if (!NT_SUCCESS(Status))
813     {
814         DPRINT1("Failed to set thread desktop!\n");
815         return Status;
816     }
817 
818     Status = UserExitReactOS(CsrGetClientThread(), ExitReactOSRequest->Flags);
819     ExitReactOSRequest->Success   = NT_SUCCESS(Status);
820     ExitReactOSRequest->LastError = GetLastError();
821 
822     NtUserSetInformationThread(NtCurrentThread(), UserThreadRestoreDesktop, NULL, 0);
823 
824     return Status;
825 }
826 
827 /* API_NUMBER: UserpEndTask */
828 CSR_API(SrvEndTask)
829 {
830     PUSER_END_TASK EndTaskRequest = &((PUSER_API_MESSAGE)ApiMessage)->Data.EndTaskRequest;
831     NTSTATUS Status;
832 
833     // FIXME: This is HACK-plemented!!
834     DPRINT1("SrvEndTask is HACKPLEMENTED!!\n");
835 
836     Status = NtUserSetInformationThread(NtCurrentThread(),
837                                         UserThreadUseActiveDesktop,
838                                         NULL,
839                                         0);
840     if (!NT_SUCCESS(Status))
841     {
842         DPRINT1("Failed to set thread desktop!\n");
843         return Status;
844     }
845 
846     SendMessageW(EndTaskRequest->WndHandle, WM_CLOSE, 0, 0);
847     // PostMessageW(EndTaskRequest->WndHandle, WM_CLOSE, 0, 0);
848 
849     if (IsWindow(EndTaskRequest->WndHandle))
850     {
851         if (EndTaskRequest->Force)
852         {
853             EndTaskRequest->Success   = DestroyWindow(EndTaskRequest->WndHandle);
854             EndTaskRequest->LastError = GetLastError();
855         }
856         else
857         {
858             EndTaskRequest->Success = FALSE;
859         }
860     }
861     else
862     {
863         EndTaskRequest->Success   = TRUE;
864         EndTaskRequest->LastError = ERROR_SUCCESS;
865     }
866 
867     NtUserSetInformationThread(NtCurrentThread(), UserThreadRestoreDesktop, NULL, 0);
868 
869     return STATUS_SUCCESS;
870 }
871 
872 /* API_NUMBER: UserpRecordShutdownReason */
873 CSR_API(SrvRecordShutdownReason)
874 {
875     DPRINT1("%s not yet implemented\n", __FUNCTION__);
876     return STATUS_NOT_IMPLEMENTED;
877 }
878 
879 /* EOF */
880