windows-nt/Source/XPSP1/NT/inetsrv/iis/utils/cfgmnt/dirwatch.cpp
2020-09-26 16:20:57 +08:00

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;
}