// // 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(lpParams)->Monitor(); reinterpret_cast(lpParams)->AckShutDown(); return 0; }