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