WindowsXP-SP1/enduser/troubleshoot/tshoot/dirmonitor.cpp

789 lines
22 KiB
C++

//
// MODULE: DirMonitor.cpp
//
// PURPOSE: Monitor changes to LST, DSC, HTI, BES files.
//
// COMPANY: Saltmine Creative, Inc. (206)-284-7511 support@saltmine.com
//
// AUTHOR: Joe Mabel
//
// ORIGINAL DATE: 9-17-98
//
// NOTES:
//
// Version Date By Comments
//--------------------------------------------------------------------
// V3.0 09-17-98 JM
//
#pragma warning(disable:4786)
#include "stdafx.h"
#include <algorithm>
#include "DirMonitor.h"
#include "event.h"
#include "apiwraps.h"
#include "CharConv.h"
#ifdef LOCAL_TROUBLESHOOTER
#include "LocalLSTReader.h"
#include "CHMFileReader.h"
#endif
#include "apgts.h" // Need for Local-Online macros.
const DWORD k_secsDefaultReloadDelay = 40; // In practice, this default should not matter,
// because SetReloadDelay() should be called before
// SetResourceDirectory(). However, 40 is a typical
// reasonable value for m_secsReloadDelay.
/////////////////////////////////////////////////////////////////////
// CTopicFileTracker
/////////////////////////////////////////////////////////////////////
CTopicFileTracker::CTopicFileTracker() :
CFileTracker()
{
}
CTopicFileTracker::~CTopicFileTracker()
{
}
void CTopicFileTracker::AddTopicInfo(const CTopicInfo & topicinfo)
{
m_topicinfo = topicinfo;
// set CFileTracker member variables accordingly for files that are present.
// If they are not present i.e. empty strings then adding them here results in
// unnecessary event log entries.
AddFile(topicinfo.GetDscFilePath());
CString strHTI = topicinfo.GetHtiFilePath();
if (!strHTI.IsEmpty())
AddFile(strHTI);
CString strBES = topicinfo.GetBesFilePath();
if (!strBES.IsEmpty())
AddFile(strBES);
}
const CTopicInfo & CTopicFileTracker::GetTopicInfo() const
{
return m_topicinfo;
}
/////////////////////////////////////////////////////////////////////
// CTemplateFileTracker
/////////////////////////////////////////////////////////////////////
CTemplateFileTracker::CTemplateFileTracker() :
CFileTracker()
{
}
CTemplateFileTracker::~CTemplateFileTracker()
{
}
void CTemplateFileTracker::AddTemplateName( const CString& strTemplateName )
{
m_strTemplateName= strTemplateName;
AddFile( strTemplateName );
}
const CString& CTemplateFileTracker::GetTemplateName() const
{
return m_strTemplateName;
}
//////////////////////////////////////////////////////////////////////
// CDirectoryMonitor::ThreadStatus
//////////////////////////////////////////////////////////////////////
/* static */ CString CDirectoryMonitor::ThreadStatusText(ThreadStatus ts)
{
switch(ts)
{
case eBeforeInit: return _T("Before Init");
case eFail: return _T("Fail");
case eWaitDirPath: return _T("Wait For Dir Path");
case eWaitChange: return _T("Wait for Change");
case eWaitSettle: return _T("Wait to Settle");
case eRun: return _T("Run");
case eBeforeWaitChange: return _T("Before Wait Change");
case eExiting: return _T("Exiting");
default: return _T("");
}
}
/////////////////////////////////////////////////////////////////////
// CDirectoryMonitor
// This class does the bulk of its work on a separate thread.
// The thread is created in the constructor by starting static function
// CDirectoryMonitor::DirectoryMonitorTask
// That function, in turn does its work by calling private members of this class that
// are specific to use on the DirectoryMonitorTask thread.
// When this goes out of scope, its own destructor calls ShutDown to stop the thread,
// waits for the thread to shut.
// The following methods are available for other threads communicating with that thread:
// CDirectoryMonitor::SetReloadDelay
// CDirectoryMonitor::SetResourceDirectory
/////////////////////////////////////////////////////////////////////
CDirectoryMonitor::CDirectoryMonitor(CTopicShop & TopicShop, const CString& strTopicName) :
m_strTopicName(strTopicName),
m_TopicShop(TopicShop),
m_pErrorTemplate(NULL),
m_strDirPath(_T("")), // Essential that this starts blank. Getting a different
// value is how we start the DirectoryMonitorTask thread.
m_bDirPathChanged(false),
m_bShuttingDown(false),
m_secsReloadDelay(k_secsDefaultReloadDelay),
m_pTrackLst( NULL ),
m_pTrackErrorTemplate( NULL ),
m_pLst( NULL ),
m_dwErr(0),
m_ThreadStatus(eBeforeInit),
m_time(0)
{
enum {eHevMon, eHevShut, eThread, eOK} Progress = eHevMon;
SetThreadStatus(eBeforeInit);
m_hevMonitorRequested = ::CreateEvent(
NULL,
FALSE, // release one thread (the DirectoryMonitorTask) on signal
FALSE, // initially non-signalled
NULL);
if (m_hevMonitorRequested)
{
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 DirectoryMonitorTask returns. See documentation of
// ::CreateThread for further details JM 10/22/98
m_hThread = ::CreateThread( NULL,
0,
(LPTHREAD_START_ROUTINE)DirectoryMonitorTask,
this,
0,
&dwThreadID);
if (m_hThread)
Progress = eOK;
}
}
if (Progress != eOK)
{
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 == eHevShut) ? _T("Can't create \"shut\" event")
: _T("Can't create thread"),
str,
EV_GTS_ERROR_DIRMONITORTHREAD );
SetThreadStatus(eFail);
if (m_hevMonitorRequested)
::CloseHandle(m_hevMonitorRequested);
if (m_hevThreadIsShut)
::CloseHandle(m_hevThreadIsShut);
}
}
CDirectoryMonitor::~CDirectoryMonitor()
{
ShutDown();
if (m_hevMonitorRequested)
::CloseHandle(m_hevMonitorRequested);
if (m_hevThreadIsShut)
::CloseHandle(m_hevThreadIsShut);
if (m_pErrorTemplate)
delete m_pErrorTemplate;
if (m_pTrackLst)
delete m_pTrackLst;
if (m_pTrackErrorTemplate)
delete m_pTrackErrorTemplate;
}
void CDirectoryMonitor::SetThreadStatus(ThreadStatus ts)
{
LOCKOBJECT();
m_ThreadStatus = ts;
time(&m_time);
UNLOCKOBJECT();
}
DWORD CDirectoryMonitor::GetStatus(ThreadStatus &ts, DWORD & seconds) const
{
time_t timeNow;
LOCKOBJECT();
ts = m_ThreadStatus;
time(&timeNow);
seconds = timeNow - m_time;
UNLOCKOBJECT();
return m_dwErr;
}
// Only for use by this class's own destructor.
void CDirectoryMonitor::ShutDown()
{
LOCKOBJECT();
m_bShuttingDown = true;
if (m_hThread)
{
::SetEvent(m_hevMonitorRequested);
UNLOCKOBJECT();
// Wait for a set period, if failure then log error msg and wait infinite.
WAIT_INFINITE( m_hevThreadIsShut );
}
else
UNLOCKOBJECT();
}
// For use by the DirectoryMonitorTask thread.
// Read LST file and add any topics that are not already in previously read LST file contents
void CDirectoryMonitor::LstFileDrivesTopics()
{
// previous LST file contents, saved for comparison.
CAPGTSLSTReader *pLstOld = m_pLst;
if (! m_strLstPath.IsEmpty() )
{
try
{
#ifdef LOCAL_TROUBLESHOOTER
m_pLst = new CLocalLSTReader( CPhysicalFileReader::makeReader( m_strLstPath ), m_strTopicName);
#else
m_pLst = new CAPGTSLSTReader( dynamic_cast<CPhysicalFileReader*>(new CNormalFileReader(m_strLstPath)) );
#endif
}
catch (bad_alloc&)
{
// Restore old LST contents.
m_pLst = pLstOld;
// Rethrow exception, logging handled upstream.
throw;
}
if (! m_pLst->Read())
{
// Restore old LST contents and log error.
delete m_pLst;
m_pLst = pLstOld;
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
_T(""),
_T(""),
EV_GTS_ERROR_LST_FILE_READ );
}
else
{
CTopicInfoVector arrNewTopicInfo;
m_pLst->GetDifference(pLstOld, arrNewTopicInfo);
if (pLstOld)
delete pLstOld;
for (CTopicInfoVector::iterator itNewTopicInfo = arrNewTopicInfo.begin();
itNewTopicInfo != arrNewTopicInfo.end();
itNewTopicInfo++
)
{
// Let the Topic Shop know about the new topic
m_TopicShop.AddTopic(*itNewTopicInfo);
// add it to our list of files to track for changes
CTopicFileTracker TopicFileTracker;
TopicFileTracker.AddTopicInfo(*itNewTopicInfo);
LOCKOBJECT();
try
{
m_arrTrackTopic.push_back(TopicFileTracker);
}
catch (exception& x)
{
CString str;
// Note STL exception in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
CCharConversion::ConvertACharToString(x.what(), str),
_T(""),
EV_GTS_STL_EXCEPTION );
}
UNLOCKOBJECT();
}
}
}
// if topic shop not already open, open it
m_TopicShop.OpenShop();
}
// Called by the topic shop to add alternate templates to track.
void CDirectoryMonitor::AddTemplateToTrack( const CString& strTemplateName )
{
LOCKOBJECT();
try
{
CTemplateFileTracker TemplateFileTracker;
TemplateFileTracker.AddTemplateName( strTemplateName );
m_arrTrackTemplate.push_back( TemplateFileTracker );
}
catch (exception& x)
{
CString str;
// Note STL exception in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
CCharConversion::ConvertACharToString(x.what(), str),
_T(""),
EV_GTS_STL_EXCEPTION );
}
UNLOCKOBJECT();
}
// For use by the DirectoryMonitorTask thread.
void CDirectoryMonitor::ReadErrorTemplate()
{
LOCKOBJECT();
if (m_pErrorTemplate)
delete m_pErrorTemplate;
CString str = k_strDefaultErrorTemplateBefore;
str += k_strErrorTemplateKey;
str += k_strDefaultErrorTemplateAfter;
try
{
m_pErrorTemplate = new CSimpleTemplate( CPhysicalFileReader::makeReader( m_strErrorTemplatePath ), str );
}
catch (bad_alloc&)
{
UNLOCKOBJECT();
// Rethrow the exception.
throw;
}
m_pErrorTemplate->Read();
UNLOCKOBJECT();
}
// For use by any thread. In this class because CDirectoryMonitor needs to own
// ErrorTemplate, since it can change during run of system.
void CDirectoryMonitor::CreateErrorPage(const CString & strError, CString& out) const
{
LOCKOBJECT();
if (m_pErrorTemplate)
{
vector<CTemplateInfo> arrTemplateInfo;
CTemplateInfo info(k_strErrorTemplateKey, strError);
try
{
arrTemplateInfo.push_back(info);
m_pErrorTemplate->CreatePage( arrTemplateInfo, out );
}
catch (exception& x)
{
CString str;
// Note STL exception in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
CCharConversion::ConvertACharToString(x.what(), str),
_T(""),
EV_GTS_STL_EXCEPTION );
// Generate the default error page to be safe.
out = k_strDefaultErrorTemplateBefore + strError + k_strDefaultErrorTemplateAfter;
}
}
else
out = k_strDefaultErrorTemplateBefore + strError + k_strDefaultErrorTemplateAfter;
UNLOCKOBJECT();
}
// Must be called on DirectoryMonitorTask thread.
// Handles all work of monitoring the directory. Loops till shutdown.
void CDirectoryMonitor::Monitor()
{
enum {
#ifndef LOCAL_TROUBLESHOOTER
eDirChange, // file in directory changed
#endif
eHev, // shutdown or change what directory
eNumHandles };
// array of handles we use when waiting for multiple events.
// Initialize first entry to default bad value.
HANDLE hList[eNumHandles]= { INVALID_HANDLE_VALUE };
if (m_strDirPath.GetLength() == 0)
{
SetThreadStatus(eWaitDirPath);
// Block this thread until notification that the directory path has been set.
::WaitForSingleObject( m_hevMonitorRequested, INFINITE);
}
SetThreadStatus(eRun);
try
{
if (RUNNING_ONLINE_TS())
{
// The DirPathChanged flag should be set here, enforce it if not.
ASSERT( m_bDirPathChanged );
if (!m_bDirPathChanged)
m_bDirPathChanged= true;
}
// Wait for an explicit wakeup.
hList[eHev] = m_hevMonitorRequested;
while (true)
{
LOCKOBJECT();
if (m_bShuttingDown)
{
UNLOCKOBJECT();
break;
}
if (m_bDirPathChanged)
{
#ifndef LOCAL_TROUBLESHOOTER
// Set the directory to be monitored.
if (hList[eDirChange] != INVALID_HANDLE_VALUE)
::FindCloseChangeNotification( hList[eDirChange] );
while (true)
{
// handle to monitor for change in the resource directory
hList[eDirChange] = ::FindFirstChangeNotification(m_strDirPath,
TRUE, // monitor subdirectories (for multilingual)
FILE_NOTIFY_CHANGE_LAST_WRITE
);
if (hList[eDirChange] == INVALID_HANDLE_VALUE)
{
// resource directoty does not exist.
// Track creation of directories in upper directory
// - it might be resource directory
bool bFail = false;
CString strUpperDir = m_strDirPath; // directory above resource directory (m_strDirPath)
if ( strUpperDir[strUpperDir.GetLength()-1] == _T('\\')
|| strUpperDir[strUpperDir.GetLength()-1] == _T('/'))
{
strUpperDir = strUpperDir.Left(strUpperDir.GetLength() ? strUpperDir.GetLength()-1 : 0);
}
int slash_last = max(strUpperDir.ReverseFind(_T('\\')),
strUpperDir.ReverseFind(_T('/')));
if (-1 != slash_last)
{
strUpperDir = strUpperDir.Left(slash_last);
hList[eDirChange] = ::FindFirstChangeNotification(strUpperDir,
TRUE, // monitor subdirectories (for multilingual)
FILE_NOTIFY_CHANGE_DIR_NAME
);
if (hList[eDirChange] == INVALID_HANDLE_VALUE)
bFail = true;
}
else
bFail = true;
if (!bFail)
{
// We have a valid handle, exit this loop.
SetThreadStatus(eRun);
break;
}
else
{
// typically would mean none of resource directory or its upper
// directory is valid, log this.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
m_strDirPath, _T(""),
EV_GTS_ERROR_CANT_FILE_NOTIFY );
SetThreadStatus(eWaitDirPath);
// Block this thread until notification that the directory path
// has been correctly set. Unlock the object so that the event
// can be set.
UNLOCKOBJECT();
::WaitForSingleObject( m_hevMonitorRequested, INFINITE);
LOCKOBJECT();
}
}
else
{
// We have a valid handle, exit this loop.
SetThreadStatus(eRun);
break;
}
}
#endif
m_bDirPathChanged = false;
if (m_pTrackLst)
delete m_pTrackLst;
m_pTrackLst = new CFileTracker;
if (RUNNING_ONLINE_TS())
m_pTrackLst->AddFile(m_strLstPath);
if (m_pTrackErrorTemplate)
delete m_pTrackErrorTemplate;
m_pTrackErrorTemplate = new CFileTracker;
if (RUNNING_ONLINE_TS())
m_pTrackErrorTemplate->AddFile(m_strErrorTemplatePath);
UNLOCKOBJECT();
ReadErrorTemplate();
LstFileDrivesTopics();
}
else
{
UNLOCKOBJECT();
if (m_pTrackLst && m_pTrackLst->Changed())
LstFileDrivesTopics();
if (m_pTrackErrorTemplate && m_pTrackErrorTemplate->Changed( false ))
ReadErrorTemplate();
}
LOCKOBJECT();
for (vector<CTopicFileTracker>::iterator itTopicFiles = m_arrTrackTopic.begin();
itTopicFiles != m_arrTrackTopic.end();
itTopicFiles ++
)
{
#ifdef LOCAL_TROUBLESHOOTER
if (m_bDirPathChanged)
#else
if (itTopicFiles->Changed())
#endif
m_TopicShop.BuildTopic(itTopicFiles->GetTopicInfo().GetNetworkName());
if (m_bShuttingDown)
break;
}
if (RUNNING_ONLINE_TS())
{
// Check if any of the alternate template files need to be reloaded.
for (vector<CTemplateFileTracker>::iterator itTemplateFiles = m_arrTrackTemplate.begin();
itTemplateFiles != m_arrTrackTemplate.end();
itTemplateFiles ++
)
{
if (itTemplateFiles->Changed())
m_TopicShop.BuildTemplate( itTemplateFiles->GetTemplateName() );
if (m_bShuttingDown)
break;
}
}
::ResetEvent(m_hevMonitorRequested);
SetThreadStatus(eWaitChange);
UNLOCKOBJECT();
DWORD dwNotifyObj = WaitForMultipleObjects (
eNumHandles,
hList,
FALSE, // only need one object, not all
INFINITE);
SetThreadStatus(eBeforeWaitChange);
// Ideally we would update files here.
// Unfortunately, we get a notification that someone has _started_
// writing to a file, not that they've finished, so we have to put in
// an artificial delay.
// We must let the system "settle down".
while (
#ifndef LOCAL_TROUBLESHOOTER
dwNotifyObj == WAIT_OBJECT_0+eDirChange &&
#endif
!m_bShuttingDown)
{
#ifndef LOCAL_TROUBLESHOOTER
// wait for the next change
if (FindNextChangeNotification( hList[eDirChange] ) == FALSE)
{
// 1) we don't believe this will ever occur
// 2) After a moderate amount of research, we have no idea how
// to recover from it if it does occur.
// SO: unless we ever actually see this, we're not going to waste
// more time researching a recovery strategy. Just throw an exception,
// effectively terminating this thread.
throw CGenSysException( __FILE__, __LINE__, m_strDirPath,
EV_GTS_ERROR_WAIT_NEXT_NFT );
}
#endif
SetThreadStatus(eWaitSettle);
dwNotifyObj = WaitForMultipleObjects (
eNumHandles,
hList,
FALSE, // only need one object, not all
m_secsReloadDelay * 1000); // convert to milliseconds
}
if (dwNotifyObj == WAIT_FAILED)
{
// 1) we don't believe this will ever occur
// 2) After a moderate amount of research, we have no idea how
// to recover from it if it does occur.
// SO: unless we ever actually see this, we're not going to waste
// more time researching a recovery strategy. Just throw an exception,
// effectively terminating this thread.
throw CGenSysException( __FILE__, __LINE__, _T("Unexpected Return State"),
EV_GTS_DEBUG );
}
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 (bad_alloc&)
{
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
_T(""), _T(""), EV_GTS_CANT_ALLOC );
}
catch (exception& x)
{
// Catch any STL exceptions thrown so that Terminate() is not called.
CString str;
CString ErrStr;
// Attempt to pull any system error code.
ErrStr.Format( _T("%ld"), ::GetLastError() );
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
CCharConversion::ConvertACharToString(x.what(), str),
ErrStr,
EV_GTS_GENERIC_PROBLEM );
}
catch (...)
{
// Catch any other exception thrown.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
_T(""), _T(""),
EV_GTS_GEN_EXCEPTION );
}
#ifndef LOCAL_TROUBLESHOOTER
if (hList[eDirChange] != INVALID_HANDLE_VALUE)
::FindCloseChangeNotification( hList[eDirChange] );
#endif
SetThreadStatus(eExiting);
}
// For general use (not part of DirectoryMonitorTask thread)
// Typically, first call to this comes _before_ first call to SetResourceDirectory;
// This allows caller to set reload delay before triggering any action on
// DirectoryMonitorTask thread.
void CDirectoryMonitor::SetReloadDelay(DWORD secsReloadDelay)
{
LOCKOBJECT();
m_secsReloadDelay = secsReloadDelay;
UNLOCKOBJECT();
}
// For general use (not part of DirectoryMonitorTask thread)
// Allows indicating that the resource directory has changed
// Until this is called, the DirectoryMonitorTask thread really won't do anything
void CDirectoryMonitor::SetResourceDirectory(const CString & strDirPath)
{
LOCKOBJECT();
if (strDirPath != m_strDirPath)
{
m_strDirPath = strDirPath;
m_strLstPath = strDirPath + LSTFILENAME;
m_strErrorTemplatePath = strDirPath + k_strErrorTemplateFileName;
m_bDirPathChanged = true;
::SetEvent(m_hevMonitorRequested);
}
UNLOCKOBJECT();
}
// Must be called on DirectoryMonitorTask thread.
void CDirectoryMonitor::AckShutDown()
{
LOCKOBJECT();
::SetEvent(m_hevThreadIsShut);
UNLOCKOBJECT();
}
// Main routine of a thread responsible for monitoring the directory.
// INPUT lpParams
// Always returns 0.
/* static */ UINT WINAPI CDirectoryMonitor::DirectoryMonitorTask(LPVOID lpParams)
{
reinterpret_cast<CDirectoryMonitor*>(lpParams)->Monitor();
reinterpret_cast<CDirectoryMonitor*>(lpParams)->AckShutDown();
return 0;
}