You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3117 lines
104 KiB
3117 lines
104 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: exitwin.c
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* History:
|
|
* 07-23-92 ScottLu Created.
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
#include <wchar.h>
|
|
#include <regstr.h>
|
|
#include <reason.h>
|
|
|
|
#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 CCHBODYMAX 512
|
|
|
|
#define CSR_THREAD_SHUTDOWNSKIP 0x00000008
|
|
#define CSR_THREAD_HANGREPORTED 0x00000010
|
|
|
|
/*
|
|
* SrvRecordShutdownReason globals
|
|
*/
|
|
ULONG g_ShutdownState;
|
|
ULONG g_DirtyShutdownMax = 1;
|
|
HMODULE g_SnapShotDllHandle;
|
|
LONG g_SnapShot = 1;
|
|
|
|
/*
|
|
* Snapshot dll entry function.
|
|
*/
|
|
typedef ULONG (*SNAPSHOTFUNC)(DWORD dwStrings, LPCTSTR *lpStrings, PLONG MaxBuffSize, LPTSTR SnapShotBuff);
|
|
|
|
BOOL WowExitTask(PCSR_THREAD pcsrt);
|
|
NTSTATUS UserClientShutdown(PCSR_PROCESS pcsrp, ULONG dwFlags, BOOLEAN fFirstPass);
|
|
BOOL CancelExitWindows(VOID);
|
|
|
|
/***************************************************************************\
|
|
* _ExitWindowsEx
|
|
*
|
|
* 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.
|
|
\***************************************************************************/
|
|
NTSTATUS _ExitWindowsEx(
|
|
PCSR_THREAD pcsrt,
|
|
UINT dwFlags)
|
|
{
|
|
LUID luidCaller;
|
|
NTSTATUS Status;
|
|
|
|
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) {
|
|
RIPMSG2(RIP_WARNING,
|
|
"Process 0x%x tried to call ExitWindowsEx with flags 0x%x",
|
|
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) {
|
|
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:
|
|
|
|
|
|
if (!CancelExitWindows()) {
|
|
Status = STATUS_PENDING;
|
|
goto fastexit;
|
|
} else {
|
|
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);
|
|
if (Status == STATUS_CANCELLED && IsSETEnabled()) {
|
|
SHUTDOWN_REASON sr;
|
|
sr.cbSize = sizeof(SHUTDOWN_REASON);
|
|
sr.uFlags = dwFlags;
|
|
sr.dwReasonCode = 0;
|
|
sr.fShutdownCancelled = TRUE;
|
|
sr.dwEventType = SR_EVENT_EXITWINDOWS;
|
|
sr.lpszComment = NULL;
|
|
|
|
/*
|
|
* Record the fact that the shutdown was cancelled.
|
|
*/
|
|
RecordShutdownReason(&sr);
|
|
}
|
|
|
|
/*
|
|
* Tell win32k.sys we're done.
|
|
*/
|
|
NtUserSetInformationThread(pcsrt->ThreadHandle, UserThreadEndShutdown, &Status, sizeof(Status));
|
|
|
|
EnterCrit();
|
|
gdwThreadEndSession = 0;
|
|
NtSetEvent(gheventCancelled, NULL);
|
|
LeaveCrit();
|
|
|
|
fastexit:
|
|
CsrRevertToSelf();
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* UserClientShutdown
|
|
*
|
|
* 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.
|
|
\***************************************************************************/
|
|
NTSTATUS UserClientShutdown(
|
|
PCSR_PROCESS pcsrp,
|
|
ULONG dwFlags,
|
|
BOOLEAN fFirstPass)
|
|
{
|
|
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 | CSR_THREAD_HANGREPORTED);
|
|
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;
|
|
|
|
CsrReferenceThread(Thread);
|
|
CsrUnlockProcess(Process);
|
|
|
|
Status = NtUserQueryInformationThread(Thread->ThreadHandle,
|
|
UserThreadShutdownInformation,
|
|
&ShutdownInfo,
|
|
sizeof(ShutdownInfo),
|
|
NULL);
|
|
|
|
CsrLockProcessByClientId(pcsrp->ClientId.UniqueProcess, &Process);
|
|
/*
|
|
* When we release the process structure lock, Thread can go away.
|
|
* Since we already ref'ed it, we're safe, but once we deref here
|
|
* its ref count can go to zero and the memory will be freed, in
|
|
* which case we must be careful not to touch it again.
|
|
*/
|
|
if (CsrDereferenceThread(Thread) == 0) {
|
|
continue;
|
|
}
|
|
|
|
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 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 | CSR_THREAD_HANGREPORTED);
|
|
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;
|
|
|
|
CsrReferenceThread(Thread);
|
|
CsrUnlockProcess(Process);
|
|
|
|
Thread->Flags |= CSR_THREAD_SHUTDOWNSKIP;
|
|
ShutdownInfo.dwFlags = dwClientFlags;
|
|
|
|
Status = NtUserQueryInformationThread(Thread->ThreadHandle,
|
|
UserThreadShutdownInformation, &ShutdownInfo, sizeof(ShutdownInfo), NULL);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
goto SkipThread;
|
|
|
|
if (ShutdownInfo.StatusShutdown == SHUTDOWN_UNKNOWN_PROCESS ||
|
|
!(ShutdownInfo.dwFlags & USER_THREAD_GUI))
|
|
goto SkipThread;
|
|
|
|
/*
|
|
* Send the end session messages to the thread.
|
|
*/
|
|
|
|
/*
|
|
* 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);
|
|
|
|
SkipThread:
|
|
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;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* WMCSCallback
|
|
*
|
|
* 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.
|
|
\***************************************************************************/
|
|
VOID CALLBACK WMCSCallback(
|
|
HWND hwnd,
|
|
UINT uMsg,
|
|
ULONG_PTR dwData,
|
|
LRESULT lResult)
|
|
{
|
|
PWMCSDATA pwmcsd = (PWMCSDATA)dwData;
|
|
|
|
UNREFERENCED_PARAMETER(hwnd);
|
|
UNREFERENCED_PARAMETER(uMsg);
|
|
|
|
if (pwmcsd->dwFlags & WMCSD_IGNORE) {
|
|
LocalFree(pwmcsd);
|
|
return;
|
|
}
|
|
|
|
pwmcsd->dwFlags |= WMCSD_REPLY;
|
|
pwmcsd->dwRet = (DWORD)lResult;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GetInputWindow
|
|
*
|
|
* 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.
|
|
\***************************************************************************/
|
|
HWND GetInputWindow(
|
|
PCSR_THREAD pcsrt,
|
|
HWND hwnd)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GetApplicationText
|
|
*
|
|
* Gets the text that identifies the given window or thread
|
|
*
|
|
* 08-01-97 GerardoB Created.
|
|
\***************************************************************************/
|
|
VOID GetApplicationText(
|
|
HWND hwnd,
|
|
HANDLE hThread,
|
|
WCHAR *pwcText,
|
|
UINT uLen)
|
|
{
|
|
/*
|
|
* 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 the window's title; let's try the thread's name.
|
|
*/
|
|
NtUserQueryInformationThread(hThread, UserThreadTaskName,
|
|
pwcText, uLen * sizeof(WCHAR), NULL);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* ReportHang
|
|
*
|
|
* This function launches an intermediate app (dumprep.exe) which packages up
|
|
* the hang information & ships it up to MS. We create an event and wait on
|
|
* it so that we know when the minidump information has been grabbed from the
|
|
* app.
|
|
*
|
|
* 08-31-00 DerekM Created
|
|
\***************************************************************************/
|
|
VOID ReportHang(
|
|
CLIENT_ID *pcidToKill)
|
|
{
|
|
PROCESS_SESSION_INFORMATION psi;
|
|
SECURITY_ATTRIBUTES sa;
|
|
SECURITY_DESCRIPTOR sd;
|
|
PCSR_THREAD pcsrt = CSR_SERVER_QUERYCLIENTTHREAD();
|
|
NTSTATUS Status;
|
|
HANDLE rghWait[2] = { NULL, NULL };
|
|
HANDLE hProc = NULL;
|
|
WCHAR wszEvent[MAX_PATH], *pwszSuffix;
|
|
DWORD dw, dwTimeout, dwStartWait;
|
|
BOOL fIs64Bit = FALSE;
|
|
#ifdef _WIN64
|
|
ULONG_PTR Wow64Info = 0;
|
|
HANDLE hProcKill = NULL;
|
|
#endif
|
|
|
|
#if defined(_DEBUG) || defined(DEBUG)
|
|
dwTimeout = 600000; // 10 minutes
|
|
#else
|
|
dwTimeout = 120000; // 2 minutes
|
|
#endif
|
|
|
|
// we're going to launch dwwin in the context of the interactive user that
|
|
// is logged on to the killing process's session. So we need to figure out
|
|
// what session it's in.
|
|
// If any of these fail, we have to bail cuz otherwise we'd have to create
|
|
// an instance of dwwin.exe in local system context, and since dwwin.exe
|
|
// can launch helpcenter, this is bad.
|
|
hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
|
|
HandleToLong(pcsrt->ClientId.UniqueProcess));
|
|
if (hProc == NULL) {
|
|
RIPMSG2(RIP_WARNING,
|
|
"HangReporting: Couldn't open killing process (pid: %d) (err: %08x)\n",
|
|
HandleToLong(pcsrt->ClientId.UniqueProcess), GetLastError());
|
|
goto done;
|
|
}
|
|
|
|
Status = NtQueryInformationProcess(hProc, ProcessSessionInformation, &psi,
|
|
sizeof(psi), NULL);
|
|
if (NT_SUCCESS(Status) == FALSE) {
|
|
RIPMSG2(RIP_WARNING,
|
|
"HangReporting: Couldn't get the session ID (pid: %d) (err: %08x)\n",
|
|
HandleToLong(pcsrt->ClientId.UniqueProcess),
|
|
RtlNtStatusToDosError(Status));
|
|
goto done;
|
|
}
|
|
|
|
#ifdef _WIN64
|
|
// need to determine if we're a Wow64 process so we can build the appropriate
|
|
// signatures...
|
|
hProcKill = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
|
|
HandleToLong(pcidToKill->UniqueProcess));
|
|
if (hProcKill == NULL) {
|
|
RIPMSG2(RIP_WARNING,
|
|
"HangReporting: Couldn't open dying process (pid: %d) (err: %08x)\n",
|
|
HandleToLong(pcidToKill->UniqueProcess), GetLastError());
|
|
goto done;
|
|
}
|
|
|
|
Status = NtQueryInformationProcess(hProcKill, ProcessWow64Information,
|
|
&Wow64Info, sizeof(Wow64Info), NULL);
|
|
if (NT_SUCCESS(Status) == FALSE) {
|
|
RIPMSG2(RIP_WARNING,
|
|
"HangReporting: Couldn't get Wow64 info (pid: %d) (err: %08x)\n",
|
|
HandleToLong(pcidToKill->UniqueProcess),
|
|
RtlNtStatusToDosError(Status));
|
|
goto done;
|
|
}
|
|
|
|
fIs64Bit = (Wow64Info == 0);
|
|
#endif
|
|
|
|
// Because of a bug where CreateProcessAsUser doesn't want to work from
|
|
// the csrss process, we have to have a remote process sitting around
|
|
// waiting on a pipe. It will call CreateProcessAsUser (as well as
|
|
// determine the correct token for the session).
|
|
//
|
|
// Note that it only accepts requests from processes running as local
|
|
// system.
|
|
|
|
// Since a remote process does the creation of dumprep.exe, we need to
|
|
// used a named event instead of relying on dumprep to inherit the event
|
|
// handle.
|
|
dw = swprintf(wszEvent, L"Global\\%d%x%x%x%x%x", psi.SessionId,
|
|
GetTickCount(), HandleToLong(pcsrt->ClientId.UniqueProcess),
|
|
HandleToLong(pcsrt->ClientId.UniqueThread),
|
|
HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess),
|
|
HandleToUlong(NtCurrentTeb()->ClientId.UniqueThread));
|
|
pwszSuffix = wszEvent + dw;
|
|
|
|
// make sure to create this event with a NULL DACL so a generic usermode
|
|
// process has access to it.
|
|
Status = RtlCreateSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
|
|
if (NT_SUCCESS(Status) == FALSE) {
|
|
RIPMSG1(RIP_WARNING, "HangReporting: could not create SD (err: %08x)\n",
|
|
RtlNtStatusToDosError(Status));
|
|
goto done;
|
|
}
|
|
|
|
// This is implemented in reclient.h & creates a SD with creator &
|
|
// LocalSystem having full rights & world & anonymous having sync
|
|
// rights.
|
|
Status = AllocDefSD(&sd,
|
|
STANDARD_RIGHTS_ALL | GENERIC_ALL | EVENT_ALL_ACCESS,
|
|
EVENT_MODIFY_STATE | SYNCHRONIZE | GENERIC_READ);
|
|
if (NT_SUCCESS(Status) == FALSE) {
|
|
RIPMSG1(RIP_WARNING, "HangReporting: could not create SD (err: %08x)\n",
|
|
RtlNtStatusToDosError(Status));
|
|
goto done;
|
|
}
|
|
|
|
ZeroMemory(&sa, sizeof(sa));
|
|
sa.nLength = sizeof(sa);
|
|
sa.lpSecurityDescriptor = &sd;
|
|
|
|
// Need an event so we know when the app we're killing is no longer
|
|
// necessary. If the event already exists, try to create a new one.
|
|
// But only do this a maximum of 7 times.
|
|
for (dw = 0; dw < 7; dw++) {
|
|
rghWait[0] = CreateEventW(&sa, TRUE, FALSE, wszEvent);
|
|
if (rghWait[0] == NULL) {
|
|
RIPMSG1(RIP_WARNING,
|
|
"HangReporting: Error creating wait event (err: %08x)\n",
|
|
GetLastError());
|
|
goto done;
|
|
}
|
|
|
|
if (GetLastError() != ERROR_ALREADY_EXISTS) {
|
|
break;
|
|
}
|
|
|
|
// Sleep for a millisecond to make the result of GetTickCount() even
|
|
// more unpredictable...
|
|
Sleep(1);
|
|
_ltow(GetTickCount(), pwszSuffix, 16);
|
|
}
|
|
|
|
if (dw >= 7) {
|
|
RIPMSG0(RIP_WARNING, "HangReporting: Could not find unique wait event name\n");
|
|
goto done;
|
|
}
|
|
|
|
FreeDefSD(&sd);
|
|
|
|
if (StartHangReport(psi.SessionId, wszEvent,
|
|
HandleToLong(pcidToKill->UniqueProcess),
|
|
HandleToLong(pcidToKill->UniqueThread),
|
|
fIs64Bit, &rghWait[1]) == FALSE)
|
|
{
|
|
RIPMSG1(RIP_WARNING,
|
|
"HangReporting: StartHangReport failed (err: %08x)\n",
|
|
GetLastError());
|
|
goto done;
|
|
}
|
|
|
|
// use MsgWaitForMultipleObjects in case this thread is doing UI processing
|
|
// Not really likely, but you never know. Anyway, only wait 2 minutes for
|
|
// dumprep to generate the minidump. If it still hasn't done it by then,
|
|
// it isn't likely to ever finish.
|
|
dwStartWait = GetTickCount();
|
|
for (;;)
|
|
{
|
|
dw = MsgWaitForMultipleObjects(2, rghWait, FALSE, dwTimeout, QS_ALLINPUT);
|
|
if (dw == WAIT_OBJECT_0 + 2)
|
|
{
|
|
DWORD dwNow;
|
|
DWORD dwSub;
|
|
MSG msg;
|
|
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
dwNow = GetTickCount();
|
|
if (dwNow < dwStartWait)
|
|
dwSub = ((DWORD)-1 - dwStartWait) + dwNow;
|
|
else
|
|
dwSub = dwNow - dwStartWait;
|
|
|
|
if (dwSub > dwTimeout)
|
|
dwTimeout = 0;
|
|
else
|
|
dwTimeout -= dwSub;
|
|
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
done:
|
|
#ifdef _WIN64
|
|
if (hProcKill != NULL) {
|
|
CloseHandle(hProcKill);
|
|
}
|
|
#endif
|
|
if (hProc != NULL) {
|
|
CloseHandle(hProc);
|
|
}
|
|
if (rghWait[0] != NULL) {
|
|
CloseHandle(rghWait[0]);
|
|
}
|
|
if (rghWait[1] != NULL) {
|
|
CloseHandle(rghWait[1]);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* ThreadShutdownNotify
|
|
*
|
|
* 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
|
|
* 08-15-00 JasonSch Added code to limit number of CSRSS worker threads
|
|
* stuck in _EndTask to 8.
|
|
\***************************************************************************/
|
|
DWORD ThreadShutdownNotify(
|
|
DWORD dwClientFlags,
|
|
ULONG_PTR dwThread,
|
|
LPARAM lParam)
|
|
{
|
|
HWND hwnd, hwndOwner, hwndDlg;
|
|
PWMCSDATA pwmcsd = NULL;
|
|
ENDDLGPARAMS edp;
|
|
DWORD dwRet, dwRealTimeout, dwTimeout, dwStartTiming, dwCmd;
|
|
MSG msg;
|
|
PCSR_THREAD pcsrt;
|
|
HANDLE hThread;
|
|
BOOL fEndTaskNow = FALSE;
|
|
static DWORD dwTSNThreads = 0;
|
|
|
|
#define ESMH_CANCELEVENT 0
|
|
#define ESMH_THREAD 1
|
|
#define ESMH_HANDLECOUNT 2
|
|
HANDLE ahandles[ESMH_HANDLECOUNT];
|
|
|
|
if (dwTSNThreads > TSN_MAX_THREADS) {
|
|
/*
|
|
* If we've already reached our limit in terms of CSRSS threads stuck
|
|
* in this function, "fail" this call.
|
|
*/
|
|
return TSN_USERSAYSCANCEL;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
#ifdef 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 // FE_IME
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
|
|
/*
|
|
* 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 thread is now officially going to be "stuck" in TSN. Increment our
|
|
* count of threads so disposed.
|
|
*/
|
|
++dwTSNThreads;
|
|
|
|
/*
|
|
* 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. If it times 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;
|
|
if ((dwClientFlags & WMCS_ENDTASK) != 0 &&
|
|
(edp.dwFlags & EDPF_HUNG) != 0) {
|
|
THREAD_BASIC_INFORMATION tbi;
|
|
CLIENT_ID *pcidToKill = NULL;
|
|
|
|
if ((dwClientFlags & WMCS_CONSOLE) != 0) {
|
|
if (NtQueryInformationThread(hThread,
|
|
ThreadBasicInformation,
|
|
&tbi,
|
|
sizeof(tbi),
|
|
NULL) == STATUS_SUCCESS) {
|
|
pcidToKill = &(tbi.ClientId);
|
|
}
|
|
} else {
|
|
pcidToKill = &(pcsrt->ClientId);
|
|
}
|
|
|
|
if (pcidToKill != NULL) {
|
|
if (!(pcsrt->Flags & CSR_THREAD_HANGREPORTED))
|
|
{
|
|
// insure that we only call this ONCE
|
|
pcsrt->Flags |= CSR_THREAD_HANGREPORTED;
|
|
ReportHang(pcidToKill);
|
|
}
|
|
}
|
|
}
|
|
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 {
|
|
/*
|
|
* If the window's gone and the thread is still responsive, then
|
|
* this must be an app that just hides its window on WM_CLOSE
|
|
* (e.g., MSN Instant Messenger). Let's nuke the app w/o
|
|
* bringing up the EndTask dialog.
|
|
*/
|
|
if (!IsWindow(hwnd)) {
|
|
DWORD dwThreadHung;
|
|
|
|
/*
|
|
* Ask the kernel if the thread is hung.
|
|
*/
|
|
dwThreadHung = gCmsHungAppTimeout;
|
|
NtUserQueryInformationThread(pcsrt->ThreadHandle,
|
|
UserThreadHungStatus,
|
|
&dwThreadHung,
|
|
sizeof(dwThreadHung),
|
|
NULL);
|
|
if (!dwThreadHung) {
|
|
dwCmd = TSN_APPSAYSOK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 cancelling, let's name the app that didn'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);
|
|
RIPMSG3(RIP_WARNING, "Log off canceled by pid:%#lx tid:%#lx - '%ws'.\n",
|
|
HandleToUlong(pcsrt->ClientId.UniqueProcess),
|
|
HandleToUlong(pcsrt->ClientId.UniqueThread),
|
|
pwcText);
|
|
}
|
|
#endif // DBG
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
--dwTSNThreads;
|
|
return dwCmd;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* SetEndTaskDlgStatus
|
|
*
|
|
* Displays the appropiate message and shows the dialog
|
|
*
|
|
* 03-11-97 GerardoB Created
|
|
\***************************************************************************/
|
|
VOID SetEndTaskDlgStatus(
|
|
ENDDLGPARAMS *pedp,
|
|
HWND hwndDlg,
|
|
UINT uStrId,
|
|
BOOL fInit)
|
|
{
|
|
BOOL f, fIsWaiting, fWasWaiting;
|
|
WCHAR *pwcText;
|
|
|
|
fWasWaiting = (pedp->uStrId == STR_ENDTASK_WAIT);
|
|
fIsWaiting = (pedp->dwFlags & EDPF_WAIT) != 0;
|
|
|
|
/*
|
|
* 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 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* EndTaskDlgProc
|
|
*
|
|
* 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.
|
|
\***************************************************************************/
|
|
INT_PTR APIENTRY EndTaskDlgProc(
|
|
HWND hwndDlg,
|
|
UINT msg,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
ENDDLGPARAMS* pedp;
|
|
WCHAR achTitle[CCHBODYMAX];
|
|
WCHAR *pwcText, *pwcTemp;
|
|
UINT uLen;
|
|
UINT uStrId;
|
|
PAINTSTRUCT ps;
|
|
HDC hdc, hdcMem;
|
|
BOOL fIsInput, fWasInput;
|
|
int iOldLayout;
|
|
|
|
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);
|
|
iOldLayout = GetLayout(hdc);
|
|
|
|
if (iOldLayout != GDI_ERROR) {
|
|
SetLayout(hdc, iOldLayout|LAYOUT_BITMAPORIENTATIONPRESERVED);
|
|
}
|
|
|
|
DrawIcon(hdc, ETD_XICON, ETD_YICON, pedp->hIcon);
|
|
|
|
if (iOldLayout != GDI_ERROR) {
|
|
SetLayout(hdc, iOldLayout);
|
|
}
|
|
|
|
/*
|
|
* 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 the 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;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* _EndTask
|
|
*
|
|
* 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 _EndTask(
|
|
HWND hwnd,
|
|
BOOL fMeanKill)
|
|
{
|
|
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;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
Status = CsrLockThreadByClientId(LongToHandle(dwThreadId), &pcsrtKill);
|
|
if (!NT_SUCCESS(Status)) {
|
|
/*
|
|
* This is probably the ghost thread, which CSRSS doesn't know about
|
|
* (as it's created via RtlCreateUserThread, which doesn't LPC into
|
|
* CSRSS like regular CreateThread does). When the ghost window gets
|
|
* the WM_CLOSE it'll handle removing the real window and thread. If
|
|
* this *isn't* a ghost window, then no real harm done, so we post
|
|
* no matter what.
|
|
*/
|
|
PostMessage(hwnd, WM_CLOSE, 0, 0);
|
|
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;
|
|
}
|
|
Status = NtTerminateProcess(pcsrtKill->Process->ProcessHandle, ExitStatus);
|
|
if (!NT_SUCCESS(Status) && Status != STATUS_PROCESS_IS_TERMINATING) {
|
|
|
|
bDoBlock = TRUE;
|
|
} else {
|
|
bDoBlock = FALSE;
|
|
}
|
|
} else {
|
|
Status = NtTerminateProcess(pcsrtKill->Process->ProcessHandle, DBG_TERMINATE_PROCESS);
|
|
if (!NT_SUCCESS(Status) && Status != STATUS_PROCESS_IS_TERMINATING) {
|
|
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;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* WowExitTask
|
|
*
|
|
* 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.
|
|
\***************************************************************************/
|
|
BOOL WowExitTask(
|
|
PCSR_THREAD pcsrt)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* InternalWaitCancel
|
|
*
|
|
* Console calls this to wait for objects or shutdown to be cancelled
|
|
*
|
|
* 29-Oct-1992 mikeke Created
|
|
\***************************************************************************/
|
|
DWORD InternalWaitCancel(
|
|
HANDLE handle,
|
|
DWORD dwMilliseconds)
|
|
{
|
|
HANDLE ahandle[2];
|
|
|
|
ahandle[0] = handle;
|
|
ahandle[1] = gheventCancel;
|
|
|
|
return WaitForMultipleObjects(2, ahandle, FALSE, dwMilliseconds);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* InternalCreateCallbackThread
|
|
*
|
|
* 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.
|
|
\***************************************************************************/
|
|
|
|
HANDLE InternalCreateCallbackThread(
|
|
HANDLE hProcess,
|
|
ULONG_PTR lpfn,
|
|
ULONG_PTR dwData)
|
|
{
|
|
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->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->fForce);
|
|
|
|
petm->dwLastError = Teb->LastErrorValue;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* IsPrivileged
|
|
*
|
|
* Check to see if the client has the specified privileges
|
|
*
|
|
* History:
|
|
* 01-02-91 JimA Created.
|
|
\***************************************************************************/
|
|
BOOL IsPrivileged(
|
|
PPRIVILEGE_SET ppSet)
|
|
{
|
|
HANDLE hToken;
|
|
NTSTATUS Status;
|
|
BOOLEAN bResult = FALSE;
|
|
|
|
/*
|
|
* Impersonate the client.
|
|
*/
|
|
if (!CsrImpersonateClient(NULL)) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Open the client's token.
|
|
*/
|
|
Status = NtOpenThreadToken(NtCurrentThread(),
|
|
TOKEN_QUERY,
|
|
TRUE,
|
|
&hToken);
|
|
if (NT_SUCCESS(Status)) {
|
|
UNICODE_STRING strSubSystem;
|
|
|
|
RtlInitUnicodeString(&strSubSystem, L"USER32");
|
|
|
|
/*
|
|
* 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));
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* SrvRegisterServicesProcess
|
|
*
|
|
* Register the services process.
|
|
*
|
|
* History:
|
|
* 05-05-95 BradG Created.
|
|
\***************************************************************************/
|
|
ULONG SrvRegisterServicesProcess(
|
|
IN OUT PCSR_API_MSG m,
|
|
IN OUT PCSR_REPLY_STATUS ReplyStatus)
|
|
{
|
|
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();
|
|
}
|
|
|
|
#ifdef FE_IME
|
|
/***************************************************************************\
|
|
* IsImeWindow
|
|
*
|
|
* Returns TRUE if it's an IME window.
|
|
*
|
|
* History:
|
|
* 06-05-96 KazuM Created.
|
|
\***************************************************************************/
|
|
BOOL
|
|
IsImeWindow(
|
|
HWND hwnd)
|
|
{
|
|
int num;
|
|
WCHAR ClassName[16];
|
|
|
|
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;
|
|
}
|
|
|
|
return (GetClassLong(hwnd, GCL_STYLE) & CS_IME) != 0;
|
|
}
|
|
#endif // FE_IME
|
|
|
|
/***************************************************************************\
|
|
* CancelExitWindows
|
|
*
|
|
* Cancel any logoff/shutdown that is in progress. This is called from _ExitWindowsEx
|
|
* to cancel an existing exitwindows call if a new call arrives with a different sid.
|
|
* This call is also used for Personal Terminal Services single session scenatio so
|
|
* that a force logoff can be initiated once the existing ExitWindows call is
|
|
* cancelled.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
BOOL CancelExitWindows(
|
|
VOID)
|
|
{
|
|
LARGE_INTEGER li;
|
|
|
|
/*
|
|
* 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) {
|
|
LeaveCrit();
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* If no one will set gheventCancelled, don't wait.
|
|
*/
|
|
if (gdwThreadEndSession == 0) {
|
|
LeaveCrit();
|
|
return TRUE;
|
|
}
|
|
|
|
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);
|
|
LeaveCrit();
|
|
return FALSE;
|
|
}
|
|
LeaveCrit();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* TestShutdownPrivilege
|
|
*
|
|
* Test to see if the clent has shutdown privilege.
|
|
*
|
|
* History:
|
|
* 02-02-20 qingboz Created.
|
|
\***************************************************************************/
|
|
BOOL
|
|
TestShutdownPrivilege(
|
|
HANDLE UserToken)
|
|
{
|
|
NTSTATUS Status;
|
|
LUID LuidPrivilege = RtlConvertLongToLuid(SE_SHUTDOWN_PRIVILEGE);
|
|
LUID TokenPrivilege;
|
|
ULONG BytesRequired;
|
|
ULONG i;
|
|
BOOL bHasPrivilege = FALSE;
|
|
BOOL bNetWork = FALSE;
|
|
PSID NetworkSid = NULL;
|
|
PTOKEN_PRIVILEGES Privileges = NULL;
|
|
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
|
|
|
|
Status = RtlAllocateAndInitializeSid(&NtAuthority,
|
|
1, SECURITY_NETWORK_RID,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
&NetworkSid );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
NetworkSid = NULL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (CheckTokenMembership(UserToken, NetworkSid, &bNetWork)) {
|
|
if (bNetWork) {
|
|
LuidPrivilege = RtlConvertLongToLuid(SE_REMOTE_SHUTDOWN_PRIVILEGE);
|
|
}
|
|
} else {
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = NtQueryInformationToken(
|
|
UserToken,
|
|
TokenPrivileges,
|
|
NULL,
|
|
0,
|
|
&BytesRequired
|
|
);
|
|
|
|
if (Status != STATUS_BUFFER_TOO_SMALL) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
Privileges = (PTOKEN_PRIVILEGES)LocalAlloc(LPTR, BytesRequired);
|
|
if (!Privileges) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = NtQueryInformationToken(
|
|
UserToken,
|
|
TokenPrivileges,
|
|
Privileges,
|
|
BytesRequired,
|
|
&BytesRequired
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
for (i=0; i<Privileges->PrivilegeCount; i++) {
|
|
TokenPrivilege = *((LUID UNALIGNED *) &Privileges->Privileges[i].Luid);
|
|
if (RtlEqualLuid(&TokenPrivilege, &LuidPrivilege)) {
|
|
bHasPrivilege = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
if (NetworkSid) {
|
|
RtlFreeSid(NetworkSid);
|
|
}
|
|
|
|
if (Privileges) {
|
|
LocalFree(Privileges);
|
|
}
|
|
|
|
return bHasPrivilege;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GetUserSid
|
|
*
|
|
* Allocs space for the user sid, fills it in and returns a pointer.
|
|
*
|
|
* Returns pointer to sid or NULL on failure.
|
|
*
|
|
* History:
|
|
* 02-02-20 qingboz Created.
|
|
\***************************************************************************/
|
|
PSID GetUserSid(
|
|
HANDLE UserToken)
|
|
{
|
|
PTOKEN_USER pUser;
|
|
PSID pSid;
|
|
DWORD BytesRequired = 200;
|
|
NTSTATUS status;
|
|
|
|
if (UserToken == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Allocate space for the user info
|
|
//
|
|
|
|
pUser = (PTOKEN_USER)LocalAlloc(LMEM_FIXED, BytesRequired);
|
|
|
|
if (pUser == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Read in the UserInfo
|
|
//
|
|
|
|
status = NtQueryInformationToken(
|
|
UserToken,
|
|
TokenUser,
|
|
pUser,
|
|
BytesRequired,
|
|
&BytesRequired
|
|
);
|
|
|
|
if (status == STATUS_BUFFER_TOO_SMALL) {
|
|
|
|
//
|
|
// Allocate a bigger buffer and try again.
|
|
//
|
|
PTOKEN_USER pTemp = pUser;
|
|
pUser = LocalReAlloc(pUser, BytesRequired, LMEM_MOVEABLE);
|
|
if (pUser == NULL) {
|
|
LocalFree( pTemp );
|
|
return NULL;
|
|
}
|
|
|
|
status = NtQueryInformationToken(
|
|
UserToken,
|
|
TokenUser,
|
|
pUser,
|
|
BytesRequired,
|
|
&BytesRequired
|
|
);
|
|
|
|
}
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LocalFree(pUser);
|
|
return NULL;
|
|
}
|
|
|
|
BytesRequired = RtlLengthSid(pUser->User.Sid);
|
|
pSid = LocalAlloc(LMEM_FIXED, BytesRequired);
|
|
if (pSid == NULL) {
|
|
LocalFree(pUser);
|
|
return NULL;
|
|
}
|
|
|
|
status = RtlCopySid(BytesRequired, pSid, pUser->User.Sid);
|
|
|
|
LocalFree(pUser);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
LocalFree(pSid);
|
|
pSid = NULL;
|
|
}
|
|
|
|
return pSid;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* SrvRecordShutdownReason
|
|
*
|
|
* Process RecordShutdownReason request from clients. This will log an event
|
|
* in the event log, and optionally take a system snapshot.
|
|
*
|
|
* History:
|
|
* 01-12-12 qingboz Created.
|
|
* 02-02-20 qingboz Added privilege check and moved some stuff from
|
|
* client to server (such as user name and SID).
|
|
\***************************************************************************/
|
|
ULONG
|
|
SrvRecordShutdownReason(
|
|
IN OUT PCSR_API_MSG m,
|
|
IN OUT PCSR_REPLY_STATUS ReplyStatus)
|
|
{
|
|
PRECORDSHUTDOWNREASONMSG prm = (PRECORDSHUTDOWNREASONMSG)&m->u.ApiMessageData;
|
|
LPWSTR lpStrings[8];
|
|
WORD wEventType;
|
|
WORD wStringCnt = 0;
|
|
WCHAR szReasonCode[32];
|
|
BOOL bRet = FALSE;
|
|
BOOL bReportEvent = FALSE;
|
|
BOOL bIsCrsssOrWinlogon = FALSE;
|
|
BOOL bIsCancelEvent = FALSE;
|
|
struct {
|
|
DWORD Reason;
|
|
PWCHAR SnapShotBuf;
|
|
} SnapShot;
|
|
UINT SnapShotSize = 0;
|
|
HANDLE hEventLog = NULL;
|
|
|
|
LPWSTR lpszUserName = NULL;
|
|
LPWSTR lpszUserDomain = NULL;
|
|
LPWSTR lpszComputerName = NULL;
|
|
LPWSTR lpszReasonTitle = NULL;
|
|
LPWSTR lpszComment = NULL;
|
|
DWORD dwComputerNameLen = MAX_COMPUTERNAME_LENGTH + 1;
|
|
DWORD dwUserNameLen = MAX_PATH + 1;
|
|
DWORD dwUserDomainLen = MAX_PATH + 1;
|
|
DWORD dwReasonTitleLen = MAX_REASON_NAME_LEN;
|
|
PSID psid;
|
|
SID_NAME_USE eUse;
|
|
HANDLE hToken;
|
|
|
|
UNREFERENCED_PARAMETER(ReplyStatus);
|
|
|
|
/*
|
|
* Need to impersonate in order to check privilege and get user name.
|
|
*/
|
|
if (!CsrImpersonateClient(NULL)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (!NT_SUCCESS(NtOpenThreadToken(NtCurrentThread(), TOKEN_QUERY, (BOOLEAN)TRUE, &hToken))) {
|
|
CsrRevertToSelf();
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Find out whether this is csrss/winlogon calling us.
|
|
*/
|
|
if (m->h.ClientId.UniqueProcess == NtCurrentTeb()->ClientId.UniqueProcess
|
|
|| HandleToUlong(m->h.ClientId.UniqueProcess) == gIdLogon) {
|
|
bIsCrsssOrWinlogon = TRUE;
|
|
}
|
|
|
|
/*
|
|
* We just need the token, so we can revert now.
|
|
*/
|
|
CsrRevertToSelf();
|
|
|
|
// Check for privilege.
|
|
if (!TestShutdownPrivilege(hToken)) {
|
|
NtClose(hToken);
|
|
return FALSE;
|
|
}
|
|
|
|
// Get SID for reporting event.
|
|
psid = GetUserSid(hToken);
|
|
NtClose(hToken); // done with the token.
|
|
if (!psid) {
|
|
return FALSE;
|
|
}
|
|
|
|
SnapShot.SnapShotBuf = NULL; // so cleanup won't free an uninitialized buffer when memory alloc fails later.
|
|
|
|
/*
|
|
* For cancel event we don't need capature buffer, so we only validate
|
|
* the buffer for non-cancelling events.
|
|
*/
|
|
if (prm->dwEventType == SR_EVENT_EXITWINDOWS && prm->fShutdownCancelled
|
|
|| prm->dwEventType == SR_EVENT_INITIATE_CLEAN_ABORT) {
|
|
bIsCancelEvent = TRUE;
|
|
} else {
|
|
if (!m->CaptureBuffer) {
|
|
goto Cleanup;
|
|
}
|
|
if (!CsrValidateMessageBuffer(m, &prm->psr, sizeof(SHUTDOWN_REASON), sizeof(BYTE))) {
|
|
goto Cleanup;
|
|
}
|
|
if (prm->dwProcessNameLen == 0 || prm->dwProcessNameLen - 1 > MAX_PATH
|
|
|| !CsrValidateMessageBuffer(m, &prm->pwchProcessName, prm->dwProcessNameLen, sizeof(WCHAR))) {
|
|
goto Cleanup;
|
|
}
|
|
if (prm->dwShutdownTypeLen == 0 || prm->dwShutdownTypeLen > SHUTDOWN_TYPE_LEN
|
|
|| !CsrValidateMessageBuffer(m, &prm->pwchShutdownType, prm->dwShutdownTypeLen, sizeof(WCHAR))) {
|
|
goto Cleanup;
|
|
}
|
|
if (prm->dwCommentLen
|
|
&& (prm->dwCommentLen > MAX_REASON_COMMENT_LEN || !CsrValidateMessageBuffer(m, &prm->pwchComment, prm->dwCommentLen, sizeof(WCHAR)))) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
SnapShot.Reason = prm->psr->dwReasonCode;
|
|
prm->pwchProcessName[prm->dwProcessNameLen - 1] = 0;
|
|
prm->pwchShutdownType[prm->dwShutdownTypeLen - 1] = 0;
|
|
if (prm->dwCommentLen) {
|
|
prm->pwchComment[prm->dwCommentLen - 1] = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function could be called multiple times during a single shutdown,
|
|
* we need to make sure that we don't log two shutdown events. We also
|
|
* need to make sure max one dirty event per reboot.
|
|
*/
|
|
if (prm->dwEventType == SR_EVENT_DIRTY) {
|
|
if (InterlockedCompareExchange((volatile LONG*)&g_DirtyShutdownMax, 0L, 1L)) {
|
|
bReportEvent = TRUE;
|
|
}
|
|
} else {
|
|
if (prm->dwEventType == SR_EVENT_EXITWINDOWS && prm->fShutdownCancelled
|
|
|| prm->dwEventType == SR_EVENT_INITIATE_CLEAN_ABORT) {
|
|
/*
|
|
* If csrss or winlogon issue the abort we will log the event no matter what.
|
|
*/
|
|
if (bIsCrsssOrWinlogon) {
|
|
bReportEvent = TRUE;
|
|
InterlockedCompareExchange((volatile LONG*)&g_ShutdownState, 0L, 1L);
|
|
} else if (InterlockedCompareExchange((volatile LONG*)&g_ShutdownState, 0L, 1L)) {
|
|
bReportEvent = TRUE;
|
|
}
|
|
} else {
|
|
if (!InterlockedCompareExchange((volatile LONG*)&g_ShutdownState, 1L, 0L)) {
|
|
bReportEvent = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bReportEvent) {
|
|
return TRUE;
|
|
}
|
|
|
|
// Allocate buffers after validations.
|
|
lpszUserName = (LPWSTR)LocalAlloc(LPTR, dwUserNameLen * sizeof(WCHAR));
|
|
lpszUserDomain = (LPWSTR)LocalAlloc(LPTR, dwUserDomainLen * sizeof(WCHAR));
|
|
lpszComputerName = (LPWSTR)LocalAlloc(LPTR, dwComputerNameLen * sizeof(WCHAR));
|
|
lpszReasonTitle = (LPWSTR)LocalAlloc(LPTR, dwReasonTitleLen * sizeof(WCHAR));
|
|
if (!lpszUserName || !lpszUserDomain || !lpszComputerName || !lpszReasonTitle) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Get the comment.
|
|
if (!bIsCancelEvent && prm->dwCommentLen > 0) {
|
|
lpszComment = LocalAlloc(LPTR, prm->dwCommentLen * sizeof(WCHAR));
|
|
if (!lpszComment) {
|
|
goto Cleanup;
|
|
}
|
|
wcsncpy(lpszComment, prm->pwchComment, prm->dwCommentLen);
|
|
lpszComment[prm->dwCommentLen-1] = 0;
|
|
}
|
|
|
|
// Get User name.
|
|
if (!LookupAccountSidW(NULL, psid, lpszUserName, &dwUserNameLen, lpszUserDomain,
|
|
&dwUserDomainLen, &eUse)) {
|
|
//
|
|
// log an event w/o user info, because shutdown might be initiated
|
|
// when lsass was terminated unexpected.
|
|
//
|
|
lpszUserName[0] = lpszUserDomain[0] = 0;
|
|
} else {
|
|
|
|
lpszUserName[MAX_PATH] = 0;
|
|
lpszUserDomain[MAX_PATH] = 0;
|
|
|
|
// We need to pack into a buffer of MAX_PATH + 1 in the form L"domain\\username"
|
|
if (wcslen(lpszUserDomain) + wcslen(lpszUserName) > MAX_PATH - 1) {
|
|
goto Cleanup;
|
|
}
|
|
if (wcslen(lpszUserDomain) > 0) {
|
|
wcscat(lpszUserDomain, L"\\");
|
|
}
|
|
wcscat(lpszUserDomain, lpszUserName);
|
|
}
|
|
|
|
// Get Computer Name.
|
|
if (!GetComputerNameW(lpszComputerName, &dwComputerNameLen)) {
|
|
//
|
|
// log an event w/o user info, because shutdown might be initiated
|
|
// when some critical process/service got terminated unexpected.
|
|
//
|
|
lpszComputerName[0]=0;
|
|
} else {
|
|
lpszComputerName[MAX_COMPUTERNAME_LENGTH] = 0;
|
|
}
|
|
|
|
// Get reason title.
|
|
if (!GetReasonTitleFromReasonCode(prm->psr->dwReasonCode, lpszReasonTitle, dwReasonTitleLen)) {
|
|
goto Cleanup;
|
|
}
|
|
lpszReasonTitle[MAX_REASON_NAME_LEN-1] = 0;
|
|
|
|
// Get the reason code string.
|
|
_snwprintf(szReasonCode, ARRAY_SIZE(szReasonCode), L"0x%x", prm->psr->dwReasonCode);
|
|
szReasonCode[ARRAY_SIZE(szReasonCode)-1] = 0;
|
|
|
|
switch (prm->dwEventType) {
|
|
LPWSTR lpCommentStart;
|
|
LPWSTR lpCommentEnd;
|
|
INT i;
|
|
|
|
case SR_EVENT_EXITWINDOWS:
|
|
if (prm->fShutdownCancelled) {
|
|
wEventType = EVENTLOG_WARNING_TYPE;
|
|
lpStrings[wStringCnt++] = lpszComputerName;
|
|
lpStrings[wStringCnt++] = lpszUserDomain;
|
|
} else {
|
|
wEventType = EVENTLOG_INFORMATION_TYPE;
|
|
lpStrings[wStringCnt++] = prm->pwchProcessName;
|
|
lpStrings[wStringCnt++] = lpszComputerName;
|
|
lpStrings[wStringCnt++] = lpszReasonTitle;
|
|
lpStrings[wStringCnt++] = szReasonCode;
|
|
lpStrings[wStringCnt++] = prm->pwchShutdownType;
|
|
lpStrings[wStringCnt++] = lpszComment;
|
|
lpStrings[wStringCnt++] = lpszUserDomain;
|
|
}
|
|
break;
|
|
case SR_EVENT_INITIATE_CLEAN:
|
|
wEventType = EVENTLOG_INFORMATION_TYPE;
|
|
lpStrings[wStringCnt++] = prm->pwchProcessName;
|
|
lpStrings[wStringCnt++] = lpszComputerName;
|
|
lpStrings[wStringCnt++] = lpszReasonTitle;
|
|
lpStrings[wStringCnt++] = szReasonCode;
|
|
lpStrings[wStringCnt++] = prm->pwchShutdownType;
|
|
lpStrings[wStringCnt++] = lpszComment;
|
|
lpStrings[wStringCnt++] = lpszUserDomain;
|
|
break;
|
|
case SR_EVENT_INITIATE_CLEAN_ABORT:
|
|
wEventType = EVENTLOG_WARNING_TYPE;
|
|
lpStrings[wStringCnt++] = lpszComputerName;
|
|
lpStrings[wStringCnt++] = lpszUserDomain;
|
|
break;
|
|
case SR_EVENT_DIRTY:
|
|
lpCommentStart = lpszComment;
|
|
lpCommentEnd = lpCommentStart + (lpCommentStart ? wcslen(lpCommentStart) : 0);
|
|
wEventType = EVENTLOG_WARNING_TYPE;
|
|
lpStrings[wStringCnt++] = lpszReasonTitle;
|
|
lpStrings[wStringCnt++] = szReasonCode;
|
|
|
|
/*
|
|
* In case of dirty event, the comment is in the following format:
|
|
* L"nnn\nccccccccnnn\ncccccccnnn\ncccccc"
|
|
* Basically it is three strings with each one headed by its length
|
|
* and a newline.
|
|
*/
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
INT cnt;
|
|
if (lpCommentStart >= lpCommentEnd) {
|
|
break;
|
|
}
|
|
cnt = _wtoi(lpCommentStart);
|
|
*lpCommentStart++ = L'\0';
|
|
while (lpCommentStart < lpCommentEnd && *lpCommentStart != L'\n') {
|
|
lpCommentStart++;
|
|
}
|
|
|
|
if (*lpCommentStart) {
|
|
lpStrings[wStringCnt++] = ++lpCommentStart;
|
|
} else {
|
|
lpStrings[wStringCnt++] = NULL;
|
|
}
|
|
lpCommentStart += cnt;
|
|
}
|
|
|
|
for (; i < 3; i++) {
|
|
lpStrings[wStringCnt++] = NULL;
|
|
}
|
|
|
|
lpStrings[wStringCnt++] = lpszUserDomain;
|
|
|
|
break;
|
|
default:
|
|
RIPERR1(ERROR_INVALID_PARAMETER, RIP_WARNING, "Unknown prm->dwEventType 0x%x", prm->dwEventType);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// First see if we need to take a snapshot.
|
|
if (prm->dwEventType == SR_EVENT_INITIATE_CLEAN) {
|
|
CONST WCHAR szSnapShotVal[] = L"Snapshot";
|
|
CONST ULONG ulMaxSnapShotSize = 2048;
|
|
SNAPSHOTFUNC pSnapShotProc;
|
|
DWORD DoSnapShotValue = SNAPSHOT_POLICY_UNPLANNED;
|
|
HKEY hKey = NULL;
|
|
|
|
// First try to read the policy.
|
|
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_RELIABILITY_POLICY, 0, KEY_QUERY_VALUE, &hKey)) {
|
|
DWORD dwSize = sizeof(DWORD);
|
|
DWORD dwType;
|
|
if (ERROR_SUCCESS == RegQueryValueEx(hKey, szSnapShotVal, NULL, &dwType, (UCHAR*)&DoSnapShotValue, &dwSize)) {
|
|
if (dwType != REG_DWORD) {
|
|
DoSnapShotValue = SNAPSHOT_POLICY_UNPLANNED;
|
|
} else if (DoSnapShotValue == 0) {
|
|
DoSnapShotValue = SNAPSHOT_POLICY_NEVER;
|
|
} else {
|
|
DoSnapShotValue = SNAPSHOT_POLICY_UNPLANNED;
|
|
}
|
|
}
|
|
RegCloseKey(hKey);
|
|
} // else SNAPSHOT_POLICY_UNPLANNED will be used.
|
|
|
|
/*
|
|
* SNAPSHOT_POLICY_ALWAYS: we will take a snapshot when we get here.
|
|
* SNAPSHOT_POLICY_NEVER: we wont event proceed into the if (no snapshot)
|
|
* SNAPSHOT_POLICY_UNPLANNED: Snapshot only if the reason is unplanned.
|
|
*/
|
|
|
|
if (DoSnapShotValue == SNAPSHOT_POLICY_ALWAYS
|
|
|| (DoSnapShotValue == SNAPSHOT_POLICY_UNPLANNED
|
|
&& !(prm->psr->dwReasonCode & SHTDN_REASON_FLAG_PLANNED))) {
|
|
|
|
SnapShotSize = ulMaxSnapShotSize/sizeof(WCHAR);
|
|
SnapShot.SnapShotBuf = LocalAlloc(LPTR, ulMaxSnapShotSize);
|
|
if (SnapShot.SnapShotBuf == NULL) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
SnapShot.SnapShotBuf[0] = 0;
|
|
|
|
/*
|
|
* Snapshot.dll is loaded once and will be unloaded when system
|
|
* shutdowns down. If we fail in any way make sure we can try it
|
|
* again next time this function gets called.
|
|
*/
|
|
if (InterlockedCompareExchange(&g_SnapShot, 0L, 1L)) {
|
|
g_SnapShotDllHandle = LoadLibrary(L"snapshot.dll");
|
|
if (!g_SnapShotDllHandle) {
|
|
InterlockedExchange(&g_SnapShot, 1L);
|
|
}
|
|
}
|
|
|
|
if (g_SnapShotDllHandle) {
|
|
pSnapShotProc = (SNAPSHOTFUNC)GetProcAddress(g_SnapShotDllHandle, "LogSystemSnapshot");
|
|
if (pSnapShotProc) {
|
|
(*pSnapShotProc)(wStringCnt, lpStrings, &SnapShotSize, SnapShot.SnapShotBuf);
|
|
} else {
|
|
SnapShot.SnapShotBuf[0] = L'\0';
|
|
}
|
|
} else {
|
|
SnapShot.SnapShotBuf[0] = L'\0';
|
|
}
|
|
|
|
SnapShot.SnapShotBuf[ulMaxSnapShotSize/sizeof(WCHAR)-1] = L'\0';
|
|
SnapShotSize = wcslen(SnapShot.SnapShotBuf);
|
|
|
|
if (SnapShotSize > 0 ) {
|
|
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_RELIABILITY, 0, KEY_ALL_ACCESS, &hKey)) {
|
|
RegSetValueEx(hKey,
|
|
REGSTR_VAL_SHUTDOWN_STATE_SNAPSHOT,
|
|
0,
|
|
REG_SZ,
|
|
(LPBYTE)SnapShot.SnapShotBuf,
|
|
SnapShotSize * sizeof(WCHAR));
|
|
RegCloseKey(hKey);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* If the client is server, we need to revert so RegisterEventSourceW() can
|
|
* succeed if the user is not in admin group.
|
|
*/
|
|
if (m->h.ClientId.UniqueProcess == NtCurrentTeb()->ClientId.UniqueProcess) {
|
|
CsrRevertToSelf();
|
|
}
|
|
hEventLog = RegisterEventSourceW(NULL, L"USER32");
|
|
if (m->h.ClientId.UniqueProcess == NtCurrentTeb()->ClientId.UniqueProcess) {
|
|
CsrImpersonateClient(NULL);
|
|
}
|
|
|
|
if (!hEventLog) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
/*
|
|
* We are required to log the snapshot info (if a snopshot is taken)
|
|
* into the data part, so we need to realloc the snapshot buf in order
|
|
* to insert the reason code.
|
|
*/
|
|
if (!SnapShot.SnapShotBuf || wcslen(SnapShot.SnapShotBuf) == 0) { // no snapshot, so just report reason.
|
|
bRet = ReportEventW(hEventLog, wEventType, 0, prm->dwEventID, psid,
|
|
wStringCnt, sizeof(DWORD),
|
|
lpStrings, &SnapShot);
|
|
} else { /* need to repack */
|
|
DWORD dwNewBuf = (lstrlenW(SnapShot.SnapShotBuf)+1) * sizeof(WCHAR) + sizeof(DWORD);
|
|
PWCHAR pBuf = LocalAlloc(LPTR, dwNewBuf);
|
|
if (pBuf) {
|
|
*((DWORD*)pBuf) = SnapShot.Reason;
|
|
swprintf(&pBuf[2], L"%s", SnapShot.SnapShotBuf);
|
|
LocalFree(SnapShot.SnapShotBuf);
|
|
SnapShot.SnapShotBuf = pBuf;
|
|
bRet = ReportEventW(hEventLog, wEventType, 0, prm->dwEventID, psid,
|
|
wStringCnt, dwNewBuf,
|
|
lpStrings, pBuf);
|
|
}
|
|
}
|
|
DeregisterEventSource(hEventLog);
|
|
|
|
Cleanup:
|
|
LocalFree(SnapShot.SnapShotBuf);
|
|
LocalFree(psid);
|
|
LocalFree(lpszUserDomain);
|
|
LocalFree(lpszUserName);
|
|
LocalFree(lpszComputerName);
|
|
LocalFree(lpszReasonTitle);
|
|
LocalFree(lpszComment);
|
|
|
|
return bRet;
|
|
}
|