Windows2000/private/ntos/w32/ntuser/server/exitwin.c
2020-09-30 17:12:32 +02:00

1915 lines
63 KiB
C

/*** Module Header *******\
* Module Name: exitwin.c
* Copyright (c) 1985 - 1999, Microsoft Corporation
* NT: Logoff user
* DOS: Exit windows
* History:
* 07-23-92 ScottLu Created.
*/
#include "precomp.h"
#pragma hdrstop
#define BEGIN_LPC_RECV(API) \
P##API##MSG a = (P##API##MSG)&m->u.ApiMessageData; \
PCSR_THREAD pcsrt; \
PTEB Teb = NtCurrentTeb(); \
NTSTATUS Status = STATUS_SUCCESS; \
UNREFERENCED_PARAMETER(ReplyStatus); \
\
Teb->LastErrorValue = 0; \
pcsrt = CSR_SERVER_QUERYCLIENTTHREAD();
#define END_LPC_RECV() \
a->dwLastError = Teb->LastErrorValue; \
return Status;
#define CCHMSGMAX 256
#define CCHBODYMAX 512
#define CSR_THREAD_SHUTDOWNSKIP 0x00000008
BOOL WowExitTask(PCSR_THREAD pcsrt);
NTSTATUS UserClientShutdown(PCSR_PROCESS pcsrp, ULONG dwFlags, BOOLEAN fFirstPass);
NTSTATUS _ExitWindowsEx(PCSR_THREAD pcsrt, UINT dwFlags, DWORD dwReserved)
/*
* Determines whether shutdown is allowed, and if so calls CSR to start shutting down processes.
* If this succeeds all the way through, tell winlogon so it'll either logoff or reboot the system.
* Shuts down the processes in the caller's sid.
* History
* 07-23-92 ScottLu Created.
*/
{
LUID luidCaller;
NTSTATUS Status = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(dwReserved);
if ((dwFlags & EWX_REBOOT) || (dwFlags & EWX_POWEROFF)) {
dwFlags |= EWX_SHUTDOWN;
}
// Only winlogon gets to set the high flags:
if ( ( dwFlags & ( ~ ( EWX_VALID ) ) ) != 0 )
{
if ( HandleToUlong(pcsrt->ClientId.UniqueProcess) != gIdLogon )
{
KdPrint(( "Process %x tried to call ExitWindowsEx with flags %x\n", pcsrt->ClientId.UniqueProcess, dwFlags ));
return STATUS_ACCESS_DENIED ;
}
}
/*
* Find out the callers sid. Only want to shutdown processes in the callers sid.
*/
if (!CsrImpersonateClient(NULL)) {
return STATUS_BAD_IMPERSONATION_LEVEL;
}
Status = CsrGetProcessLuid(NULL, &luidCaller);
if (!NT_SUCCESS(Status)) {
CsrRevertToSelf();
return Status;
}
/*
* Loop until we can do the shutdown; if we cannot do it, we'll go to fastexit and bail.
*/
while (TRUE)
{
LARGE_INTEGER li;
Status = NtUserSetInformationThread(pcsrt->ThreadHandle, UserThreadInitiateShutdown, &dwFlags, sizeof(dwFlags));
switch (Status)
{
case STATUS_PENDING:
/*
* The logoff/shutdown is in progress and nothing
* more needs to be done.
*/
goto fastexit;
case STATUS_RETRY:
/*
* Another logoff/shutdown is in progress and we need
* to cancel it so we can do an override.
* if someone else is trying to cancel shutdown, exit
*/
EnterCrit();
li.QuadPart = 0;
if (NtWaitForSingleObject(gheventCancel, FALSE, &li) == WAIT_OBJECT_0) {
Status = STATUS_PENDING;
LeaveCrit();
goto fastexit;
}
/*
* If no one will set gheventCancelled, don't wait.
*/
if (gdwThreadEndSession == 0) {
LeaveCrit();
continue;
}
NtClearEvent(gheventCancelled);
NtSetEvent(gheventCancel, NULL);
LeaveCrit();
/*
* Wait for the other guy to be cancelled
*/
NtWaitForSingleObject(gheventCancelled, FALSE, NULL);
EnterCrit();
/*
* This signals that we are no longer trying to cancel a
* shutdown
*/
NtClearEvent(gheventCancel);
/*
* If someone managed to start a shutdown again, exit
* Can this happen? Let's assert to check it out.
*/
if (gdwThreadEndSession != 0) {
UserAssert(gdwThreadEndSession == 0);
Status = STATUS_PENDING;
LeaveCrit();
goto fastexit;
}
LeaveCrit();
continue;
case STATUS_CANT_WAIT:
/*
* There is no notify window and the calling thread has
* windows that prevent this request from succeeding.
* The client handles this by starting another thread
* to recall ExitWindowsEx.
*/
goto fastexit;
default:
if (!NT_SUCCESS(Status)) {
SetLastError(RtlNtStatusToDosError(Status));
goto fastexit;
}
}
break;
}
/*
* This thread is doing the shutdown
*/
EnterCrit();
UserAssert(gdwThreadEndSession == 0);
gdwThreadEndSession = HandleToUlong(pcsrt->ClientId.UniqueThread);
LeaveCrit();
/*
* Call csr to loop through the processes shutting them down.
*/
Status = CsrShutdownProcesses(&luidCaller, dwFlags);
/*
* Tell win32k.sys we're done.
*/
NtUserSetInformationThread(pcsrt->ThreadHandle, UserThreadEndShutdown, &Status, sizeof(Status));
EnterCrit();
gdwThreadEndSession = 0;
NtSetEvent(gheventCancelled, NULL);
LeaveCrit();
fastexit:
CsrRevertToSelf();
return Status;
}
NTSTATUS UserClientShutdown(PCSR_PROCESS pcsrp, ULONG dwFlags, BOOLEAN fFirstPass)
/*
* This gets called from CSR. If we recognize the application (i.e., it has a top level window), then send queryend/end session messages to it.
* Otherwise say we don't recognize it.
* 07-23-92 ScottLu Created.
*/
{
PLIST_ENTRY ListHead, ListNext;
PCSR_PROCESS Process;
PCSR_THREAD Thread;
USERTHREAD_SHUTDOWN_INFORMATION ShutdownInfo;
BOOL fNoMsgs;
BOOL fNoMsgsEver = TRUE;
BOOL Forced = FALSE;
BOOL bDoBlock;
BOOL fNoRetry;
DWORD cmd = 0, dwClientFlags;
NTSTATUS Status;
NTSTATUS TerminateStatus = STATUS_ACCESS_DENIED;
UINT cThreads;
BOOL fSendEndSession = FALSE;
#if DBG
DWORD dwLocalThreadEndSession = gdwThreadEndSession;
#endif
/*
* If this is a logoff and the process does not belong to the account doing the logoff and is not LocalSystem, do not send end-session messages.
* Console will notify the app of the logoff.
*/
if (!(dwFlags & EWX_SHUTDOWN) && (pcsrp->ShutdownFlags & SHUTDOWN_OTHERCONTEXT)) {
Status = SHUTDOWN_UNKNOWN_PROCESS;
goto CleanupAndExit;
}
/*
* Calculate whether to allow exit and force-exit this process before we unlock pcsrp.
*/
fNoRetry = (pcsrp->ShutdownFlags & SHUTDOWN_NORETRY) || (dwFlags & EWX_FORCE);
/*
* Setup flags for WM_CLIENTSHUTDOWN
* -Assume the process is going to OK the WM_QUERYENDSESSION (WMCS_EXIT)
* -NT's shutdown always starts with a logoff.
* -Shutdown or logoff? (WMCS_SHUTDOWN)
* -Should display dialog for hung apps? (WMCS_NODLGIFHUNG)
* -is this process in the context being logged off? (WMCS_CONTEXTLOGOFF)
*/
dwClientFlags = WMCS_EXIT | (fNoRetry ? WMCS_NORETRY : 0);
//}
/*
* Check the flags originally passed by the ExitWindows caller to see if we're really just logging off.
*/
if (!(dwFlags & (EWX_WINLOGON_OLD_REBOOT | EWX_WINLOGON_OLD_SHUTDOWN))) {
dwClientFlags |= WMCS_LOGOFF;
}
if (dwFlags & EWX_FORCEIFHUNG) {
dwClientFlags |= WMCS_NODLGIFHUNG;
}
if (!(pcsrp->ShutdownFlags & (SHUTDOWN_SYSTEMCONTEXT | SHUTDOWN_OTHERCONTEXT))) {
dwClientFlags |= WMCS_CONTEXTLOGOFF;
}
/*
* Lock the process while we walk the thread list.
* We know that the process is valid and therefore do not need to check the return status.
*/
CsrLockProcessByClientId(pcsrp->ClientId.UniqueProcess, &Process);
ShutdownInfo.StatusShutdown = SHUTDOWN_UNKNOWN_PROCESS;
/*
* Go through the thread list and mark them as not shutdown yet.
*/
ListHead = &pcsrp->ThreadList;
ListNext = ListHead->Flink;
while (ListNext != ListHead) {
Thread = CONTAINING_RECORD( ListNext, CSR_THREAD, Link );
Thread->Flags &= ~CSR_THREAD_SHUTDOWNSKIP;
ListNext = ListNext->Flink;
}
/*
* Perform the proper shutdown operation on each thread. Keep a count of the number of gui threads found.
*/
cThreads = 0;
ShutdownInfo.drdRestore.pdeskRestore = NULL;
ShutdownInfo.drdRestore.hdeskNew = NULL;
while (TRUE) {
ListNext = ListHead->Flink;
while (ListNext != ListHead) {
Thread = CONTAINING_RECORD( ListNext, CSR_THREAD, Link );
/*
* Skip the thread doing the shutdown. Assume that it's
* ready.
* gdwThreadEndSession shouldn't change while the shutdown
* is in progress; so this should be thread safe.
*/
UserAssert(gdwThreadEndSession == dwLocalThreadEndSession);
if (HandleToUlong(Thread->ClientId.UniqueThread) == gdwThreadEndSession) {
Thread->Flags |= CSR_THREAD_SHUTDOWNSKIP;
}
if (!(Thread->Flags & (CSR_THREAD_DESTROYED | CSR_THREAD_SHUTDOWNSKIP))) {
break;
}
ListNext = ListNext->Flink;
}
if (ListNext == ListHead)
break;
Thread->Flags |= CSR_THREAD_SHUTDOWNSKIP;
ShutdownInfo.dwFlags = dwClientFlags;
Status = NtUserQueryInformationThread(Thread->ThreadHandle, UserThreadShutdownInformation, &ShutdownInfo, sizeof(ShutdownInfo), NULL);
if (!NT_SUCCESS(Status))
continue;
if (ShutdownInfo.StatusShutdown == SHUTDOWN_UNKNOWN_PROCESS)
continue;
if (ShutdownInfo.StatusShutdown == SHUTDOWN_KNOWN_PROCESS) {
CsrUnlockProcess(Process);
Status = SHUTDOWN_KNOWN_PROCESS;
goto RestoreDesktop;
}
/*
* If this process is not in the account being logged off and it is not on the windowstation being logged off, don't send the end session messages.
*/
if (!(dwClientFlags & WMCS_CONTEXTLOGOFF) && (ShutdownInfo.hwndDesktop == NULL)) {
/*
* This process is not in the context being logged off. Do not terminate it and let console send an event to the process.
*/
ShutdownInfo.StatusShutdown = SHUTDOWN_UNKNOWN_PROCESS;
continue;
}
/*
* Shut down this process.
*/
cThreads++;
if (ISTS()) {
Forced = (dwFlags & EWX_FORCE);
fNoMsgs = (pcsrp->ShutdownFlags & SHUTDOWN_NORETRY) || !(ShutdownInfo.dwFlags & USER_THREAD_GUI);
fNoMsgsEver &= fNoMsgs;
if (Forced && (!(dwFlags & EWX_NONOTIFY) || (gSessionId != 0))) {
dwClientFlags &= ~WMCS_LOGOFF; // WinStation Reset or Shutdown. Don't do this for console session.
}
if (fNoMsgs || Forced) {
BoostHardError((ULONG_PTR)Thread->ClientId.UniqueProcess, BHE_FORCE);
}
bDoBlock = (fNoMsgs == FALSE);
} else {
if (fNoRetry || !(ShutdownInfo.dwFlags & USER_THREAD_GUI)) {
/*
* Dispose of any hard errors.
*/
BoostHardError((ULONG_PTR)Thread->ClientId.UniqueProcess, BHE_FORCE);
bDoBlock = FALSE;
} else {
bDoBlock = TRUE;
}
}
if (bDoBlock) {
CsrReferenceThread(Thread);
CsrUnlockProcess(Process);
/*
* There are problems in changing shutdown to send all the QUERYENDSESSIONs at once before doing any ENDSESSIONs, like Windows does.
* The whole machine needs to be modal if you do this.
* If it isn't modal, then you have this problem. Imagine app 1 and 2.
* 1 gets the queryendsession, no problem. 2 gets it and brings up a dialog.
* Now being a simple user, you decide you need to change the document in app 1.
* Now you switch back to app 2, hit ok, and everything goes away - including app 1 without saving its changes.
* Also, apps expect that once they've received the QUERYENDSESSION,
* they are not going to get anything else of any particular interest (unless it is a WM_ENDSESSION with FALSE) We had bugs pre 511 where apps were blowing up because of this.
* If this change is made, the entire system must be modal while this is going on. - ScottLu 6/30/94
*/
cmd = ThreadShutdownNotify(dwClientFlags | WMCS_QUERYEND, (ULONG_PTR)Thread, 0);
CsrLockProcessByClientId(pcsrp->ClientId.UniqueProcess, &Process);
CsrDereferenceThread(Thread);
/*
* If shutdown has been cancelled, let csr know about it.
*/
switch (cmd)
{
case TSN_USERSAYSCANCEL:
case TSN_APPSAYSNOTOK:
if (!Forced) {
dwClientFlags &= ~WMCS_EXIT;
}
/*
* Fall through.
*/
case TSN_APPSAYSOK:
fSendEndSession = TRUE;
break;
case TSN_USERSAYSKILL:
/*
* Since we cannot just kill one thread, the whole process
* is going down. Hence, there is no point on continuing
* checking other threads. Also, the user wants it killed
* so we won't waste any time sending more messages
*/
dwClientFlags |= WMCS_EXIT;
goto KillIt;
case TSN_NOWINDOW:
/*
* Did this process have a window?
* If this is the second pass we terminate the process even if it did
* not have any windows in case the app was just starting up.
* WOW hits this often when because it takes so long to start up.
* Logon (with WOW auto-starting) then logoff WOW won't die but will
* lock some files open so you can't logon next time.
*/
if (fFirstPass) {
cThreads--;
}
break;
}
}
}
/*
* If end session message need to be sent, do it now.
*/
if (fSendEndSession) {
/*
* Go through the thread list and mark them as not
* shutdown yet.
*/
ListNext = ListHead->Flink;
while (ListNext != ListHead) {
Thread = CONTAINING_RECORD( ListNext, CSR_THREAD, Link );
Thread->Flags &= ~CSR_THREAD_SHUTDOWNSKIP;
ListNext = ListNext->Flink;
}
/*
* Perform the proper shutdown operation on each thread.
*/
while (TRUE) {
ListHead = &pcsrp->ThreadList;
ListNext = ListHead->Flink;
while (ListNext != ListHead) {
Thread = CONTAINING_RECORD( ListNext, CSR_THREAD, Link );
if (!(Thread->Flags &
(CSR_THREAD_DESTROYED | CSR_THREAD_SHUTDOWNSKIP))) {
break;
}
ListNext = ListNext->Flink;
}
if (ListNext == ListHead)
break;
Thread->Flags |= CSR_THREAD_SHUTDOWNSKIP;
ShutdownInfo.dwFlags = dwClientFlags;
Status = NtUserQueryInformationThread(Thread->ThreadHandle, UserThreadShutdownInformation, &ShutdownInfo, sizeof(ShutdownInfo), NULL);
if (!NT_SUCCESS(Status))
continue;
if (ShutdownInfo.StatusShutdown == SHUTDOWN_UNKNOWN_PROCESS || !(ShutdownInfo.dwFlags & USER_THREAD_GUI))
continue;
/*
* Send the end session messages to the thread.
*/
CsrReferenceThread(Thread);
CsrUnlockProcess(Process);
/*
* If the user says kill it, the user wants it to go away now
* no matter what. If the user didn't say kill, then call again
* because we need to send WM_ENDSESSION messages.
*/
ThreadShutdownNotify(dwClientFlags, (ULONG_PTR)Thread, 0);
CsrLockProcessByClientId(pcsrp->ClientId.UniqueProcess, &Process);
CsrDereferenceThread(Thread);
}
}
KillIt:
CsrUnlockProcess(Process);
if (ISTS()) {
bDoBlock = (!fNoMsgsEver && !(dwClientFlags & WMCS_EXIT));
} else {
bDoBlock = (!fNoRetry && !(dwClientFlags & WMCS_EXIT));
}
if (bDoBlock) {
Status = SHUTDOWN_CANCEL;
goto RestoreDesktop;
}
/*
* Set the final shutdown status according to the number of gui
* threads found. If the count is zero, we have an unknown process.
*/
if (cThreads == 0)
ShutdownInfo.StatusShutdown = SHUTDOWN_UNKNOWN_PROCESS;
else
ShutdownInfo.StatusShutdown = SHUTDOWN_KNOWN_PROCESS;
if (ShutdownInfo.StatusShutdown == SHUTDOWN_UNKNOWN_PROCESS || !(dwClientFlags & WMCS_CONTEXTLOGOFF)) {
/*
* This process is not in the context being logged off. Do
* not terminate it and let console send an event to the process.
*/
Status = SHUTDOWN_UNKNOWN_PROCESS;
if (ShutdownInfo.drdRestore.hdeskNew) {
goto RestoreDesktop;
}
goto CleanupAndExit;
}
/*
* Calling ExitProcess() in the app's context will not always work
* because the app may have .dll termination deadlocks: so the thread
* will hang with the rest of the process. To ensure apps go away,
* we terminate the process with NtTerminateProcess().
* Pass this special value, DBG_TERMINATE_PROCESS, which tells
* NtTerminateProcess() to return failure if it can't terminate the
* process because the app is being debugged.
*/
if (ISTS()) {
NTSTATUS ExitStatus;
HANDLE DebugPort;
ExitStatus = DBG_TERMINATE_PROCESS;
if (NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessDebugPort, &DebugPort, sizeof(HANDLE), NULL)) && (DebugPort != NULL)) {
// Csr is being debugged - go ahead and kill the process
ExitStatus = 0;
}
TerminateStatus = NtTerminateProcess(pcsrp->ProcessHandle, ExitStatus);
} else {
TerminateStatus = NtTerminateProcess(pcsrp->ProcessHandle, DBG_TERMINATE_PROCESS);
}
pcsrp->Flags |= CSR_PROCESS_TERMINATED;
/*
* Let csr know we know about this process - meaning it was our
* responsibility to shut it down.
*/
Status = SHUTDOWN_KNOWN_PROCESS;
RestoreDesktop:
/*
* Release the desktop that was used.
*/
{
USERTHREAD_USEDESKTOPINFO utudi;
utudi.hThread = NULL;
RtlCopyMemory(&(utudi.drdRestore), &(ShutdownInfo.drdRestore), sizeof(DESKRESTOREDATA));
NtUserSetInformationThread(NtCurrentThread(), UserThreadUseDesktop, &utudi, sizeof(utudi));
}
/*
* Now that we're done with the process handle, derefence the csr
* process structure.
*/
if (Status != SHUTDOWN_UNKNOWN_PROCESS) {
/*
* If TerminateProcess returned STATUS_ACCESS_DENIED, then the process
* is being debugged and it wasn't terminated.Otherwise we need to wait
* anyway since TerminateProcess might return failure when the process
* is going away (ie STATUS_PROCESS_IS_TERMINATING).If termination
* indeed fail, something is wrong anyway so waiting a bit won't
* hurt much.
* If we wait give the process whatever exit timeout value configured
* in the registry, but no less than the 5 second Hung App timeout.
*/
if (TerminateStatus != STATUS_ACCESS_DENIED) {
LARGE_INTEGER li;
li.QuadPart = (LONGLONG)-10000 * gdwProcessTerminateTimeout;
TerminateStatus = NtWaitForSingleObject(pcsrp->ProcessHandle, FALSE, &li);
if (TerminateStatus != STATUS_WAIT_0) {
RIPMSG2(RIP_WARNING, "UserClientShutdown: wait for process %x failed with status %x", pcsrp->ClientId.UniqueProcess, TerminateStatus);
}
}
CsrDereferenceProcess(pcsrp);
}
CleanupAndExit:
return Status;
}
VOID CALLBACK WMCSCallback(HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult)
/*
* This function is passed to SendMessageCallback when sending the WM_CLIENTSHUTDOWN message.
* It propagates the return value back
* if ThreadShutdownNotify is still waiting for it; otherwise,
* it just frees the memory.
* 03-04-97 GerardoB Created.
*/
{
PWMCSDATA pwmcsd = (PWMCSDATA)dwData;
if (pwmcsd->dwFlags & WMCSD_IGNORE) {
LocalFree(pwmcsd);
return;
}
pwmcsd->dwFlags |= WMCSD_REPLY;
pwmcsd->dwRet = (DWORD)lResult;
UNREFERENCED_PARAMETER(hwnd);
UNREFERENCED_PARAMETER(uMsg);
}
HWND GetInputWindow (PCSR_THREAD pcsrt, HWND hwnd)
/*
* We assume a thread is waiting for input if it's not hung, the (main)
* window is disabled and it owns an enabled popup.
* 03-06-97 GerardoB Created.
*/
{
DWORD dwTimeout;
HWND hwndPopup;
/*
* Ask the kernel if the thread is hung
*/
dwTimeout = gCmsHungAppTimeout;
NtUserQueryInformationThread(pcsrt->ThreadHandle,
UserThreadHungStatus, &dwTimeout, sizeof(dwTimeout), NULL);
/*
* If not hung and disabled, see if it owns an enabled popup
*/
if (!dwTimeout && !IsWindowEnabled(hwnd)) {
hwndPopup = GetWindow(hwnd, GW_ENABLEDPOPUP);
if ((hwndPopup != NULL) && (hwndPopup != hwnd)) {
return hwndPopup;
}
}
return NULL;
}
void GetApplicationText (HWND hwnd, HANDLE hThread, WCHAR *pwcText, UINT uLen)
/*
* Gets the text that identifies the given window or thread
* 08-01-97 GerardoB Created.
*/
{
/*
* GetWindowText doesn't call the hwnd's proc; otherwise, we could get blocked here for good.
*/
GetWindowText(hwnd, pwcText, uLen);
if (*pwcText == 0) {
/*
* We couldn't get window title; let's try thread name
*/
NtUserQueryInformationThread(hThread, UserThreadTaskName, pwcText, uLen * sizeof(WCHAR), NULL);
}
}
DWORD ThreadShutdownNotify(DWORD dwClientFlags, ULONG_PTR dwThread, LPARAM lParam)
/*
* This function notifies a given thread that it's time (or about time)
* to go away. This is called from _EndTask to post the WM_CLOSE message
* or from UserClientShutdown to send the WM_QUERYENDSESSION and
* WM_ENDSESSION messages. If the thread fails to respond, then the
* "End Application" dialog is brought up. This function is also called
* from Console to display that dialog too.
* 03-07-97 GerardoB Created to replace SendShutdownMessages,
* MySendEndSessionMessages and DoEndTaskDialog
*/
{
HWND hwnd, hwndOwner;
PWMCSDATA pwmcsd = NULL;
HWND hwndDlg;
ENDDLGPARAMS edp;
DWORD dwRet, dwRealTimeout, dwTimeout, dwStartTiming, dwCmd;
MSG msg;
PCSR_THREAD pcsrt;
HANDLE hThread;
BOOL fEndTaskNow = FALSE;
#define ESMH_CANCELEVENT 0
#define ESMH_THREAD 1
#define ESMH_HANDLECOUNT 2
HANDLE ahandles[ESMH_HANDLECOUNT];
/*
* If this is console, just set up the wait loop and
* bring the dialog up right away. Otherwise, find
* the notification window, notify it and go wait.
*/
if (dwClientFlags & WMCS_CONSOLE) {
hThread = (HANDLE)dwThread;
dwRealTimeout = 0;
goto SetupWaitLoop;
} else {
pcsrt = (PCSR_THREAD)dwThread;
hThread = pcsrt->ThreadHandle;
hwnd = (HWND)lParam;
}
/*
* If no window was provided,
* find a top-level window owned by the thread
*/
if (hwnd == NULL) {
EnumThreadWindows(HandleToUlong(pcsrt->ClientId.UniqueThread),
&FindWindowFromThread, (LPARAM)&hwnd);
}
if (hwnd == NULL) {
return TSN_NOWINDOW;
}
/*
* Find the root owner (we'll guess this is the "main" window)
*/
while ((hwndOwner = GetWindow(hwnd, GW_OWNER)) != NULL) {
hwnd = hwndOwner;
}
#if defined(FE_IME)
/*
* If this is a console window, then just returns TSN_APPSAYSOK.
* In this routine:
* Normally windows NT environment, hwnd never point to console window.
* However, In ConIme process, its owner window point to console window.
*/
if (!(dwClientFlags & WMCS_ENDTASK)) {
if ((HANDLE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE) == ghModuleWin) {
return TSN_APPSAYSOK;
}
}
#endif
/*
* If this is an EndTask request but the window is disabled,
* then we want to bring the dialog up right way (the app
* is probably waiting for input).
* Otherwise, we bring the window to the foreground, send/post
* the request and wait
*/
/*
* Bug 296188 - joejo
* Make sure we respond to the user ASAP when they
* attempt to shutdown an application that we know is hung.
*/
if ((dwClientFlags & WMCS_ENDTASK)) {
dwTimeout = gCmsHungAppTimeout;
NtUserQueryInformationThread(pcsrt->ThreadHandle, UserThreadHungStatus, &dwTimeout, sizeof(dwTimeout), NULL);
if (!IsWindowEnabled(hwnd) || dwTimeout){
dwRealTimeout = 0;
fEndTaskNow = TRUE;
}
}
if (!fEndTaskNow) {
SetForegroundWindow(hwnd);
dwRealTimeout = gCmsHungAppTimeout;
if (dwClientFlags & WMCS_ENDTASK) {
PostMessage(hwnd, WM_CLOSE, 0, 0);
} else {
/*
* If the shutdown was canceled, we don't need to wait.
* (we're just sending the WM_ENDSESSION(FALSE))
*/
if (!(dwClientFlags & (WMCS_QUERYEND | WMCS_EXIT))) {
SendNotifyMessage(hwnd, WM_CLIENTSHUTDOWN, dwClientFlags, 0);
return TSN_APPSAYSOK;
}
/*
* Allocate callback data. If out of memory, kill it.
*/
pwmcsd = (PWMCSDATA)LocalAlloc(LPTR, sizeof(WMCSDATA));
if (pwmcsd == NULL) {
return TSN_USERSAYSKILL;
}
SendMessageCallback(hwnd, WM_CLIENTSHUTDOWN, dwClientFlags, 0, WMCSCallback, (ULONG_PTR)pwmcsd);
}
}
SetupWaitLoop:
/*
* This tells us if the hwndDlg is valid. This is set/cleared by
* EndTaskDlgProc
*/
ZeroMemory(&edp, sizeof(edp));
edp.dwFlags = EDPF_NODLG;
/*
* Loop until the hwnd replies, the request is canceled
* or the thread goes away. It time out, bring up the
* dialog and wait until the user tells us what to do.
*/
*(ahandles + ESMH_CANCELEVENT) = gheventCancel;
*(ahandles + ESMH_THREAD) = hThread;
dwStartTiming = GetTickCount();
dwCmd = 0;
while (dwCmd == 0) {
/*
* Calculate how long we have to wait.
*/
dwTimeout = dwRealTimeout;
if ((dwTimeout != 0) && (dwTimeout != INFINITE)) {
dwTimeout -= (GetTickCount() - dwStartTiming);
if ((int)dwTimeout < 0) {
dwTimeout = 0;
}
}
dwRet = MsgWaitForMultipleObjects(ESMH_HANDLECOUNT, ahandles, FALSE, dwTimeout, QS_ALLINPUT);
switch (dwRet)
{
case WAIT_OBJECT_0 + ESMH_CANCELEVENT:
/*
* The request has been canceled.
*/
dwCmd = TSN_USERSAYSCANCEL;
break;
case WAIT_OBJECT_0 + ESMH_THREAD:
/*
* The thread is gone.
*/
dwCmd = TSN_APPSAYSOK;
break;
case WAIT_OBJECT_0 + ESMH_HANDLECOUNT:
/*
* We got some input; process it.
*/
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if ((edp.dwFlags & EDPF_NODLG) || !IsDialogMessage(hwndDlg, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
/*
* If we got a reply to the message, act on it
*/
if ((pwmcsd != NULL) && (pwmcsd->dwFlags & WMCSD_REPLY)) {
switch (pwmcsd->dwRet)
{
default:
/*
* If the message was not processed (the thread
* exited) or someone processed it and returned
* a bogus value, just shut them down.
* Fall through
*/
case WMCSR_ALLOWSHUTDOWN:
/*
* We're going to nuke this app, so get rid of
* any pending harderror boxes he might have
*/
BoostHardError((ULONG_PTR) pcsrt->ClientId.UniqueProcess, BHE_FORCE);
/*
* Fall through.
*/
case WMCSR_DONE:
dwCmd = TSN_APPSAYSOK;
break;
case WMCSR_CANCEL:
dwCmd = TSN_APPSAYSNOTOK;
break;
}
}
/*
* Else if the dialog is still up, keep waiting for the user
* to tell us what to do
*/
else if (!(edp.dwFlags & EDPF_NODLG)) {
break;
}
/*
* Else if the user dismissed the dialog, act on his response
*/
else if (edp.dwFlags & EDPF_RESPONSE) {
switch (edp.dwRet)
{
case IDC_ENDNOW:
/*
* The user wants us to kill it
*/
dwCmd = TSN_USERSAYSKILL;
break;
/* case IDCANCEL: */
default:
dwCmd = TSN_USERSAYSCANCEL;
break;
}
}
break;
case WAIT_TIMEOUT:
if (dwClientFlags & WMCS_NORETRY) {
/*
* We come here only for Terminal Server case. We return
* TSN_APPSAYSOK as Terminal Server 4 did in this case.
*/
UserAssert(ISTS());
dwCmd = TSN_APPSAYSOK;
break;
}
/*
* Once we time out, we bring up the dialog and let
* its timer take over.
*/
dwRealTimeout = INFINITE;
/*
* Check if the windows app is waiting for input;
* if not, we assume it is hung for EndTask;
* otherwise we enter a wait mode that brings the
* dialog up just to provide some (waiting) feedback
* Console just gets the dialog right away
*/
if (!(dwClientFlags & WMCS_CONSOLE)) {
if (BoostHardError((ULONG_PTR)pcsrt->ClientId.UniqueProcess, BHE_TEST) || (GetInputWindow(pcsrt, hwnd) != NULL)) {
edp.dwFlags |= EDPF_INPUT;
} else {
/*
* EWX_FORCEIFHUNG support.
* Also, if this is an ExitWindows call and the process is not in
* the context being logged off, we won't kill it.
* So don't bother asking the user what to do
*/
if ((dwClientFlags & WMCS_NODLGIFHUNG) || (!(dwClientFlags & WMCS_ENDTASK) && !(dwClientFlags & WMCS_CONTEXTLOGOFF))) {
dwCmd = TSN_USERSAYSKILL;
break;
}
/*
* Hung or Wait?
*/
if (dwClientFlags & WMCS_ENDTASK) {
edp.dwFlags |= EDPF_HUNG;
} else {
edp.dwFlags |= EDPF_WAIT;
}
}
}
/*
* If the registry says no dialog, then tell the caller
* the user wants to kill the app.
*/
if (gfAutoEndTask) {
dwCmd = TSN_USERSAYSKILL;
break;
}
/*
* Setup the parameters needed by EndTaskDlgProc
*/
edp.dwRet = 0;
edp.dwClientFlags = dwClientFlags;
if (dwClientFlags & WMCS_CONSOLE) {
edp.pcsrt = NULL;
edp.lParam = lParam;
} else {
edp.pcsrt = pcsrt;
edp.lParam = (LPARAM)hwnd;
}
hwndDlg = CreateDialogParam (ghModuleWin, MAKEINTRESOURCE(IDD_ENDTASK), NULL, EndTaskDlgProc, (LPARAM)(&edp));
/*
* If we cannot ask the user, then kill the app.
*/
if (hwndDlg == NULL) {
edp.dwFlags |= EDPF_NODLG;
dwCmd = TSN_USERSAYSKILL;
break;
}
break;
default:
/*
* Unexpected return; something is wrong. Kill the app.
*/
UserAssert(dwRet != dwRet);
dwCmd = TSN_USERSAYSKILL;
break;
} /* switch (dwRet) */
} /* while (dwCmd == 0) */
/*
* If the dialog is up, nuke it.
*/
if (!(edp.dwFlags & EDPF_NODLG)) {
DestroyWindow(hwndDlg);
}
/*
* Make sure pwmcsd is freed or marked to be freed by WMCSCallback
* when the reply comes
*/
if (pwmcsd != NULL) {
if (pwmcsd->dwFlags & WMCSD_REPLY) {
LocalFree(pwmcsd);
} else {
pwmcsd->dwFlags |= WMCSD_IGNORE;
}
}
#if DBG
/*
* If Canceling, let's the name the app that doesn't let us log off
*/
if ((dwClientFlags & WMCS_EXIT) && (dwCmd == TSN_APPSAYSNOTOK)) {
WCHAR achTitle[CCHBODYMAX];
WCHAR *pwcText;
UserAssert(!(dwClientFlags & WMCS_CONSOLE));
pwcText = achTitle;
*(achTitle + CCHBODYMAX - 1) = (WCHAR)0;
GetApplicationText(hwnd, hThread, pwcText, CCHBODYMAX - 1);
KdPrint(("Log off canceled by pid:%#lx tid:%#lx - '%ws'.\n",
HandleToUlong(pcsrt->ClientId.UniqueProcess),
HandleToUlong(pcsrt->ClientId.UniqueThread),
pwcText));
}
#endif
/*
* If we're killing this dude, clean any hard errors.
* Also if wow takes care of it, then our caller doesn't need to
*/
if ((dwCmd == TSN_USERSAYSKILL) && !(dwClientFlags & WMCS_CONSOLE)) {
BoostHardError((ULONG_PTR)pcsrt->ClientId.UniqueProcess, BHE_FORCE);
if (!(pcsrt->Flags & CSR_THREAD_DESTROYED) && WowExitTask(pcsrt)) {
dwCmd = TSN_APPSAYSOK;
}
}
return dwCmd;
}
void SetEndTaskDlgStatus(ENDDLGPARAMS *pedp, HWND hwndDlg, UINT uStrId, BOOL fInit)
/*
* Displays the appropiate message and shows the dialog
* 03-11-97 GerardoB Created
*/
{
BOOL f, fIsWaiting, fWasWaiting;
WCHAR *pwcText;
fWasWaiting = (pedp->uStrId == STR_ENDTASK_WAIT);
fIsWaiting = (pedp->dwFlags & EDPF_WAIT);
/*
* Store the current message id, load it and show it
*/
pedp->uStrId = uStrId;
pwcText = ServerLoadString(ghModuleWin, uStrId, NULL, &f);
if (pwcText != NULL) {
SetDlgItemText(hwndDlg, IDC_STATUSMSG, pwcText);
LocalFree(pwcText);
}
/*
* If we haven't decided that the app is hung, set a
* timer to keep an eye on it.
*/
if (!(pedp->dwFlags & EDPF_HUNG) && !(pedp->dwClientFlags & WMCS_CONSOLE)) {
SetTimer(hwndDlg, IDT_CHECKAPPSTATE, gCmsHungAppTimeout, NULL);
}
/*
* If initializing or switching to/from the wait mode,
* set the proper status for IDC_STATUSCANCEL, IDCANCEL,
* IDC_ENDNOW and the start/stop the progress bar.
* Invalidate paint if/as needed
*/
if (fInit || (fIsWaiting ^ fWasWaiting)) {
RECT rc;
HWND hwndStatusCancel = GetDlgItem(hwndDlg, IDC_STATUSCANCEL);
HWND hwndCancelButton = GetDlgItem(hwndDlg, IDCANCEL);
HWND hwndEndButton = GetDlgItem(hwndDlg, IDC_ENDNOW);
DWORD dwSwpFlags;
/*
* If on wait mode, we hide the cancel button and its
* explanatory text. The End button will be moved to
* the cancel button position.
*/
dwSwpFlags = ((fIsWaiting ? SWP_HIDEWINDOW : SWP_SHOWWINDOW)
| SWP_NOREDRAW | SWP_NOSIZE | SWP_NOMOVE
| SWP_NOZORDER | SWP_NOSENDCHANGING
| SWP_NOACTIVATE);
/*
* If we're hiding the cancel button, give focus/def id to
* the End button.
* Note that DM_SETDEIF won't do the right thing unless
* both Cancel/End buttons are visible.
*/
if (fIsWaiting) {
SendMessage(hwndDlg, DM_SETDEFID, IDC_ENDNOW, 0);
SetFocus(hwndEndButton);
}
SetWindowPos(hwndStatusCancel, NULL, 0, 0, 0, 0, dwSwpFlags);
SetWindowPos(hwndCancelButton, NULL, 0, 0, 0, 0, dwSwpFlags);
/*
* If the cancel button is visible, give it focus/def id.
*/
if (!fIsWaiting) {
SendMessage(hwndDlg, DM_SETDEFID, IDCANCEL, 0);
SetFocus(hwndCancelButton);
}
/*
* Initialize progress bar (first time around)
*/
if (fIsWaiting && (pedp->hbrProgress == NULL)) {
int iMagic;
/*
* Initialize progress bar stuff.
* The size and location calculations below were made up
* to make it look good(?).
* We need that location on dialog coordiantes since the
* progress bar is painted on the dialog's WM_PAINT.
*/
GetClientRect(hwndStatusCancel, &pedp->rcBar);
iMagic = (pedp->rcBar.bottom - pedp->rcBar.top) / 4;
InflateRect(&pedp->rcBar, 0, -iMagic + GetSystemMetrics(SM_CYEDGE));
pedp->rcBar.right -= (5 * iMagic);
OffsetRect(&pedp->rcBar, 0, -iMagic);
MapWindowPoints(hwndStatusCancel, hwndDlg, (LPPOINT)&pedp->rcBar, 2);
/*
* Calculate drawing rectangle and dimensions.
* We kind of make it look like comctrl's progress bar.
*/
pedp->rcProgress = pedp->rcBar;
InflateRect(&pedp->rcProgress, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE));
pedp->iProgressStop = pedp->rcProgress.right;
pedp->iProgressWidth = ((2 * (pedp->rcProgress.bottom - pedp->rcProgress.top)) / 3);
pedp->rcProgress.right = pedp->rcProgress.left + pedp->iProgressWidth - 1;
pedp->hbrProgress = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION));
/*
* Remember the End button position.
*/
GetWindowRect(hwndEndButton, &pedp->rcEndButton);
MapWindowPoints(NULL, hwndDlg, (LPPOINT)&pedp->rcEndButton, 2);
}
/*
* Start/Stop progress bar and set End button position
*/
if (fIsWaiting) {
RECT rcEndButton;
UINT uTimeout = (gdwHungToKillCount * gCmsHungAppTimeout) / ((pedp->iProgressStop - pedp->rcProgress.left) / pedp->iProgressWidth);
SetTimer(hwndDlg, IDT_PROGRESS, uTimeout, NULL);
/*
* The Cancel and the End buttons might have different widths when
* localized. So make sure we position it inside the dialog and
* to the right end of the dialog.
*/
GetWindowRect(hwndCancelButton, &rc);
GetWindowRect(hwndEndButton, &rcEndButton);
rc.left = rc.right - (rcEndButton.right - rcEndButton.left);
MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rc, 2);
} else if (fWasWaiting) {
KillTimer(hwndDlg, IDT_PROGRESS);
rc = pedp->rcEndButton;
}
/*
* Move the End button if needed
*/
if (fIsWaiting || fWasWaiting) {
SetWindowPos(hwndEndButton, NULL, rc.left, rc.top, 0, 0, SWP_NOREDRAW | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSENDCHANGING);
}
/*
* Make sure we repaint if needed
*/
if (!fInit) {
InvalidateRect(hwndDlg, NULL, TRUE);
}
} /* if (fInit || (fIsWaiting ^ fWasWaiting)) */
/*
* If initializing or in hung mode, make sure the user can
* see the dialog; only bring it to the foreground on
* initialization (no rude focus stealing)
*/
if (fInit || (pedp->dwFlags & EDPF_HUNG)) {
SetWindowPos(hwndDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
if (fInit) {
SetForegroundWindow(hwndDlg);
}
}
}
INT_PTR APIENTRY EndTaskDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
/*
* This is the dialog procedure for the dialog that comes up when an app is not responding.
* 03-06-97 GerardoB Rewrote it once again. New template though
* 07-23-92 ScottLu Rewrote it, but used same dialog template.
* 04-28-92 JonPa Created.
*/
{
ENDDLGPARAMS* pedp;
WCHAR achTitle[CCHBODYMAX];
WCHAR *pwcText, *pwcTemp;
UINT uLen;
UINT uStrId;
PAINTSTRUCT ps;
HDC hdc, hdcMem;
BOOL fIsInput, fWasInput;
#ifdef USE_MIRRORING
int iOldLayout;
#endif
switch (msg)
{
case WM_INITDIALOG:
/*
* Save parameters
*/
pedp = (ENDDLGPARAMS*)lParam;
SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (ULONG_PTR)pedp);
/*
* This tells the caller that the dialog is up
*/
pedp->dwFlags &= ~EDPF_NODLG;
/*
* Build the dialog title making sure that
* we end up with a NULL terminated string.
*/
*(achTitle + CCHBODYMAX - 1) = (WCHAR)0;
uLen = GetWindowText(hwndDlg, achTitle, CCHBODYMAX - 1);
pwcText = achTitle + uLen;
uLen = CCHBODYMAX - 1 - uLen;
/*
* Console provides the title; we figure it out for windows apps.
*/
if (pedp->dwClientFlags & WMCS_CONSOLE) {
pwcTemp = (WCHAR *)pedp->lParam;
while (uLen-- && (*pwcText++ = *pwcTemp++));
} else {
GetApplicationText((HWND)pedp->lParam, pedp->pcsrt->ThreadHandle, pwcText, uLen);
}
SetWindowText(hwndDlg, achTitle);
/*
* Get the app's icon: first look for atomIconProperty
* if not available, try the class icon.
* else, use the default icon.
*/
pedp->hIcon = (HICON)GetProp((HWND)pedp->lParam, ICON_PROP_NAME);
if (pedp->hIcon == NULL) {
pedp->hIcon = (HICON)GetClassLongPtr((HWND)pedp->lParam, GCLP_HICON);
if (pedp->hIcon == NULL) {
if (pedp->dwClientFlags & WMCS_CONSOLE) {
pedp->hIcon = LoadIcon(ghModuleWin, MAKEINTRESOURCE(IDI_CONSOLE));
}
else {
pedp->hIcon = LoadIcon(NULL, IDI_APPLICATION);
}
}
}
/*
* Figure out what message the caller wants initially
*/
if (pedp->dwClientFlags & WMCS_CONSOLE) {
uStrId = STR_ENDTASK_CONSOLE;
} else if (pedp->dwFlags & EDPF_INPUT) {
uStrId = STR_ENDTASK_INPUT;
} else if (pedp->dwFlags & EDPF_WAIT) {
uStrId = STR_ENDTASK_WAIT;
} else {
uStrId = STR_ENDTASK_HUNG;
}
/*
* Display the message, set the focus and show the dialog
*/
SetEndTaskDlgStatus(pedp, hwndDlg, uStrId, TRUE);
return FALSE;
case WM_PAINT:
pedp = (ENDDLGPARAMS*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
if ((pedp == NULL) || (pedp->hIcon == NULL)) {
break;
}
/*
* Draw the icon
*/
hdc = BeginPaint(hwndDlg, &ps);
#ifdef USE_MIRRORING
iOldLayout = GetLayout(hdc);
if (iOldLayout != GDI_ERROR) {
SetLayout(hdc, iOldLayout|LAYOUT_BITMAPORIENTATIONPRESERVED);
}
#endif
DrawIcon(hdc, ETD_XICON, ETD_YICON, pedp->hIcon);
#ifdef USE_MIRRORING
if (iOldLayout != GDI_ERROR) {
SetLayout(hdc, iOldLayout);
}
#endif
/*
* If waiting, draw the progress bar;
* else draw the warning sign
*/
if (pedp->dwFlags & EDPF_WAIT) {
RECT rc;
/*
* Draw a client-edge-looking border.
*/
rc = pedp->rcBar;
DrawEdge(hdc, &rc, BDR_SUNKENOUTER, BF_RECT | BF_ADJUST);
InflateRect(&rc, -1, -1);
/*
* Draw the blocks up to current position
*/
rc.right = rc.left + pedp->iProgressWidth - 1;
while (rc.left < pedp->rcProgress.left) {
if (rc.right > pedp->iProgressStop) {
rc.right = pedp->iProgressStop;
if (rc.left >= rc.right) {
break;
}
}
FillRect(hdc, &rc, pedp->hbrProgress);
rc.left += pedp->iProgressWidth;
rc.right += pedp->iProgressWidth;
}
} else {
/*
* Load the bitmap the first time around and
* figure out where it goes
*/
if (pedp->hbmpWarning == NULL) {
BITMAP bmp;
pedp->hbmpWarning = LoadBitmap(ghModuleWin, MAKEINTRESOURCE(IDB_WARNING));
if (GetObject(pedp->hbmpWarning, sizeof(bmp), &bmp)) {
pedp->rcWarning.left = ETD_XICON;
pedp->rcWarning.top = ETD_XICON + 32 - bmp.bmHeight;
pedp->rcWarning.right = bmp.bmWidth;
pedp->rcWarning.bottom = bmp.bmHeight;
}
}
/*
* Blit it
*/
hdcMem = CreateCompatibleDC(hdc);
SelectObject(hdcMem, pedp->hbmpWarning);
GdiTransparentBlt(hdc, pedp->rcWarning.left, pedp->rcWarning.top,
pedp->rcWarning.right, pedp->rcWarning.bottom,
hdcMem, 0, 0, pedp->rcWarning.right, pedp->rcWarning.bottom, RGB(255, 0, 255));
DeleteDC(hdcMem);
}
EndPaint(hwndDlg, &ps);
return TRUE;
case WM_TIMER:
pedp = (ENDDLGPARAMS*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
if (pedp == NULL) {
return TRUE;
}
switch (wParam)
{
case IDT_CHECKAPPSTATE:
pedp->dwCheckTimerCount++;
/*
* Check if the app has switched from/to a waiting-for-input
* mode. If so, update the dialog and wait a little longer
*/
fIsInput = (BoostHardError((ULONG_PTR)pedp->pcsrt->ClientId.UniqueProcess, BHE_TEST) || (GetInputWindow(pedp->pcsrt, (HWND)pedp->lParam) != NULL));
fWasInput = (pedp->dwFlags & EDPF_INPUT);
if (fIsInput ^ fWasInput) {
UINT uProgress;
pedp->dwFlags &= ~(EDPF_INPUT | EDPF_WAIT);
pedp->dwFlags |= (fIsInput ? EDPF_INPUT : EDPF_WAIT);
SetEndTaskDlgStatus(pedp, hwndDlg, (fIsInput ? STR_ENDTASK_INPUT : STR_ENDTASK_WAIT), FALSE);
pedp->dwCheckTimerCount /= 2;
uProgress = pedp->rcProgress.left - pedp->rcBar.left - GetSystemMetrics(SM_CXEDGE);
uProgress /= 2;
pedp->rcProgress.left -= uProgress;
pedp->rcProgress.right -= uProgress;
}
/*
* Is it time to declare it hung?
*/
if (pedp->dwCheckTimerCount >= gdwHungToKillCount) {
KillTimer(hwndDlg, IDT_CHECKAPPSTATE);
pedp->dwFlags &= ~(EDPF_INPUT | EDPF_WAIT);
pedp->dwFlags |= EDPF_HUNG;
SetEndTaskDlgStatus(pedp, hwndDlg, STR_ENDTASK_HUNG, FALSE);
}
break;
case IDT_PROGRESS:
/*
* Draw the next block in the progress bar.
*/
if (pedp->rcProgress.right >= pedp->iProgressStop) {
pedp->rcProgress.right = pedp->iProgressStop;
if (pedp->rcProgress.left >= pedp->rcProgress.right) {
break;
}
}
hdc = GetDC(hwndDlg);
FillRect(hdc, &pedp->rcProgress, pedp->hbrProgress);
ReleaseDC(hwndDlg, hdc);
pedp->rcProgress.left += pedp->iProgressWidth;
pedp->rcProgress.right += pedp->iProgressWidth;
break;
}
return TRUE;
case WM_NCACTIVATE:
/*
* Make sure we're uncovered when active and not covering the app
* when inactive
*/
pedp = (ENDDLGPARAMS*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
if (pedp != NULL) {
HWND hwnd;
if (wParam) {
hwnd = HWND_TOPMOST;
} else if (pedp->dwClientFlags & WMCS_CONSOLE) {
hwnd = HWND_TOP;
} else {
hwnd = (HWND)pedp->lParam;
}
SetWindowPos(hwndDlg, hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
break;
case WM_COMMAND:
/*
* The user has made a choice, we're done.
*/
pedp = (ENDDLGPARAMS*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
if (pedp != NULL) {
pedp->dwRet = (DWORD)wParam;
}
DestroyWindow(hwndDlg);
break;
case WM_DESTROY:
/*
* We're dead. Make sure the caller knows we're history
*/
pedp = (ENDDLGPARAMS*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
if (pedp != NULL) {
pedp->dwFlags |= (EDPF_NODLG | EDPF_RESPONSE);
if (pedp->hbmpWarning != NULL) {
DeleteObject(pedp->hbmpWarning);
}
if (pedp->hbrProgress != NULL) {
DeleteObject(pedp->hbrProgress);
}
}
break;
}
return FALSE;
}
BOOL _EndTask(HWND hwnd, BOOL fShutdown, BOOL fMeanKill)
/*
* This routine is called from the task manager to end an application - for
* gui apps, either a win32 app or a win16 app. Note: Multiple console
* processes can live in a single console window. We'll pass these requests for destruction to console.
* 07-25-92 ScottLu Created.
*/
{
BOOL fRet = TRUE;
PCSR_THREAD pcsrt = CSR_SERVER_QUERYCLIENTTHREAD();
PCSR_THREAD pcsrtKill;
DWORD dwThreadId;
DWORD dwProcessId;
LPWSTR lpszMsg;
BOOL fAllocated;
DWORD dwCmd;
USERTHREAD_USEDESKTOPINFO utudi;
NTSTATUS Status;
/*
* Note: fShutdown isn't used for anything in this routine!
* They are still there because I haven't removed them: the old endtask
* code relied on them.
*/
UNREFERENCED_PARAMETER(fShutdown);
/*
* Set the current work thread to a desktop so we can
* go safely into win32k.sys
*/
utudi.hThread = pcsrt->ThreadHandle;
utudi.drdRestore.pdeskRestore = NULL;
Status = NtUserSetInformationThread(NtCurrentThread(), UserThreadUseDesktop, &utudi, sizeof(utudi));
if (!NT_SUCCESS(Status)) {
/*
* We were unable to get the thread's desktop. Game over
*/
return TRUE;
}
/*
* Get the process and thread that owns hwnd.
*/
dwThreadId = GetWindowThreadProcessId(hwnd, &dwProcessId);
if (dwThreadId == 0) {
goto RestoreDesktop;
}
/*
* Don't allow destruction of winlogon
*/
if (dwProcessId == gIdLogon) {
goto RestoreDesktop;
}
/*
* If this is a console window, then just send the close message to
* it, and let console clean up the processes in it.
*/
if ((HANDLE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE) == ghModuleWin) {
PostMessage(hwnd, WM_CLOSE, 0, 0);
goto RestoreDesktop;
}
/*
* Find the CSR_THREAD for the window.
*/
CsrLockThreadByClientId((HANDLE)LongToHandle( dwThreadId ), &pcsrtKill);
if (pcsrtKill == NULL) {
goto RestoreDesktop;
}
CsrReferenceThread(pcsrtKill);
CsrUnlockThread(pcsrtKill);
/*
* If this is a WOW app, then shutdown just this wow application.
*/
if (!fMeanKill) {
/*
* Find out what to do now - did the user cancel or the app cancel,
* etc? Only allow cancelling if we are not forcing the app to
* exit.
*/
dwCmd = ThreadShutdownNotify(WMCS_ENDTASK, (ULONG_PTR)pcsrtKill, (LPARAM)hwnd);
switch (dwCmd)
{
case TSN_APPSAYSNOTOK:
/*
* App says not ok - this'll let taskman bring up the "are you sure?"
* dialog to the user.
*/
CsrDereferenceThread(pcsrtKill);
fRet = FALSE;
goto RestoreDesktop;
case TSN_USERSAYSCANCEL:
/*
* User hit cancel on the timeout dialog - so the user really meant
* it. Let taskman know everything is ok by returning TRUE.
*/
CsrDereferenceThread(pcsrtKill);
goto RestoreDesktop;
}
}
/*
* Kill the application now. If the thread has not been destroyed,
* nuke the task. If WowExitTask returns that the thread is not a WOW task, terminate the process.
*/
if (!(pcsrtKill->Flags & CSR_THREAD_DESTROYED) && !WowExitTask(pcsrtKill)) {
BOOL bDoBlock;
/*
* Calling ExitProcess() in the app's context will not always work
* because the app may have .dll termination deadlocks: so the thread
* will hang with the rest of the process. To ensure apps go away,
* we terminate the process with NtTerminateProcess().
* Pass this special value, DBG_TERMINATE_PROCESS, which tells
* NtTerminateProcess() to return failure if it can't terminate the process because the app is being debugged.
*/
if (ISTS()) {
NTSTATUS ExitStatus;
HANDLE DebugPort;
ExitStatus = DBG_TERMINATE_PROCESS;
if (NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessDebugPort, &DebugPort, sizeof(HANDLE), NULL)) && (DebugPort != NULL)) {
// Csr is being debugged - go ahead and kill the process
ExitStatus = 0;
}
if (!NT_SUCCESS(NtTerminateProcess(pcsrtKill->Process->ProcessHandle, ExitStatus))) {
bDoBlock = TRUE;
} else {
bDoBlock = FALSE;
}
} else {
if (!NT_SUCCESS(NtTerminateProcess(pcsrtKill->Process->ProcessHandle, DBG_TERMINATE_PROCESS))) {
bDoBlock = TRUE;
} else {
bDoBlock = FALSE;
}
}
if (bDoBlock) {
/*
* If the app is being debugged, don't close it - because that can cause a hang to the NtTerminateProcess() call.
*/
lpszMsg = ServerLoadString(ghModuleWin, STR_APPDEBUGGED, NULL, &fAllocated);
if (lpszMsg) {
MessageBoxEx(NULL, lpszMsg, NULL, MB_OK | MB_SETFOREGROUND, 0);
LocalFree(lpszMsg);
}
} else {
pcsrtKill->Process->Flags |= CSR_PROCESS_TERMINATED;
}
}
CsrDereferenceThread(pcsrtKill);
RestoreDesktop:
utudi.hThread = NULL;
Status = NtUserSetInformationThread(NtCurrentThread(), UserThreadUseDesktop, &utudi, sizeof(utudi));
UserAssert(NT_SUCCESS(Status));
return fRet;
}
BOOL WowExitTask(PCSR_THREAD pcsrt)
/*
* Calls wow back to make sure a specific task has exited. Returns
* TRUE if the thread is a WOW task, FALSE if not.
* 08-02-92 ScottLu Created.
*/
{
HANDLE ahandle[2];
USERTHREAD_WOW_INFORMATION WowInfo;
NTSTATUS Status;
ahandle[1] = gheventCancel;
/*
* Query task id and exit function.
*/
Status = NtUserQueryInformationThread(pcsrt->ThreadHandle, UserThreadWOWInformation, &WowInfo, sizeof(WowInfo), NULL);
if (!NT_SUCCESS(Status))
return FALSE;
/*
* If no task id was returned, it is not a WOW task
*/
if (WowInfo.hTaskWow == 0)
return FALSE;
/*
* Try to make it exit itself. This will work most of the time.
* If this doesn't work, terminate this process.
*/
ahandle[0] = InternalCreateCallbackThread(pcsrt->Process->ProcessHandle, (ULONG_PTR)WowInfo.lpfnWowExitTask, (ULONG_PTR)WowInfo.hTaskWow);
if (ahandle[0] == NULL) {
NtTerminateProcess(pcsrt->Process->ProcessHandle, 0);
pcsrt->Process->Flags |= CSR_PROCESS_TERMINATED;
goto Exit;
}
WaitForMultipleObjects(2, ahandle, FALSE, INFINITE);
NtClose(ahandle[0]);
Exit:
return TRUE;
}
DWORD InternalWaitCancel(HANDLE handle, DWORD dwMilliseconds)
/*
* Console calls this to wait for objects or shutdown to be cancelled
* 29-Oct-1992 mikeke Created
*/
{
HANDLE ahandle[2];
ahandle[0] = handle;
ahandle[1] = gheventCancel;
return WaitForMultipleObjects(2, ahandle, FALSE, dwMilliseconds);
}
HANDLE InternalCreateCallbackThread(HANDLE hProcess, ULONG_PTR lpfn, ULONG_PTR dwData)
/*
* This routine creates a remote thread in the context of a given process.
* It is used to call the console control routine, as well as ExitProcess when forcing an exit.
* Returns a thread handle.
* 07-28-92 ScottLu Created.
*/
{
LONG BasePriority;
HANDLE hThread, hToken;
PTOKEN_DEFAULT_DACL lpDaclDefault;
TOKEN_DEFAULT_DACL daclDefault;
ULONG cbDacl;
SECURITY_ATTRIBUTES attrThread;
SECURITY_DESCRIPTOR sd;
DWORD idThread;
NTSTATUS Status;
hThread = NULL;
Status = NtOpenProcessToken(hProcess, TOKEN_QUERY, &hToken);
if (!NT_SUCCESS(Status)) {
KdPrint(("NtOpenProcessToken failed, status = %x\n", Status));
return NULL;
}
cbDacl = 0;
NtQueryInformationToken(hToken, TokenDefaultDacl, &daclDefault, sizeof(daclDefault), &cbDacl);
lpDaclDefault = (PTOKEN_DEFAULT_DACL)LocalAlloc(LMEM_FIXED, cbDacl);
if (lpDaclDefault == NULL) {
KdPrint(("LocalAlloc failed for lpDaclDefault"));
goto closeexit;
}
Status = NtQueryInformationToken(hToken, TokenDefaultDacl, lpDaclDefault, cbDacl, &cbDacl);
if (!NT_SUCCESS(Status)) {
KdPrint(("NtQueryInformationToken failed, status = %x\n", Status));
goto freeexit;
}
if (!NT_SUCCESS(RtlCreateSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION1))) {
UserAssert(FALSE);
goto freeexit;
}
RtlSetDaclSecurityDescriptor(&sd, TRUE, lpDaclDefault->DefaultDacl, TRUE);
attrThread.nLength = sizeof(attrThread);
attrThread.lpSecurityDescriptor = &sd;
attrThread.bInheritHandle = FALSE;
GetLastError();
hThread = CreateRemoteThread(hProcess, &attrThread, 0L, (LPTHREAD_START_ROUTINE)lpfn, (LPVOID)dwData, 0, &idThread);
if (hThread != NULL) {
BasePriority = THREAD_PRIORITY_HIGHEST;
NtSetInformationThread(hThread, ThreadBasePriority, &BasePriority, sizeof(LONG));
}
freeexit:
LocalFree((HANDLE)lpDaclDefault);
closeexit:
NtClose(hToken);
return hThread;
}
ULONG SrvExitWindowsEx(IN OUT PCSR_API_MSG m, IN OUT PCSR_REPLY_STATUS ReplyStatus)
{
BEGIN_LPC_RECV(EXITWINDOWSEX);
Status = _ExitWindowsEx(pcsrt, a->uFlags, a->dwReserved);
a->fSuccess = NT_SUCCESS(Status);
END_LPC_RECV();
}
ULONG SrvEndTask(IN OUT PCSR_API_MSG m, IN OUT PCSR_REPLY_STATUS ReplyStatus)
{
PENDTASKMSG petm = (PENDTASKMSG)&m->u.ApiMessageData;
PCSR_THREAD pcsrt;
PTEB Teb = NtCurrentTeb();
Teb->LastErrorValue = 0;
pcsrt = CSR_SERVER_QUERYCLIENTTHREAD();
/*
* Don't block the client so it can respond to messages while we
* process this request -- we might bring up the End Application
* dialog or the hwnd being shutdown might request some user action.
*/
if (pcsrt->Process->ClientPort != NULL) {
m->ReturnValue = STATUS_SUCCESS;
petm->dwLastError = 0;
petm->fSuccess = TRUE;
NtReplyPort(pcsrt->Process->ClientPort, (PPORT_MESSAGE)m);
*ReplyStatus = CsrServerReplied;
}
petm->fSuccess = _EndTask(petm->hwnd, petm->fShutdown, petm->fForce);
petm->dwLastError = Teb->LastErrorValue;
return STATUS_SUCCESS;
}
BOOL IsPrivileged(PPRIVILEGE_SET ppSet)
/*
* Check to see if the client has the specified privileges
* History:
* 01-02-91 JimA Created.
*/
{
HANDLE hToken;
NTSTATUS Status;
BOOLEAN bResult = FALSE;
UNICODE_STRING strSubSystem;
/*
* Impersonate the client
*/
if (!CsrImpersonateClient(NULL))
return FALSE;
/*
* Open the client's token
*/
RtlInitUnicodeString(&strSubSystem, L"USER32");
if (NT_SUCCESS(Status = NtOpenThreadToken(NtCurrentThread(), TOKEN_QUERY, (BOOLEAN)TRUE, &hToken))) {
/*
* Perform the check
*/
Status = NtPrivilegeCheck(hToken, ppSet, &bResult);
NtPrivilegeObjectAuditAlarm(&strSubSystem, NULL, hToken, 0, ppSet, bResult);
NtClose(hToken);
if (!bResult) {
SetLastError(ERROR_ACCESS_DENIED);
}
}
CsrRevertToSelf();
if (!NT_SUCCESS(Status))
SetLastError(RtlNtStatusToDosError(Status));
/*
* Return result of privilege check
*/
return (BOOL)(bResult && NT_SUCCESS(Status));
}
ULONG SrvRegisterServicesProcess(IN OUT PCSR_API_MSG m, IN OUT PCSR_REPLY_STATUS ReplyStatus)
/*
* Register the services process.
* History:
* 05-05-95 BradG Created.
*/
{
PRIVILEGE_SET psTcb = { 1, PRIVILEGE_SET_ALL_NECESSARY,
{ SE_TCB_PRIVILEGE, 0 }
};
BEGIN_LPC_RECV(REGISTERSERVICESPROCESS);
/*
* Allow only one services process and then only if it has TCB privilege.
*/
EnterCrit();
if ((gdwServicesProcessId != 0) || !IsPrivileged(&psTcb)) {
SetLastError(ERROR_ACCESS_DENIED);
a->fSuccess = FALSE;
} else {
gdwServicesProcessId = a->dwProcessId;
a->fSuccess = TRUE;
}
LeaveCrit();
END_LPC_RECV();
}
#if defined(FE_IME)
/*
* return TRUE if it's IME window
* History:
* 06-05-96 KazuM Created.
*/
BOOL IsImeWindow(HWND hwnd)
{
int num;
WCHAR ClassName[16];
DWORD ClassStyle;
num = GetClassName(hwnd, ClassName, sizeof(ClassName) / sizeof(WCHAR) - 1);
if (num == 0)
return FALSE;
ClassName[num] = L'\0';
if (wcsncmp(ClassName, L"IME", 3) == 0)
return TRUE;
ClassStyle = GetClassLong(hwnd, GCL_STYLE);
if (ClassStyle & CS_IME)
return TRUE;
else
return FALSE;
}
#endif