Windows-Server-2003/enduser/troubleshoot/tshoot/registrymonitor.cpp

408 lines
11 KiB
C++

//
// MODULE: RegistryMonitor.cpp
//
// PURPOSE: Monitor changes to the registry.
//
// COMPANY: Saltmine Creative, Inc. (206)-284-7511 support@saltmine.com
//
// AUTHOR: Joe Mabel
//
// ORIGINAL DATE: 9-16-98
//
// NOTES:
//
// Version Date By Comments
//--------------------------------------------------------------------
// V3.0 09-16-98 JM
//
#pragma warning(disable:4786)
#include "stdafx.h"
#include "RegistryMonitor.h"
#include "apgts.h"//Added 20010302 for RUNNING_LOCAL_TS macro
#include "event.h"
#include "apiwraps.h"
#include "ThreadPool.h"
#include "apgtslog.h"
//////////////////////////////////////////////////////////////////////
// CRegistryMonitor::ThreadStatus
//////////////////////////////////////////////////////////////////////
/* static */ CString CRegistryMonitor::ThreadStatusText(ThreadStatus ts)
{
switch(ts)
{
case eBeforeInit: return _T("Before Init");
case eInit: return _T("Init");
case eFail: return _T("Fail");
case eDefaulting: return _T("Defaulting");
case eWait: return _T("Wait");
case eRun: return _T("Run");
case eExiting: return _T("Exiting");
default: return _T("");
}
}
//////////////////////////////////////////////////////////////////////
// CRegistryMonitor
// This class does the bulk of its work on a separate thread.
// The thread is created in the constructor by starting static function
// CDirectoryMonitor::RegistryMonitorTask
// That function, in turn does its work by calling private members of this class that
// are specific to use on the RegistryMonitorTask thread.
// When this goes out of scope, its own destructor calls ShutDown to stop the thread,
// waits for the thread to shut.
// Inherited methods from CRegistryMonitor are available to other threads.
//////////////////////////////////////////////////////////////////////
CRegistryMonitor::CRegistryMonitor( CDirectoryMonitor & DirectoryMonitor,
CThreadPool * pThreadPool,
const CString& strTopicName,
CHTMLLog *pLog )
: CAPGTSRegConnector( strTopicName ),
m_DirectoryMonitor(DirectoryMonitor),
m_bMustStartDirMonitor(true),
m_bMustStartThreadPool(true),
m_bShuttingDown(false),
m_dwErr(0),
m_ThreadStatus(eBeforeInit),
m_time(0),
m_pThreadPool(pThreadPool),
m_pLog( pLog )
{
enum {eHevMon, eHevInit, eHevShut, eThread, eOK} Progress = eHevMon;
SetThreadStatus(eBeforeInit);
m_hevMonitorRequested = ::CreateEvent(
NULL,
FALSE, // release one thread (the RegistryMonitorTask) on signal
FALSE, // initially non-signalled
NULL);
if (m_hevMonitorRequested)
{
Progress = eHevInit;
m_hevInitialized = ::CreateEvent(
NULL,
FALSE, // release one thread (this one) on signal
FALSE, // initially non-signalled
NULL);
if (m_hevInitialized)
{
Progress = eHevShut;
m_hevThreadIsShut = ::CreateEvent(
NULL,
FALSE, // release one thread (this one) on signal
FALSE, // initially non-signalled
NULL);
if (m_hevThreadIsShut)
{
Progress = eThread;
DWORD dwThreadID; // No need to hold onto dwThreadID in member variable.
// All Win32 functions take the handle m_hThread instead.
// The one reason you'd ever want to know this ID is for
// debugging
// Note that there is no corresponding ::CloseHandle(m_hThread).
// That is because the thread goes out of existence on the implicit
// ::ExitThread() when RegistryMonitorTask returns. See documentation of
// ::CreateThread for further details JM 10/22/98
m_hThread = ::CreateThread( NULL,
0,
(LPTHREAD_START_ROUTINE)RegistryMonitorTask,
this,
0,
&dwThreadID);
if (m_hThread)
Progress = eOK;
}
}
}
if (m_hThread)
{
// Wait for a set period, if failure then log error msg and wait infinite.
WAIT_INFINITE( m_hevInitialized );
}
else
{
m_dwErr = GetLastError();
CString str;
str.Format(_T("%d"), m_dwErr);
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
(Progress == eHevMon) ? _T("Can't create monitor event")
: (Progress == eHevInit) ? _T("Can't create \"init\" event")
: (Progress == eHevShut) ? _T("Can't create \"shut\" event")
: _T("Can't create thread"),
str,
EV_GTS_ERROR_REGMONITORTHREAD );
SetThreadStatus(eFail);
if (m_hevMonitorRequested)
::CloseHandle(m_hevMonitorRequested);
if (m_hevInitialized)
::CloseHandle(m_hevInitialized);
if (m_hevThreadIsShut)
::CloseHandle(m_hevThreadIsShut);
}
}
CRegistryMonitor::~CRegistryMonitor()
{
ShutDown();
if (m_hevMonitorRequested)
::CloseHandle(m_hevMonitorRequested);
if (m_hevInitialized)
::CloseHandle(m_hevInitialized);
if (m_hevThreadIsShut)
::CloseHandle(m_hevThreadIsShut);
}
void CRegistryMonitor::SetThreadStatus(ThreadStatus ts)
{
Lock();
m_ThreadStatus = ts;
time(&m_time);
Unlock();
}
DWORD CRegistryMonitor::GetStatus(ThreadStatus &ts, DWORD & seconds)
{
time_t timeNow;
Lock();
ts = m_ThreadStatus;
time(&timeNow);
seconds = timeNow - m_time;
Unlock();
return m_dwErr;
}
// Only for use by this class's own destructor.
void CRegistryMonitor::ShutDown()
{
Lock();
m_bShuttingDown = true;
if (m_hThread)
{
::SetEvent(m_hevMonitorRequested);
Unlock();
// Wait for a set period, if failure then log error msg and wait infinite.
WAIT_INFINITE( m_hevThreadIsShut );
}
else
Unlock();
}
// Must be called on RegistryMonitorTask thread. Handles all work of monitoring the directory.
void CRegistryMonitor::Monitor()
{
enum {eRegChange, eHev /*shutdown*/, eNumHandles};
HANDLE hList[eNumHandles]= { NULL }; // array of handles we can use when waiting for multiple events
HKEY hk= NULL; // handle to key in registry
DWORD dwNErr = 0;
LONG lResult = ERROR_SUCCESS + 1; // scratch for returns of any of several
// calls to Win32 Registry fns. Initialize to arbitrary value
// != ERROR_SUCCESS so we don't close what we haven't opened.
SetThreadStatus(eInit);
try
{
// create an event for registry notification
hList[eRegChange] = ::CreateEvent(NULL, FALSE, FALSE, NULL);
if (hList[eRegChange] == NULL)
{
throw CGenSysException( __FILE__, __LINE__,
_T("Registry notification event"),
EV_GTS_ERROR_REG_NFT_CEVT );
}
CString str = ThisProgramFullKey();
// Technically, KEY_ALL_ACCESS is overkill, but this program should always
// run in an environment where this shoudl succeed, so we haven't bothered
// trying to limit to only the access we need. At the very least, we need
// KEY_QUERY_VALUE | KEY_NOTIFY.
// [BC - 20010302] - Registry access needs to be restricted to run local TShoot
// for certain user accts, such as WinXP built in guest acct. To minimize change
// access only restricted for local TShoot, not online.
REGSAM samRegistryAccess= KEY_ALL_ACCESS;
if(RUNNING_LOCAL_TS())
samRegistryAccess= KEY_QUERY_VALUE | KEY_NOTIFY;
lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, str, 0, samRegistryAccess, &hk );
if (lResult != ERROR_SUCCESS)
{
CString strError;
strError.Format(_T("%ld"),lResult);
::SetEvent(m_hevInitialized); // OK to ask this object for registry values;
// of course, you'll just get defaults.
SetThreadStatus(eDefaulting);
throw CGeneralException( __FILE__, __LINE__, strError,
EV_GTS_ERROR_REG_NFT_OPKEY );
}
// ...and we also wait for an explicit wakeup
hList[eHev] = m_hevMonitorRequested;
while (true)
{
if (m_bShuttingDown)
break;
LoadChangedRegistryValues();
::SetEvent(m_hevInitialized); // OK to ask this object for registry values
// set up to be informed of change
lResult = ::RegNotifyChangeKeyValue( hk,
FALSE,
REG_NOTIFY_CHANGE_LAST_SET,
hList[eRegChange],
TRUE);
if (lResult != ERROR_SUCCESS)
{
CString strError;
strError.Format(_T("%ld"),lResult);
throw CGeneralException( __FILE__, __LINE__, strError,
EV_GTS_ERROR_REG_NFT_SETNTF );
}
::ResetEvent(m_hevMonitorRequested); // maybe we don't need to do this. JM 9/16/98
SetThreadStatus(eWait);
DWORD dwNotifyObj = WaitForMultipleObjects (
eNumHandles,
hList,
FALSE, // only need one object, not all
INFINITE);
SetThreadStatus(eRun);
}
}
catch (CGenSysException& x)
{
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( x.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
x.GetErrorMsg(), x.GetSystemErrStr(),
x.GetErrorCode() );
}
catch (CGeneralException& x)
{
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( x.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
x.GetErrorMsg(), _T("General exception"),
x.GetErrorCode() );
}
catch (...)
{
// Catch any other exception thrown.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
_T(""), _T(""),
EV_GTS_GEN_EXCEPTION );
}
if (hk != NULL)
::RegCloseKey(hk);
if (hList[eRegChange] != NULL)
::CloseHandle(hList[eRegChange]);
SetThreadStatus(eExiting);
}
// Must be called on RegistryMonitorTask thread.
void CRegistryMonitor::AckShutDown()
{
Lock();
::SetEvent(m_hevThreadIsShut);
Unlock();
}
// get new registry values into our internal data structure.
void CRegistryMonitor::LoadChangedRegistryValues()
{
int maskChanged;
int maskCreated;
Read(maskChanged, maskCreated);
// It actually matters that we set reload delay before we set directory monitor path.
// The first time through here, the call to m_DirectoryMonitor.SetResourceDirectory
// actually sets loose the DirectoryMonitorTask
if ( (maskChanged & eReloadDelay) == eReloadDelay)
{
DWORD dwReloadDelay;
GetNumericInfo(eReloadDelay, dwReloadDelay);
m_DirectoryMonitor.SetReloadDelay(dwReloadDelay);
}
if ( m_bMustStartDirMonitor || (maskChanged & eResourcePath) == eResourcePath)
{
CString strResourcePath;
GetStringInfo(eResourcePath, strResourcePath);
m_DirectoryMonitor.SetResourceDirectory(strResourcePath); // side effect: if the
// directory monitor is not yet started, this tells it what
// directory to monitor so it can start.
m_bMustStartDirMonitor = false;
}
if ( (maskChanged & eDetailedEventLogging) == eDetailedEventLogging)
{
DWORD dw;
GetNumericInfo(eDetailedEventLogging, dw);
CEvent::s_bLogAll = dw ? true : false;
}
if ((maskChanged & eLogFilePath) == eLogFilePath)
{
// Notify the logging object about the new logging file path.
CString strLogFilePath;
GetStringInfo( eLogFilePath, strLogFilePath);
m_pLog->SetLogDirectory( strLogFilePath );
}
if ( m_bMustStartThreadPool
|| (maskChanged & eMaxThreads) == eMaxThreads
|| (maskChanged & eThreadsPP) == eThreadsPP )
{
m_pThreadPool->ExpandPool(GetDesiredThreadCount());
m_bMustStartThreadPool = false;
}
return;
}
// Main routine of a thread responsible for monitoring the registry.
// INPUT lpParams
// Always returns 0.
/* static */ UINT WINAPI CRegistryMonitor::RegistryMonitorTask(LPVOID lpParams)
{
reinterpret_cast<CRegistryMonitor*>(lpParams)->Monitor();
reinterpret_cast<CRegistryMonitor*>(lpParams)->AckShutDown();
return 0;
}