1915 lines
63 KiB
C
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 |