|
|
// --------------------------------------------------------------------------
// Module Name: ExternalProcess.cpp
//
// Copyright (c) 1999-2000, Microsoft Corporation
//
// Class to handle premature termination of external processes or signaling
// of termination of an external process.
//
// History: 1999-09-20 vtan created
// 2000-02-01 vtan moved from Neptune to Whistler
// 2001-02-21 vtan add PRERELEASE to DBG condition
// --------------------------------------------------------------------------
#include "StandardHeader.h"
#include "ExternalProcess.h"
#include "RegistryResources.h"
#include "StatusCode.h"
#include "Thread.h"
#include "TokenGroups.h"
#if (defined(DBG) || defined(PRERELEASE))
static const TCHAR kNTSD[] = TEXT("ntsd");
#endif /* (defined(DBG) || defined(PRERELEASE)) */
// --------------------------------------------------------------------------
// CJobCompletionWatcher
//
// Purpose: This is a private class (declared only by name in the header
// file which implements the watcher thread) for the IO
// completion port related to the job object for the external
// process.
//
// History: 1999-10-07 vtan created
// --------------------------------------------------------------------------
class CJobCompletionWatcher : public CThread { private: CJobCompletionWatcher (void); CJobCompletionWatcher (const CJobCompletionWatcher& copyObject); const CJobCompletionWatcher& operator = (const CJobCompletionWatcher& assignObject); public: CJobCompletionWatcher (CExternalProcess* pExternalProcess, CJob& job, HANDLE hEvent); ~CJobCompletionWatcher (void);
void ForceExit (void); protected: virtual DWORD Entry (void); virtual void Exit (void); private: CExternalProcess *_pExternalProcess; HANDLE _hEvent; HANDLE _hPortJobCompletion; bool _fExitLoop; };
// --------------------------------------------------------------------------
// CJobCompletionWatcher::CJobCompletionWatcher
//
// Arguments: pExternalProcess = CExternalProcess owner of this object.
// job = CJob containing the job object.
//
// Returns: <none>
//
// Purpose: Constructs the CJobCompletionWatcher object. Creates the IO
// completion port and assigns the port into the job object.
//
// History: 1999-10-07 vtan created
// --------------------------------------------------------------------------
CJobCompletionWatcher::CJobCompletionWatcher (CExternalProcess *pExternalProcess, CJob& job, HANDLE hEvent) : CThread(), _pExternalProcess(pExternalProcess), _hEvent(hEvent), _hPortJobCompletion(CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 1)), _fExitLoop(false)
{ pExternalProcess->AddRef(); if (_hPortJobCompletion != NULL) { if (!NT_SUCCESS(job.SetCompletionPort(_hPortJobCompletion))) { ReleaseHandle(_hPortJobCompletion); } } Resume(); }
// --------------------------------------------------------------------------
// CJobCompletionWatcher::~CJobCompletionWatcher
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Release the IO completion port used.
//
// History: 1999-10-07 vtan created
// --------------------------------------------------------------------------
CJobCompletionWatcher::~CJobCompletionWatcher (void)
{ ReleaseHandle(_hPortJobCompletion); }
// --------------------------------------------------------------------------
// CJobCompletionWatcher::ForceExit
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Sets the internal member variable telling the watcher loop
// to exit. This allows the context to be invalidated while the
// thread is still active. When detected the thread will exit.
//
// History: 1999-10-07 vtan created
// --------------------------------------------------------------------------
void CJobCompletionWatcher::ForceExit (void)
{ _fExitLoop = true; if (_pExternalProcess != NULL) { _pExternalProcess->Release(); _pExternalProcess = NULL; } TBOOL(PostQueuedCompletionStatus(_hPortJobCompletion, 0, NULL, NULL)); }
// --------------------------------------------------------------------------
// CJobCompletionWatcher::Entry
//
// Arguments: <none>
//
// Returns: DWORD
//
// Purpose: Continually poll the IO completion port waiting for process
// exit messages. There are other messages that are ignored.
// When the process has exited call the CExternalProcess which
// allows it to make a decision and/or restart the external
// process which will cause us to wait on that process.
//
// History: 1999-10-07 vtan created
// --------------------------------------------------------------------------
DWORD CJobCompletionWatcher::Entry (void)
{
// Must have an IO completion port to work with.
if (_hPortJobCompletion != NULL) { DWORD dwCompletionCode; ULONG_PTR pCompletionKey; LPOVERLAPPED pOverlapped;
do { if (_hEvent != NULL) { TBOOL(SetEvent(_hEvent)); _hEvent = NULL; }
// Get the completion status on the IO waiting forever.
// Exit the loop if an error condition occurred.
if ((GetQueuedCompletionStatus(_hPortJobCompletion, &dwCompletionCode, &pCompletionKey, &pOverlapped, INFINITE) != FALSE) && !_fExitLoop) { switch (dwCompletionCode) { case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: DISPLAYMSG("JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT\r\n"); break; case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: _fExitLoop = _pExternalProcess->HandleNoProcess(); break; case JOB_OBJECT_MSG_NEW_PROCESS: _pExternalProcess->HandleNewProcess(PtrToUlong(pOverlapped)); break; case JOB_OBJECT_MSG_EXIT_PROCESS: case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: _pExternalProcess->HandleTermination(PtrToUlong(pOverlapped)); break; default: break; } } else { _fExitLoop = true; } } while (!_fExitLoop); } return(0); }
// --------------------------------------------------------------------------
// CJobCompletionWatcher::Exit
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Release the CExternalProcess given in the constructor so that
// the object can actually be released (reference count drops to
// zero).
//
// History: 2000-05-01 vtan created
// --------------------------------------------------------------------------
void CJobCompletionWatcher::Exit (void)
{ if (_pExternalProcess != NULL) { _pExternalProcess->Release(); _pExternalProcess = NULL; } CThread::Exit(); }
// --------------------------------------------------------------------------
// IExternalProcess::Start
//
// Arguments: pszCommandLine = Command line to process.
// dwCreateFlags = Flags when creating process.
// startupInfo = STARTUPINFO struct.
// processInformation = PROCESS_INFORMATION struct.
//
// Returns: NTSTATUS
//
// Purpose: This function is the default implementation of
// IExternalProcess::Start which starts the process in the SYSTEM
// context of a restricted user.
//
// History: 1999-09-20 vtan created
// --------------------------------------------------------------------------
NTSTATUS IExternalProcess::Start (const TCHAR *pszCommandLine, DWORD dwCreateFlags, const STARTUPINFO& startupInfo, PROCESS_INFORMATION& processInformation)
{ NTSTATUS status; HANDLE hTokenProcess; TCHAR szCommandLine[MAX_PATH * 2];
// A user token is not allowed for this function. This function ALWAYS
// starts the process as a restricted SYSTEM context process. To start
// in a user context override this implementation with your own (or
// impersonate the user before instantiating CExternalProcess).
lstrcpyn(szCommandLine, pszCommandLine, ARRAYSIZE(szCommandLine)); if (OpenProcessToken(GetCurrentProcess(), TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY, &hTokenProcess) != FALSE) { HANDLE hTokenRestricted;
status = RemoveTokenSIDsAndPrivileges(hTokenProcess, hTokenRestricted); if (NT_SUCCESS(status)) { TCHAR szCommandLine[MAX_PATH];
AllowSetForegroundWindow(ASFW_ANY);
(TCHAR*)lstrcpyn(szCommandLine, pszCommandLine, ARRAYSIZE(szCommandLine)); if (dwCreateFlags == 0) { dwCreateFlags = NORMAL_PRIORITY_CLASS; } if (CreateProcessAsUser(hTokenRestricted, NULL, szCommandLine, NULL, NULL, FALSE, dwCreateFlags, NULL, NULL, const_cast<STARTUPINFO*>(&startupInfo), &processInformation) != FALSE) { status = STATUS_SUCCESS; } else { status = CStatusCode::StatusCodeOfLastError(); } ReleaseHandle(hTokenRestricted); } ReleaseHandle(hTokenProcess); } else { status = CStatusCode::StatusCodeOfLastError(); } return(status); }
// --------------------------------------------------------------------------
// IExternalProcess::AllowTermination
//
// Arguments: dwExitCode = Exit code of process.
//
// Returns: bool
//
// Purpose: This function returns whether external process termination is
// allowed.
//
// History: 2000-05-01 vtan created
// --------------------------------------------------------------------------
bool IExternalProcess::AllowTermination (DWORD dwExitCode)
{ UNREFERENCED_PARAMETER(dwExitCode);
return(true); }
// --------------------------------------------------------------------------
// IExternalProcess::SignalTermination
//
// Arguments: <none>
//
// Returns: NTSTATUS
//
// Purpose: This function is invoked by the external process handler
// when the external process terminates normally.
//
// History: 1999-09-21 vtan created
// --------------------------------------------------------------------------
NTSTATUS IExternalProcess::SignalTermination (void)
{ return(STATUS_SUCCESS); }
// --------------------------------------------------------------------------
// IExternalProcess::SignalAbnormalTermination
//
// Arguments: <none>
//
// Returns: NTSTATUS
//
// Purpose: This function is invoked by the external process handler
// when the external process terminates and cannot be restarted.
// This indicates a serious condition from which this function
// can attempt to recover.
//
// History: 1999-09-21 vtan created
// --------------------------------------------------------------------------
NTSTATUS IExternalProcess::SignalAbnormalTermination (void)
{ return(STATUS_SUCCESS); }
// --------------------------------------------------------------------------
// IExternalProcess::SignalRestart
//
// Arguments: <none>
//
// Returns: NTSTATUS
//
// Purpose: Signals restart of the external process. This allows a derived
// implementation to do something when this happens.
//
// History: 2001-01-09 vtan created
// --------------------------------------------------------------------------
NTSTATUS IExternalProcess::SignalRestart (void)
{ return(STATUS_SUCCESS); }
// --------------------------------------------------------------------------
// IExternalProcess::RemoveTokenSIDsAndPrivileges
//
// Arguments: hTokenIn = Token to remove SIDs and privileges from.
// hTokenOut = Generated token returned.
//
// Returns: NTSTATUS
//
// Purpose: Remove designated SIDs and privileges from the given token.
// Currently this removes the local administrators SID and all
// all privileges except SE_RESTORE_NAME. On checked builds
// SE_DEBUG_NAME is also not removed.
//
// History: 2000-06-21 vtan created
// --------------------------------------------------------------------------
NTSTATUS IExternalProcess::RemoveTokenSIDsAndPrivileges (HANDLE hTokenIn, HANDLE& hTokenOut)
{ NTSTATUS status; DWORD dwFlags = 0, dwReturnLength; TOKEN_PRIVILEGES *pTokenPrivileges; CTokenGroups tokenGroup;
hTokenOut = NULL; TSTATUS(tokenGroup.CreateAdministratorGroup()); (BOOL)GetTokenInformation(hTokenIn, TokenPrivileges, NULL, 0, &dwReturnLength); pTokenPrivileges = static_cast<TOKEN_PRIVILEGES*>(LocalAlloc(LMEM_FIXED, dwReturnLength)); if (pTokenPrivileges != NULL) { if (GetTokenInformation(hTokenIn, TokenPrivileges, pTokenPrivileges, dwReturnLength, &dwReturnLength) != FALSE) { bool fKeepPrivilege; ULONG ulCount; LUID luidRestorePrivilege; LUID luidChangeNotifyPrivilege; #if (defined(DBG) || defined(PRERELEASE))
LUID luidDebugPrivilege; #endif /* (defined(DBG) || defined(PRERELEASE)) */
luidRestorePrivilege.LowPart = SE_RESTORE_PRIVILEGE; luidRestorePrivilege.HighPart = 0; luidChangeNotifyPrivilege.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE; luidChangeNotifyPrivilege.HighPart = 0; #if (defined(DBG) || defined(PRERELEASE))
luidDebugPrivilege.LowPart = SE_DEBUG_PRIVILEGE; luidDebugPrivilege.HighPart = 0; #endif /* (defined(DBG) || defined(PRERELEASE)) */
// Privileges kept are actually removed from the privilege array.
// This is because NtFilterToken will REMOVE the privileges passed
// in the array. Keep SE_DEBUG_NAME on checked builds.
ulCount = 0; while (ulCount < pTokenPrivileges->PrivilegeCount) { fKeepPrivilege = ((RtlEqualLuid(&pTokenPrivileges->Privileges[ulCount].Luid, &luidRestorePrivilege) != FALSE) || (RtlEqualLuid(&pTokenPrivileges->Privileges[ulCount].Luid, &luidChangeNotifyPrivilege) != FALSE)); #if (defined(DBG) || defined(PRERELEASE))
fKeepPrivilege = fKeepPrivilege || (RtlEqualLuid(&pTokenPrivileges->Privileges[ulCount].Luid, &luidDebugPrivilege) != FALSE); #endif /* (defined(DBG) || defined(PRERELEASE)) */
if (fKeepPrivilege) { MoveMemory(&pTokenPrivileges->Privileges[ulCount], &pTokenPrivileges->Privileges[ulCount + 1], pTokenPrivileges->PrivilegeCount - ulCount - 1); --pTokenPrivileges->PrivilegeCount; } else { ++ulCount; } } } else { ReleaseMemory(pTokenPrivileges); } }
if (pTokenPrivileges == NULL) { dwFlags = DISABLE_MAX_PRIVILEGE; }
status = NtFilterToken(hTokenIn, dwFlags, const_cast<TOKEN_GROUPS*>(tokenGroup.Get()), pTokenPrivileges, NULL, &hTokenOut);
ReleaseMemory(pTokenPrivileges); return(status); }
// --------------------------------------------------------------------------
// CExternalProcess::CExternalProcess
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Constructor for CExternalProcess.
//
// History: 1999-09-14 vtan created
// --------------------------------------------------------------------------
CExternalProcess::CExternalProcess (void) : _hProcess(NULL), _dwProcessID(0), _dwProcessExitCode(STILL_ACTIVE), _dwCreateFlags(NORMAL_PRIORITY_CLASS), _dwStartFlags(STARTF_USESHOWWINDOW), _wShowFlags(SW_SHOW), _iRestartCount(0), _pIExternalProcess(NULL), _jobCompletionWatcher(NULL)
{ _szCommandLine[0] = _szParameter[0] = TEXT('\0');
// Configure our job object. Only allow a single process to execute
// for this job. Restriction of UI is done by subclassing. The UIHost
// does not restrict UI but the screen saver does.
TSTATUS(_job.SetActiveProcessLimit(1)); }
// --------------------------------------------------------------------------
// CExternalProcess::~CExternalProcess
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Destructor for CExternalProcess.
//
// History: 1999-09-14 vtan created
// --------------------------------------------------------------------------
CExternalProcess::~CExternalProcess (void)
{
// Force the watcher thread to exit regardless of any job object
// messages that come in. This will prevent it using its reference
// to CExternalProcess which is now being destructed. It will also
// prevent the external process from being started up again now
// that we know the external process should go away.
if (_jobCompletionWatcher != NULL) { _jobCompletionWatcher->ForceExit(); }
// If the process is still alive here then give it 100 milliseconds to
// terminate before forcibly terminating it.
if (_hProcess != NULL) { DWORD dwExitCode;
if ((GetExitCodeProcess(_hProcess, &dwExitCode) == FALSE) || (STILL_ACTIVE == dwExitCode)) { if (WaitForSingleObject(_hProcess, 100) == WAIT_TIMEOUT) { NTSTATUS status;
status = Terminate();
#if (defined(DBG) || defined(PRERELEASE))
if (ERROR_ACCESS_DENIED == GetLastError()) { status = NtCurrentTeb()->LastStatusValue; if (STATUS_PROCESS_IS_TERMINATING == status) { status = STATUS_SUCCESS; } } TSTATUS(status);
#endif /* (defined(DBG) || defined(PRERELEASE)) */
} } } ReleaseHandle(_hProcess); _dwProcessID = 0;
if (_jobCompletionWatcher != NULL) { _jobCompletionWatcher->Release(); _jobCompletionWatcher = NULL; } if (_pIExternalProcess != NULL) { _pIExternalProcess->Release(); _pIExternalProcess = NULL; } }
// --------------------------------------------------------------------------
// CExternalProcess::SetInterface
//
// Arguments: pIExternalProcess = IExternalProcess interface pointer.
//
// Returns: <none>
//
// Purpose: Store the IExternalProcess interface pointer.
//
// History: 1999-09-14 vtan created
// --------------------------------------------------------------------------
void CExternalProcess::SetInterface (IExternalProcess *pIExternalProcess)
{ if (_pIExternalProcess != NULL) { _pIExternalProcess->Release(); _pIExternalProcess = NULL; } if (pIExternalProcess != NULL) { pIExternalProcess->AddRef(); } _pIExternalProcess = pIExternalProcess; }
// --------------------------------------------------------------------------
// CExternalProcess::GetInterface
//
// Arguments: <none>
//
// Returns: IExternalProcess*
//
// Purpose: Returns the IExternalProcess interface pointer. Not that the
// caller gets a reference.
//
// History: 2001-01-09 vtan created
// --------------------------------------------------------------------------
IExternalProcess* CExternalProcess::GetInterface (void) const
{ IExternalProcess *pIResult;
if (_pIExternalProcess != NULL) { pIResult = _pIExternalProcess; pIResult->AddRef(); } else { pIResult = NULL; } return(pIResult); }
// --------------------------------------------------------------------------
// CExternalProcess::SetParameter
//
// Arguments: pszParameter = String of parameter to append.
//
// Returns: <none>
//
// Purpose: Sets the parameter to append to each invokation of the
// external process.
//
// History: 1999-09-20 vtan created
// --------------------------------------------------------------------------
void CExternalProcess::SetParameter (const TCHAR* pszParameter)
{ if (pszParameter != NULL) { lstrcpyn(_szParameter, pszParameter, ARRAYSIZE(_szParameter)); } else { _szParameter[0] = TEXT('\0'); } }
// --------------------------------------------------------------------------
// CExternalProcess::Start
//
// Arguments: <none>
//
// Returns: NTSTATUS
//
// Purpose: If the external process is specified start it. If it starts
// successfully then register a wait callback in case it
// terminates unexpectedly so we can restart the process. This
// ensures that the external process is always available if
// required. If the external process cannot be started return
// with an error.
//
// History: 1999-09-20 vtan created
// --------------------------------------------------------------------------
NTSTATUS CExternalProcess::Start (void)
{ NTSTATUS status;
ASSERTMSG(_pIExternalProcess != NULL, "Must call CExternalProcess::SetInterface before using CExternalProcess::Start"); if (_szCommandLine[0] != TEXT('\0')) { STARTUPINFO startupInfo; PROCESS_INFORMATION processInformation; TCHAR szCommandLine[MAX_PATH * 2];
lstrcpy(szCommandLine, _szCommandLine); lstrcat(szCommandLine, _szParameter);
// Start the process on Winlogon's desktop.
ZeroMemory(&startupInfo, sizeof(startupInfo)); startupInfo.cb = sizeof(startupInfo); startupInfo.lpDesktop = TEXT("WinSta0\\Winlogon"); startupInfo.dwFlags = _dwStartFlags; startupInfo.wShowWindow = _wShowFlags; status = _pIExternalProcess->Start(szCommandLine, _dwCreateFlags | CREATE_SUSPENDED, startupInfo, processInformation); if (NT_SUCCESS(status)) {
// The process is created suspended so that it can
// assigned to the job object for this object.
TSTATUS(_job.AddProcess(processInformation.hProcess));
// The process is still suspended so resume the
// primary thread.
if (processInformation.hThread != NULL) { (DWORD)ResumeThread(processInformation.hThread); TBOOL(CloseHandle(processInformation.hThread)); }
// Keep the handle to the process so that we can kill
// it when our object goes out of scope.
_hProcess = processInformation.hProcess; _dwProcessID = processInformation.dwProcessId;
// Don't reallocate another CJobCompletionWatcher if
// one already exists. Just ignore this case.
if (_jobCompletionWatcher == NULL) { HANDLE hEvent;
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); _jobCompletionWatcher = new CJobCompletionWatcher(this, _job, hEvent); if ((_jobCompletionWatcher != NULL) && _jobCompletionWatcher->IsCreated() && (hEvent != NULL)) { (DWORD)WaitForSingleObject(hEvent, INFINITE); } if (hEvent != NULL) { TBOOL(CloseHandle(hEvent)); } } } } else { DISPLAYMSG("No external process to start in CExternalProcess::Start"); status = STATUS_UNSUCCESSFUL; } return(status); }
// --------------------------------------------------------------------------
// CExternalProcess::End
//
// Arguments: <none>
//
// Returns: NTSTATUS
//
// Purpose: End the process. Ends the watcher thread as well to release
// all held references.
//
// History: 2000-05-05 vtan created
// --------------------------------------------------------------------------
NTSTATUS CExternalProcess::End (void)
{ if (_jobCompletionWatcher != NULL) { _jobCompletionWatcher->ForceExit(); } return(STATUS_SUCCESS); }
// --------------------------------------------------------------------------
// CExternalProcess::Terminate
//
// Arguments: <none>
//
// Returns: NTSTATUS
//
// Purpose: Terminate the process unconditionally.
//
// History: 1999-10-14 vtan created
// --------------------------------------------------------------------------
NTSTATUS CExternalProcess::Terminate (void)
{ NTSTATUS status;
if (TerminateProcess(_hProcess, 0) != FALSE) { status = STATUS_SUCCESS; } else { status = CStatusCode::StatusCodeOfLastError(); } return(status); }
// --------------------------------------------------------------------------
// CExternalProcess::HandleNoProcess
//
// Arguments: <none>
//
// Returns: bool
//
// Purpose: This function restarts the external process if required. It
// uses the IExternalProcess to communicate with the external
// process controller to make the decisions. This function is
// only called when the active process count drops to zero. If
// the external process is being debugged then this will happen
// when the debugger quits as well.
//
// History: 1999-11-30 vtan created
// --------------------------------------------------------------------------
bool CExternalProcess::HandleNoProcess (void)
{ bool fResult;
fResult = true; NotifyNoProcess(); if (_pIExternalProcess != NULL) { if (_pIExternalProcess->AllowTermination(_dwProcessExitCode)) { TSTATUS(_pIExternalProcess->SignalTermination()); } else {
// Only try to start the external process 10 times (restart
// it 9 times). Give up and signal abnormal termination if exceeded.
if ((++_iRestartCount <= 9) && NT_SUCCESS(Start())) { TSTATUS(_pIExternalProcess->SignalRestart()); fResult = false; } else { TSTATUS(_pIExternalProcess->SignalAbnormalTermination()); } } } return(fResult); }
// --------------------------------------------------------------------------
// CExternalProcess::HandleNewProcess
//
// Arguments: dwProcessID = Process ID of new process.
//
// Returns: <none>
//
// Purpose: This function is called by the Job object watcher when a new
// process is added to the job. Normally this will fail because
// of the quota limit. However, when debugging is enabled this
// will be allowed.
//
// History: 1999-10-27 vtan created
// --------------------------------------------------------------------------
void CExternalProcess::HandleNewProcess (DWORD dwProcessID)
{ if (_dwProcessID != dwProcessID) { ReleaseHandle(_hProcess); _hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID); _dwProcessID = dwProcessID; } }
// --------------------------------------------------------------------------
// CExternalProcess::HandleTermination
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: If the external process terminates unexpectedly this function
// will be invoked by the wait callback when the process HANDLE
// becomes signaled. It's acceptable for the process to terminate
// if there is a dialog result so ignore this case. Otherwise
// close the handle to the process that died and wait for the
// job object signal that zero processes are actually running.
// That signal will restart the process.
//
// History: 1999-08-24 vtan created
// 1999-09-14 vtan factored
// --------------------------------------------------------------------------
void CExternalProcess::HandleTermination (DWORD dwProcessID)
{
// Make sure the process that is exiting is the process we are tracking.
// In every case other than debugging this will be true because the job
// object limits the active process count. In the case of debugging make
// sure we don't restart two processes because ntsd quit as well as the
// external process itself!
if (_dwProcessID == dwProcessID) { if (GetExitCodeProcess(_hProcess, &_dwProcessExitCode) == FALSE) { _dwProcessExitCode = STILL_ACTIVE; } ReleaseHandle(_hProcess); _dwProcessID = 0; } }
// --------------------------------------------------------------------------
// CExternalProcess::IsStarted
//
// Arguments: <none>
//
// Returns: bool
//
// Purpose: Returns whether there is an external process that has been
// started.
//
// History: 1999-09-14 vtan created
// --------------------------------------------------------------------------
bool CExternalProcess::IsStarted (void) const
{ return(_hProcess != NULL); }
// --------------------------------------------------------------------------
// CExternalProcess::NotifyNoProcess
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Derivable function for notification of process termination.
//
// History: 2001-01-09 vtan created
// --------------------------------------------------------------------------
void CExternalProcess::NotifyNoProcess (void)
{ }
// --------------------------------------------------------------------------
// CExternalProcess::AdjustForDebugging
//
// Arguments: <none>
//
// Returns: <none>
//
// Purpose: Adjusts the job object to allow debugging of the external
// process.
//
// History: 1999-10-22 vtan created
// --------------------------------------------------------------------------
void CExternalProcess::AdjustForDebugging (void)
{
#if (defined(DBG) || defined(PRERELEASE))
// If it looks like the external process is being debugged
// then lift the process restriction to allow it to be debugged.
if (IsBeingDebugged()) { _job.SetActiveProcessLimit(0); }
#endif /* (defined(DBG) || defined(PRERELEASE)) */
}
#if (defined(DBG) || defined(PRERELEASE))
// --------------------------------------------------------------------------
// CExternalProcess::IsBeingDebugged
//
// Arguments: <none>
//
// Returns: bool
//
// Purpose: Returns whether the external process will end up being started
// under a debugger.
//
// History: 2000-10-04 vtan created
// --------------------------------------------------------------------------
bool CExternalProcess::IsBeingDebugged (void) const
{ return(IsPrefixedWithNTSD() || IsImageFileExecutionDebugging()); }
// --------------------------------------------------------------------------
// CExternalProcess::IsPrefixedWithNTSD
//
// Arguments: <none>
//
// Returns: bool
//
// Purpose: Returns whether the command line starts with "ntsd".
//
// History: 1999-10-25 vtan created
// --------------------------------------------------------------------------
bool CExternalProcess::IsPrefixedWithNTSD (void) const
{
// Is the command line prefixed with "ntsd"?
return(CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, _szCommandLine, 4, kNTSD, 4) == CSTR_EQUAL); }
// --------------------------------------------------------------------------
// CExternalProcess::IsImageFileExecutionDebugging
//
// Arguments: <none>
//
// Returns: bool
//
// Purpose: Returns whether the system is set to debug this particular
// executable file via "Image File Execution Options".
//
// History: 1999-10-25 vtan created
// --------------------------------------------------------------------------
bool CExternalProcess::IsImageFileExecutionDebugging (void) const
{ bool fResult; TCHAR *pC, *pszFilePart; TCHAR szCommandLine[MAX_PATH], szExecutablePath[MAX_PATH];
fResult = false;
// Make a copy of the command line. Find the first space character
// or the end of the string and NULL terminate it. This does NOT
// check for quotes!
lstrcpy(szCommandLine, _szCommandLine); pC = szCommandLine; while ((*pC != TEXT(' ')) && (*pC != TEXT('\0'))) { ++pC; } *pC++ = TEXT('\0'); if (SearchPath(NULL, szCommandLine, TEXT(".exe"), ARRAYSIZE(szExecutablePath), szExecutablePath, &pszFilePart) != 0) { LONG errorCode; TCHAR szImageKey[MAX_PATH]; CRegKey regKey;
// Open the associated "Image File Execution Options" key.
lstrcpy(szImageKey, TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\")); lstrcat(szImageKey, pszFilePart); errorCode = regKey.Open(HKEY_LOCAL_MACHINE, szImageKey, KEY_READ); if (ERROR_SUCCESS == errorCode) {
// Read the "Debugger" value.
errorCode = regKey.GetString(TEXT("Debugger"), szCommandLine, ARRAYSIZE(szCommandLine)); if (ERROR_SUCCESS == errorCode) {
// Look for "ntsd".
fResult = (CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, szCommandLine, 4, kNTSD, 4) == CSTR_EQUAL); } } } return(fResult); }
#endif /* (defined(DBG) || defined(PRERELEASE)) */
|