windows-nt/Source/XPSP1/NT/shell/services/bamsrv/gracefulterminateapplication.cpp
2020-09-26 16:20:57 +08:00

532 lines
20 KiB
C++

// --------------------------------------------------------------------------
// Module Name: GracefulTerminateApplication.cpp
//
// Copyright (c) 2000, Microsoft Corporation
//
// Class to manager terminating applications gracefully.
//
// History: 2000-10-27 vtan created
// 2000-11-04 vtan split into separate file
// --------------------------------------------------------------------------
#ifdef _X86_
#include "StandardHeader.h"
#include "GracefulTerminateApplication.h"
#include "KernelResources.h"
#include "Thread.h"
#include "WarningDialog.h"
// --------------------------------------------------------------------------
// CProgressDialog
//
// Purpose: A class to manage displaying a progress dialog on a separate
// thread if a certain period of time elapses. This is so that
// in case the process doesn't terminate in a period of time
// a dialog indicating wait is shown so the user is not left
// staring at a blank screen.
//
// History: 2000-11-04 vtan created
// --------------------------------------------------------------------------
class CProgressDialog : public CThread
{
private:
CProgressDialog (void);
public:
CProgressDialog (CWarningDialog *pWarningDialog);
virtual ~CProgressDialog (void);
void SignalTerminated (void);
protected:
virtual DWORD Entry (void);
private:
CWarningDialog* _pWarningDialog;
CEvent _event;
};
// --------------------------------------------------------------------------
// CProgressDialog::CProgressDialog
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Constructor for CProgressDialog. Keeps a reference to the
// given CWarningDialog.
//
// History: 2000-11-04 vtan created
// --------------------------------------------------------------------------
CProgressDialog::CProgressDialog (CWarningDialog *pWarningDialog) :
_pWarningDialog(NULL),
_event(NULL)
{
if (IsCreated())
{
pWarningDialog->AddRef();
_pWarningDialog = pWarningDialog;
Resume();
}
}
// --------------------------------------------------------------------------
// CProgressDialog::~CProgressDialog
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Releases the CWarningDialog reference.
//
// History: 2000-11-04 vtan created
// --------------------------------------------------------------------------
CProgressDialog::~CProgressDialog (void)
{
_pWarningDialog->Release();
_pWarningDialog = NULL;
}
// --------------------------------------------------------------------------
// CProgressDialog::SignalTerminated
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Signals the internal event that the process being watched is
// termination. This is necessary because there is no handle to
// the actual process to watch as it's kept on the server side
// and not given to us the client. However, the result of the
// termination is. Signaling this object will release the waiting
// thread and cause it to exit.
//
// History: 2000-11-04 vtan created
// --------------------------------------------------------------------------
void CProgressDialog::SignalTerminated (void)
{
TSTATUS(_event.Set());
}
// --------------------------------------------------------------------------
// CProgressDialog::Entry
//
// Arguments: <none>
//
// Returns: DWORD
//
// Purpose: Thread which waits for the internal event to be signaled. If
// the event is signaled it will "cancel" the 3 second wait and
// the thread will exit. Otherwise the wait times out and the
// progress dialog is shown - waiting for termination.
//
// History: 2000-11-04 vtan created
// --------------------------------------------------------------------------
DWORD CProgressDialog::Entry (void)
{
DWORD dwWaitResult;
// Wait for the event to be signaled or for it to timeout. If signaled
// then the process is terminated and no progress is required. Otherwise
// prepare to show progress while the process is being terminated.
if (NT_SUCCESS(_event.Wait(2000, &dwWaitResult)) && (WAIT_TIMEOUT == dwWaitResult))
{
_pWarningDialog->ShowProgress(100, 7500);
}
return(0);
}
// --------------------------------------------------------------------------
// CGracefulTerminateApplication::CGracefulTerminateApplication
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Constructor for CGracefulTerminateApplication.
//
// History: 2000-10-27 vtan created
// --------------------------------------------------------------------------
CGracefulTerminateApplication::CGracefulTerminateApplication (void) :
_dwProcessID(0),
_fFoundWindow(false)
{
}
// --------------------------------------------------------------------------
// CGracefulTerminateApplication::~CGracefulTerminateApplication
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Destructor for CGracefulTerminateApplication.
//
// History: 2000-10-27 vtan created
// --------------------------------------------------------------------------
CGracefulTerminateApplication::~CGracefulTerminateApplication (void)
{
}
// --------------------------------------------------------------------------
// CGracefulTerminateApplication::Terminate
//
// Arguments: dwProcessID = Process ID of process to terminate.
//
// Returns: <none>
//
// Purpose: Walk the window list for top level windows that correspond
// to this process ID and are visible. Close them. The callback
// handles the work and this function returns the result in the
// process exit code which the server examines.
//
// History: 2000-10-27 vtan created
// --------------------------------------------------------------------------
void CGracefulTerminateApplication::Terminate (DWORD dwProcessID)
{
DWORD dwExitCode;
_dwProcessID = dwProcessID;
TBOOL(EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(this)));
if (_fFoundWindow)
{
dwExitCode = WAIT_WINDOWS_FOUND;
}
else
{
dwExitCode = NO_WINDOWS_FOUND;
}
ExitProcess(dwExitCode);
}
// --------------------------------------------------------------------------
// CGracefulTerminateApplication::Prompt
//
// Arguments: hInstance = HINSTANCE of this DLL.
// hProcess = Inherited handle to parent process.
//
// Returns: <none>
//
// Purpose: Shows a prompt that handles termination of the parent of this
// process. The parent is assumed to be a bad application type 1
// that caused this stub to be executed via the
// "rundll32 shsvcs.dll,FUSCompatibilityEntry prompt" command.
// Because there can only be a single instance of the type 1
// application running and the parent of this process hasn't
// registered yet querying for this process by image name will
// always find the correct process.
//
// History: 2000-11-03 vtan created
// --------------------------------------------------------------------------
void CGracefulTerminateApplication::Prompt (HINSTANCE hInstance, HANDLE hProcess)
{
bool fTerminated;
ULONG ulReturnLength;
PROCESS_BASIC_INFORMATION processBasicInformation;
// Read the parent's image name from the RTL_USER_PROCESS_PARAMETERS.
fTerminated = false;
if (hProcess != NULL)
{
if (NT_SUCCESS(NtQueryInformationProcess(hProcess,
ProcessBasicInformation,
&processBasicInformation,
sizeof(processBasicInformation),
&ulReturnLength)))
{
SIZE_T dwNumberOfBytesRead;
PEB peb;
if (ReadProcessMemory(hProcess,
processBasicInformation.PebBaseAddress,
&peb,
sizeof(peb),
&dwNumberOfBytesRead) != FALSE)
{
RTL_USER_PROCESS_PARAMETERS processParameters;
if (ReadProcessMemory(hProcess,
peb.ProcessParameters,
&processParameters,
sizeof(processParameters),
&dwNumberOfBytesRead) != FALSE)
{
WCHAR *pszImageName;
pszImageName = static_cast<WCHAR*>(LocalAlloc(LMEM_FIXED, processParameters.ImagePathName.Length + (sizeof('\0') * sizeof(WCHAR))));
if (pszImageName != NULL)
{
if (ReadProcessMemory(hProcess,
processParameters.ImagePathName.Buffer,
pszImageName,
processParameters.ImagePathName.Length,
&dwNumberOfBytesRead) != FALSE)
{
pszImageName[processParameters.ImagePathName.Length / sizeof(WCHAR)] = L'\0';
// And show a prompt for this process.
fTerminated = ShowPrompt(hInstance, pszImageName);
}
(HLOCAL)LocalFree(pszImageName);
}
}
}
}
TBOOL(CloseHandle(hProcess));
}
ExitProcess(fTerminated);
}
// --------------------------------------------------------------------------
// CGracefulTerminateApplication::ShowPrompt
//
// Arguments: hInstance = HINSTANCE of this DLL.
// pszImagename = Image name of process to terminate.
//
// Returns: bool
//
// Purpose: Shows the appropriate prompt for termination of the first
// instance of a BAM type 1 process. If the current user does
// not have administrator privileges then a "STOP" dialog is
// shown that the user must get the other user to close the
// program. Otherwise the "PROMPT" dialog is shown which gives
// the user the option to terminate the process.
//
// If termination is requested and the termatinion fails the
// another warning dialog to that effect is shown.
//
// History: 2000-11-03 vtan created
// --------------------------------------------------------------------------
bool CGracefulTerminateApplication::ShowPrompt (HINSTANCE hInstance, const WCHAR *pszImageName)
{
bool fTerminated;
ULONG ulConnectionInfoLength;
HANDLE hPort;
UNICODE_STRING portName;
SECURITY_QUALITY_OF_SERVICE sqos;
WCHAR szConnectionInfo[32];
fTerminated = false;
RtlInitUnicodeString(&portName, FUS_PORT_NAME);
sqos.Length = sizeof(sqos);
sqos.ImpersonationLevel = SecurityImpersonation;
sqos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
sqos.EffectiveOnly = TRUE;
lstrcpyW(szConnectionInfo, FUS_CONNECTION_REQUEST);
ulConnectionInfoLength = sizeof(szConnectionInfo);
if (NT_SUCCESS(NtConnectPort(&hPort,
&portName,
&sqos,
NULL,
NULL,
NULL,
szConnectionInfo,
&ulConnectionInfoLength)))
{
bool fCanTerminateFirstInstance;
CWarningDialog *pWarningDialog;
WCHAR szUser[256];
// Get the user's privilege level for this operation. This API also
// returns the current user for the BAM type 1 process.
fCanTerminateFirstInstance = CanTerminateFirstInstance(hPort, pszImageName, szUser, ARRAYSIZE(szUser));
pWarningDialog = new CWarningDialog(hInstance, NULL, pszImageName, szUser);
if (pWarningDialog != NULL)
{
// Show the appropriate dialog based on the privilege level.
if (pWarningDialog->ShowPrompt(fCanTerminateFirstInstance) == IDOK)
{
CProgressDialog *pProgressDialog;
// Create a progress dialog object in case of delayed termination.
// This will create the watcher thread.
pProgressDialog = new CProgressDialog(pWarningDialog);
if ((pProgressDialog != NULL) && !pProgressDialog->IsCreated())
{
pProgressDialog->Release();
pProgressDialog = NULL;
}
// Attempt to terminate the process if requested by the user.
fTerminated = TerminatedFirstInstance(hPort, pszImageName);
// Once this function returns signal the event (in case the
// thread is still waiting). If the thread is still waiting this
// effectively cancels the dialog. Either way if the dialog is
// shown then close it, wait for the thread to exit and release
// the reference to destroy the object.
if (pProgressDialog != NULL)
{
pProgressDialog->SignalTerminated();
pWarningDialog->CloseDialog();
pProgressDialog->WaitForCompletion(INFINITE);
pProgressDialog->Release();
}
// If there was some failure then let the user know.
if (!fTerminated)
{
pWarningDialog->ShowFailure();
}
}
pWarningDialog->Release();
}
TBOOL(CloseHandle(hPort));
}
return(fTerminated);
}
// --------------------------------------------------------------------------
// CGracefulTerminateApplication::CanTerminateFirstInstance
//
// Arguments: hPort = Port to server.
// pszImageName = Image name of process to terminate.
// pszUser = User of process (returned).
// cchUser = Count of characters for buffer.
//
// Returns: bool
//
// Purpose: Asks the server whether the current user has privileges to
// terminate the BAM type 1 process of the given image name
// which is known to be running. The API returns whether the
// operation is allowed and who the current user of the process
// is.
//
// History: 2000-11-03 vtan created
// --------------------------------------------------------------------------
bool CGracefulTerminateApplication::CanTerminateFirstInstance (HANDLE hPort, const WCHAR *pszImageName, WCHAR *pszUser, int cchUser)
{
bool fCanTerminate;
fCanTerminate = false;
if ((hPort != NULL) && (pszImageName != NULL))
{
FUSAPI_PORT_MESSAGE portMessageIn, portMessageOut;
ZeroMemory(&portMessageIn, sizeof(portMessageIn));
ZeroMemory(&portMessageOut, sizeof(portMessageOut));
portMessageIn.apiBAM.apiGeneric.ulAPINumber = API_BAM_QUERYUSERPERMISSION;
portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.pszImageName = pszImageName;
portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.cchImageName = lstrlen(pszImageName) + sizeof('\0');
portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.pszUser = pszUser;
portMessageIn.apiBAM.apiSpecific.apiQueryUserPermission.in.cchUser = cchUser;
portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_BAM);
portMessageIn.portMessage.u1.s1.TotalLength = static_cast<CSHORT>(sizeof(FUSAPI_PORT_MESSAGE));
if (NT_SUCCESS(NtRequestWaitReplyPort(hPort, &portMessageIn.portMessage, &portMessageOut.portMessage)) &&
NT_SUCCESS(portMessageOut.apiBAM.apiGeneric.status))
{
fCanTerminate = portMessageOut.apiBAM.apiSpecific.apiQueryUserPermission.out.fCanShutdownApplication;
pszUser[cchUser - sizeof('\0')] = L'\0';
}
else
{
pszUser[0] = L'\0';
}
}
return(fCanTerminate);
}
// --------------------------------------------------------------------------
// CGracefulTerminateApplication::TerminatedFirstInstance
//
// Arguments: hPort = Port to server.
// pszImageName = Image name to terminate.
//
// Returns: bool
//
// Purpose: Asks the server to terminate the first running instance of the
// BAM type 1 process.
//
// History: 2000-11-03 vtan created
// --------------------------------------------------------------------------
bool CGracefulTerminateApplication::TerminatedFirstInstance (HANDLE hPort, const WCHAR *pszImageName)
{
bool fTerminated;
fTerminated = false;
if (hPort != NULL)
{
FUSAPI_PORT_MESSAGE portMessageIn, portMessageOut;
ZeroMemory(&portMessageIn, sizeof(portMessageIn));
ZeroMemory(&portMessageOut, sizeof(portMessageOut));
portMessageIn.apiBAM.apiGeneric.ulAPINumber = API_BAM_TERMINATERUNNING;
portMessageIn.apiBAM.apiSpecific.apiTerminateRunning.in.pszImageName = pszImageName;
portMessageIn.apiBAM.apiSpecific.apiTerminateRunning.in.cchImageName = lstrlen(pszImageName) + sizeof('\0');
portMessageIn.portMessage.u1.s1.DataLength = sizeof(API_BAM);
portMessageIn.portMessage.u1.s1.TotalLength = static_cast<CSHORT>(sizeof(FUSAPI_PORT_MESSAGE));
if (NT_SUCCESS(NtRequestWaitReplyPort(hPort, &portMessageIn.portMessage, &portMessageOut.portMessage)) &&
NT_SUCCESS(portMessageOut.apiBAM.apiGeneric.status))
{
fTerminated = portMessageOut.apiBAM.apiSpecific.apiTerminateRunning.out.fResult;
}
}
return(fTerminated);
}
// --------------------------------------------------------------------------
// CGracefulTerminateApplication::EnumWindowsProc
//
// Arguments: See the platform SDK under EnumWindowsProc.
//
// Returns: See the platform SDK under EnumWindowsProc.
//
// Purpose: Top level window enumerator callback which compares the
// process IDs of the windows and whether they are visible. If
// there is a match on BOTH counts the a WM_CLOSE message is
// posted to the window to allow a graceful termination.
//
// History: 2000-10-27 vtan created
// --------------------------------------------------------------------------
BOOL CALLBACK CGracefulTerminateApplication::EnumWindowsProc (HWND hwnd, LPARAM lParam)
{
DWORD dwThreadID, dwProcessID;
CGracefulTerminateApplication *pThis;
pThis = reinterpret_cast<CGracefulTerminateApplication*>(lParam);
dwThreadID = GetWindowThreadProcessId(hwnd, &dwProcessID);
if ((dwProcessID == pThis->_dwProcessID) && IsWindowVisible(hwnd))
{
pThis->_fFoundWindow = true;
TBOOL(PostMessage(hwnd, WM_CLOSE, 0, 0));
}
return(TRUE);
}
#endif /* _X86_ */