387 lines
10 KiB
C++
387 lines
10 KiB
C++
|
// DirWatch.cpp: implementation of the CWatchFileSys class.
|
||
|
//
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
#include "stdafx.h"
|
||
|
#include "DirWatch.h"
|
||
|
#include "Error.h"
|
||
|
#include "MT.h"
|
||
|
#include "AutoPtr.h"
|
||
|
#include "Error.h"
|
||
|
#include "iadmw.h" // COM Interface header
|
||
|
#include "iiscnfg.h" // MD_ & IIS_MD_ #defines
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
// Construction/Destruction
|
||
|
//////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
CWatchFileSys::CWatchFileSys()
|
||
|
: m_WatchInfo(this), m_pOpQ(NULL)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
CWatchFileSys::~CWatchFileSys()
|
||
|
{
|
||
|
// verify that thread terminated
|
||
|
ShutDown();
|
||
|
}
|
||
|
|
||
|
HRESULT CWatchFileSys::NewInit(COpQueue *pOpQ)
|
||
|
{
|
||
|
_ASSERTE(pOpQ && !m_pOpQ);
|
||
|
m_pOpQ = pOpQ;
|
||
|
HRESULT hr = S_OK;
|
||
|
CComPtr<IMSAdminBase> pIAdminBase;
|
||
|
|
||
|
// check if we already have a metabase instance
|
||
|
// create adminbase instance
|
||
|
hr = CoCreateInstance(CLSID_MSAdminBase,
|
||
|
NULL,
|
||
|
CLSCTX_ALL,
|
||
|
IID_IMSAdminBase,
|
||
|
(void **) &pIAdminBase);
|
||
|
IF_FAIL_RTN1(hr,"CoCreateInstance IID_IMSAdminBase");
|
||
|
|
||
|
METADATA_HANDLE hMD = NULL;
|
||
|
WCHAR szKeyName[3+6+ 2* METADATA_MAX_NAME_LEN] // /LM/W3SVC/sitename/vir_dir
|
||
|
= L"/LM/W3SVC/";
|
||
|
LPTSTR szSiteKeyName = &szKeyName[wcslen(szKeyName)]; // point to the end of string so we can append it
|
||
|
DWORD iSiteEnumIndex = 0;
|
||
|
LPTSTR szVDirKeyName = NULL;
|
||
|
DWORD iVDirEnumIndex = 0;
|
||
|
|
||
|
hr = pIAdminBase->OpenKey(METADATA_MASTER_ROOT_HANDLE,
|
||
|
szKeyName,
|
||
|
METADATA_PERMISSION_READ,
|
||
|
20,
|
||
|
&hMD);
|
||
|
IF_FAIL_RTN1(hr,"IAdminBase::OpenKey");
|
||
|
|
||
|
METADATA_RECORD MDRec;
|
||
|
DWORD iBufLen = 1024;
|
||
|
DWORD iReqBufLen = 0;
|
||
|
PBYTE pbBuf = new BYTE[iBufLen];
|
||
|
if(!pbBuf)
|
||
|
{
|
||
|
pIAdminBase->CloseKey(hMD);
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
DWORD iDataIndex = 0;
|
||
|
|
||
|
while(SUCCEEDED(hr = pIAdminBase->EnumKeys(hMD,TEXT(""),szSiteKeyName,iSiteEnumIndex)))
|
||
|
{
|
||
|
// iterate through all virtual sites on this machine
|
||
|
wcscat(szSiteKeyName,L"/ROOT/");
|
||
|
szVDirKeyName = szSiteKeyName + wcslen(szSiteKeyName);
|
||
|
|
||
|
iVDirEnumIndex = 0;
|
||
|
while(SUCCEEDED(hr = pIAdminBase->EnumKeys(hMD,szSiteKeyName,szVDirKeyName,iVDirEnumIndex)))
|
||
|
{
|
||
|
// iterate through all virtual directories in each site
|
||
|
MDRec.dwMDIdentifier = MD_VR_PATH;
|
||
|
MDRec.dwMDAttributes = METADATA_INHERIT;
|
||
|
MDRec.dwMDUserType = IIS_MD_UT_FILE;
|
||
|
MDRec.dwMDDataType = ALL_METADATA;
|
||
|
MDRec.dwMDDataLen = iBufLen;
|
||
|
MDRec.pbMDData = pbBuf;
|
||
|
hr = pIAdminBase->GetData(hMD,szSiteKeyName,&MDRec,&iReqBufLen);
|
||
|
if(hr == RETURNCODETOHRESULT(ERROR_INSUFFICIENT_BUFFER))
|
||
|
{
|
||
|
delete [] pbBuf;
|
||
|
pbBuf = new BYTE[iReqBufLen];
|
||
|
if(!pbBuf)
|
||
|
{
|
||
|
pIAdminBase->CloseKey(hMD);
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
iBufLen = iReqBufLen;
|
||
|
MDRec.dwMDDataLen = iBufLen;
|
||
|
MDRec.pbMDData = pbBuf;
|
||
|
hr = pIAdminBase->GetData(hMD,szSiteKeyName,&MDRec,&iReqBufLen);
|
||
|
}
|
||
|
|
||
|
// @todo: verify that this dir should be watched
|
||
|
// i.e. check if do-not-version flag is set
|
||
|
|
||
|
if(SUCCEEDED(hr))
|
||
|
{
|
||
|
// add
|
||
|
wstring szPrj(L"/Files/"); //@todo: decide on prj
|
||
|
szPrj.append(szSiteKeyName);
|
||
|
hr = Add((LPCTSTR)MDRec.pbMDData,szPrj.c_str());
|
||
|
IF_FAIL_RPT1(hr,"CWatchFileSys::Add");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CError::Trace("Can't get dir for ");
|
||
|
CError::Trace(szVDirKeyName);
|
||
|
CError::Trace("\n");
|
||
|
}
|
||
|
iVDirEnumIndex++;
|
||
|
}
|
||
|
iSiteEnumIndex++;
|
||
|
}
|
||
|
pIAdminBase->CloseKey(hMD);
|
||
|
delete [] pbBuf;
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
void CWatchFileSys::ShutDownHelper(CWatchInfo &rWatchInfo)
|
||
|
{
|
||
|
if(rWatchInfo.m_hThread)
|
||
|
{
|
||
|
// end notification thread
|
||
|
PostQueuedCompletionStatus(rWatchInfo.m_hCompPort,0,0,NULL);
|
||
|
// wait for thread to finish
|
||
|
WaitForSingleObject(rWatchInfo.m_hThread,INFINITE);
|
||
|
CloseHandle(rWatchInfo.m_hThread);
|
||
|
rWatchInfo.m_hThread = NULL;
|
||
|
rWatchInfo.m_iThreadID = 0;
|
||
|
}
|
||
|
if(rWatchInfo.m_hCompPort)
|
||
|
{
|
||
|
// clean up
|
||
|
CloseHandle(rWatchInfo.m_hCompPort);
|
||
|
rWatchInfo.m_hCompPort = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CWatchFileSys::ShutDown()
|
||
|
{
|
||
|
ShutDownHelper(m_WatchInfo);
|
||
|
m_pOpQ = NULL;
|
||
|
}
|
||
|
|
||
|
DWORD WINAPI CWatchFileSys::NotificationThreadProc(LPVOID lpParam)
|
||
|
{
|
||
|
_ASSERTE(lpParam);
|
||
|
CWatchInfo *pWI = (CWatchInfo*) lpParam;
|
||
|
CWatchFileSys *pWatchFileSys = pWI->m_pWatchFileSys;
|
||
|
|
||
|
// vars for accessing the notification
|
||
|
DWORD iBytes = 0;
|
||
|
CDirInfo *pDirInfo = NULL;
|
||
|
LPOVERLAPPED pOverlapped = NULL;
|
||
|
PFILE_NOTIFY_INFORMATION pfni = NULL;
|
||
|
DWORD cbOffset = 0;
|
||
|
|
||
|
// vars for creating a file op
|
||
|
HRESULT hr;
|
||
|
COpFileSys *pOp = NULL;
|
||
|
LPCTSTR szPrj = NULL;
|
||
|
LPCTSTR szDir = NULL;
|
||
|
wstring szFileName;
|
||
|
wstring szOldFileName;
|
||
|
|
||
|
do
|
||
|
{
|
||
|
_ASSERTE(pWI->m_hCompPort);
|
||
|
GetQueuedCompletionStatus(pWI->m_hCompPort,
|
||
|
&iBytes,
|
||
|
(LPDWORD) &pDirInfo,
|
||
|
&pOverlapped,
|
||
|
INFINITE);
|
||
|
if(pDirInfo)
|
||
|
{
|
||
|
// get ptr to first file_notify_info in buffer
|
||
|
pfni = (PFILE_NOTIFY_INFORMATION) pDirInfo->m_cBuffer;
|
||
|
|
||
|
// clean
|
||
|
szFileName.erase(); // empty to avoid compare wrong compares
|
||
|
szOldFileName.erase(); // empty
|
||
|
|
||
|
// remember dir and prj they are the same for all entries
|
||
|
szPrj = pDirInfo->m_szPrj.c_str();
|
||
|
szDir = pDirInfo->m_szDir.c_str();
|
||
|
|
||
|
// process all file_notify_infos in buffer
|
||
|
_ASSERTE(pWatchFileSys->m_pOpQ);
|
||
|
do
|
||
|
{
|
||
|
cbOffset = pfni->NextEntryOffset;
|
||
|
|
||
|
// sometime an errorous action #0 is send, let's ignore it
|
||
|
switch(pfni->Action) {
|
||
|
case FILE_ACTION_ADDED:
|
||
|
case FILE_ACTION_REMOVED:
|
||
|
case FILE_ACTION_MODIFIED:
|
||
|
case FILE_ACTION_RENAMED_OLD_NAME:
|
||
|
case FILE_ACTION_RENAMED_NEW_NAME:
|
||
|
break;
|
||
|
default:
|
||
|
// unknown action, let's ignore it
|
||
|
pfni = (PFILE_NOTIFY_INFORMATION) ((LPBYTE)pfni + cbOffset);// get next offset
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// on rename remember old filename
|
||
|
szOldFileName.erase();
|
||
|
if(pfni->Action == FILE_ACTION_RENAMED_OLD_NAME)
|
||
|
{
|
||
|
// make sure next entry exists and is new-name entry
|
||
|
_ASSERTE(cbOffset); // there is another entry
|
||
|
PFILE_NOTIFY_INFORMATION pNextfni = (PFILE_NOTIFY_INFORMATION) ((LPBYTE)pfni + cbOffset);
|
||
|
_ASSERTE(pNextfni->Action == FILE_ACTION_RENAMED_NEW_NAME); // the next entry contians the new name
|
||
|
|
||
|
// assign old name
|
||
|
szOldFileName.assign(pfni->FileName,pfni->FileNameLength/2);
|
||
|
|
||
|
// skip to next (new-name) entry
|
||
|
pfni = pNextfni;
|
||
|
cbOffset = pNextfni->NextEntryOffset;
|
||
|
|
||
|
// clear szFileName so it doesn't get skiped in next lines
|
||
|
szFileName.erase();
|
||
|
}
|
||
|
|
||
|
// assign affected filename
|
||
|
szFileName.assign(pfni->FileName,pfni->FileNameLength/2);
|
||
|
|
||
|
// create new operation
|
||
|
pOp = new COpFileSys(pfni->Action,szPrj,szDir,szFileName.c_str(),szOldFileName.c_str());
|
||
|
if(!pOp)
|
||
|
{
|
||
|
// this is bad. no more mem? what to do? need to shutdown entire thread/process
|
||
|
FAIL_RPT1(E_OUTOFMEMORY,"new COpFile()");
|
||
|
|
||
|
// continue
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// add operation
|
||
|
hr = pWatchFileSys->m_pOpQ->Add(pOp);
|
||
|
if(FAILED(hr))
|
||
|
{
|
||
|
// @todo log err
|
||
|
FAIL_RPT1(E_FAIL,"COpQueue::Add failed");
|
||
|
delete pOp;
|
||
|
}
|
||
|
if(hr == S_FALSE) // op was a dupl
|
||
|
delete pOp; // so delete and ignore
|
||
|
pOp = NULL;
|
||
|
|
||
|
// get next offset
|
||
|
pfni = (PFILE_NOTIFY_INFORMATION) ((LPBYTE)pfni + cbOffset);
|
||
|
} while(cbOffset);
|
||
|
|
||
|
// reissue the watch
|
||
|
if(!pWatchFileSys->IssueWatch(pDirInfo))
|
||
|
{
|
||
|
// @todo: log error
|
||
|
}
|
||
|
}
|
||
|
} while( pDirInfo );
|
||
|
|
||
|
// end of thread
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
bool CWatchFileSys::AddHelper(CWatchInfo &rWatchInfo,CDirInfo *pDirInfo)
|
||
|
{
|
||
|
// create completion port, or add to it
|
||
|
rWatchInfo.m_hCompPort = CreateIoCompletionPort(pDirInfo->m_hDir,
|
||
|
rWatchInfo.m_hCompPort,
|
||
|
(DWORD)(CDirInfo*) pDirInfo,
|
||
|
0);
|
||
|
if(!rWatchInfo.m_hCompPort)
|
||
|
return false;
|
||
|
|
||
|
// watch directory
|
||
|
if(!IssueWatch(pDirInfo))
|
||
|
return false;
|
||
|
|
||
|
// create notification thread (if not already exist)
|
||
|
if(!rWatchInfo.m_hThread)
|
||
|
{
|
||
|
rWatchInfo.m_hThread = _beginthreadex(
|
||
|
NULL, // no security descriptor
|
||
|
0, // default stack size
|
||
|
NotificationThreadProc, //thread procedure
|
||
|
&rWatchInfo, // thread procedure argument
|
||
|
0, // run imideately
|
||
|
&rWatchInfo.m_iThreadID); // place to store id
|
||
|
if(!rWatchInfo.m_hThread)
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// if everything was successfull, add dirinfo to list
|
||
|
rWatchInfo.AddDirInfo(pDirInfo);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
HRESULT CWatchFileSys::Add(LPCTSTR szDir,LPCTSTR szRelPrj)
|
||
|
{
|
||
|
CAutoPtr<CDirInfo> pDirInfo;
|
||
|
_ASSERTE(szDir && szRelPrj);
|
||
|
|
||
|
// @todo: check that dir is not already part of list (check in subtree as well)
|
||
|
|
||
|
// @todo: convert szDir to Abstolute path
|
||
|
|
||
|
// create dirinfo
|
||
|
pDirInfo = new CDirInfo(szDir,szRelPrj);
|
||
|
if(!pDirInfo)
|
||
|
FAIL_RTN1(E_OUTOFMEMORY,"new CDirInfo()");
|
||
|
|
||
|
// get handle to dir
|
||
|
pDirInfo->m_hDir = CreateFile(szDir,
|
||
|
FILE_LIST_DIRECTORY,
|
||
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
||
|
NULL,
|
||
|
OPEN_EXISTING,
|
||
|
FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED,
|
||
|
NULL);
|
||
|
if(pDirInfo->m_hDir == INVALID_HANDLE_VALUE)
|
||
|
goto _Error;
|
||
|
|
||
|
if(!AddHelper(m_WatchInfo,pDirInfo))
|
||
|
goto _Error;
|
||
|
// @todo: call only if startup flag set.
|
||
|
// the following call is slow!!!
|
||
|
// the following line should only be called if you want to bring the
|
||
|
// versioning store to the same state as the file system. I.e. all files
|
||
|
// will be checked in, and unnecessary files in the version store will be
|
||
|
// marked deleted.
|
||
|
|
||
|
// pVerEngine->SyncPrj(szPrj.c_str,szDir); // @todo: should only be called when
|
||
|
pDirInfo = NULL;
|
||
|
|
||
|
CError::Trace("Watching: ");
|
||
|
CError::Trace(szDir);
|
||
|
CError::Trace("\n");
|
||
|
|
||
|
return S_OK;
|
||
|
|
||
|
_Error:
|
||
|
CError::ErrorMsgBox(GetLastError());
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
|
||
|
BOOL CWatchFileSys::IssueWatch(CDirInfo * pDirInfo)
|
||
|
{
|
||
|
_ASSERTE(pDirInfo);
|
||
|
BOOL b;
|
||
|
DWORD dwNotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME
|
||
|
| FILE_NOTIFY_CHANGE_DIR_NAME
|
||
|
// | FILE_NOTIFY_CHANGE_SIZE
|
||
|
// | FILE_NOTIFY_CHANGE_CREATION
|
||
|
| FILE_NOTIFY_CHANGE_LAST_WRITE;
|
||
|
|
||
|
b = ReadDirectoryChangesW(pDirInfo->m_hDir,
|
||
|
pDirInfo->m_cBuffer,
|
||
|
MAX_BUFFER,
|
||
|
TRUE,
|
||
|
dwNotifyFilter,
|
||
|
& pDirInfo->m_iBuffer,
|
||
|
& pDirInfo->m_Overlapped,
|
||
|
NULL);
|
||
|
if(!b)
|
||
|
{
|
||
|
CError::ErrorTrace(GetLastError(),"ReadDirectoryChangesW failed",__FILE__,__LINE__);
|
||
|
}
|
||
|
return b;
|
||
|
}
|
||
|
|