Leaked source code of windows server 2003
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

/**************************** 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;
}