Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

386 lines
10 KiB

  1. // DirWatch.cpp: implementation of the CWatchFileSys class.
  2. //
  3. //////////////////////////////////////////////////////////////////////
  4. #include "stdafx.h"
  5. #include "DirWatch.h"
  6. #include "Error.h"
  7. #include "MT.h"
  8. #include "AutoPtr.h"
  9. #include "Error.h"
  10. #include "iadmw.h" // COM Interface header
  11. #include "iiscnfg.h" // MD_ & IIS_MD_ #defines
  12. //////////////////////////////////////////////////////////////////////
  13. // Construction/Destruction
  14. //////////////////////////////////////////////////////////////////////
  15. CWatchFileSys::CWatchFileSys()
  16. : m_WatchInfo(this), m_pOpQ(NULL)
  17. {
  18. }
  19. CWatchFileSys::~CWatchFileSys()
  20. {
  21. // verify that thread terminated
  22. ShutDown();
  23. }
  24. HRESULT CWatchFileSys::NewInit(COpQueue *pOpQ)
  25. {
  26. _ASSERTE(pOpQ && !m_pOpQ);
  27. m_pOpQ = pOpQ;
  28. HRESULT hr = S_OK;
  29. CComPtr<IMSAdminBase> pIAdminBase;
  30. // check if we already have a metabase instance
  31. // create adminbase instance
  32. hr = CoCreateInstance(CLSID_MSAdminBase,
  33. NULL,
  34. CLSCTX_ALL,
  35. IID_IMSAdminBase,
  36. (void **) &pIAdminBase);
  37. IF_FAIL_RTN1(hr,"CoCreateInstance IID_IMSAdminBase");
  38. METADATA_HANDLE hMD = NULL;
  39. WCHAR szKeyName[3+6+ 2* METADATA_MAX_NAME_LEN] // /LM/W3SVC/sitename/vir_dir
  40. = L"/LM/W3SVC/";
  41. LPTSTR szSiteKeyName = &szKeyName[wcslen(szKeyName)]; // point to the end of string so we can append it
  42. DWORD iSiteEnumIndex = 0;
  43. LPTSTR szVDirKeyName = NULL;
  44. DWORD iVDirEnumIndex = 0;
  45. hr = pIAdminBase->OpenKey(METADATA_MASTER_ROOT_HANDLE,
  46. szKeyName,
  47. METADATA_PERMISSION_READ,
  48. 20,
  49. &hMD);
  50. IF_FAIL_RTN1(hr,"IAdminBase::OpenKey");
  51. METADATA_RECORD MDRec;
  52. DWORD iBufLen = 1024;
  53. DWORD iReqBufLen = 0;
  54. PBYTE pbBuf = new BYTE[iBufLen];
  55. if(!pbBuf)
  56. {
  57. pIAdminBase->CloseKey(hMD);
  58. return E_OUTOFMEMORY;
  59. }
  60. DWORD iDataIndex = 0;
  61. while(SUCCEEDED(hr = pIAdminBase->EnumKeys(hMD,TEXT(""),szSiteKeyName,iSiteEnumIndex)))
  62. {
  63. // iterate through all virtual sites on this machine
  64. wcscat(szSiteKeyName,L"/ROOT/");
  65. szVDirKeyName = szSiteKeyName + wcslen(szSiteKeyName);
  66. iVDirEnumIndex = 0;
  67. while(SUCCEEDED(hr = pIAdminBase->EnumKeys(hMD,szSiteKeyName,szVDirKeyName,iVDirEnumIndex)))
  68. {
  69. // iterate through all virtual directories in each site
  70. MDRec.dwMDIdentifier = MD_VR_PATH;
  71. MDRec.dwMDAttributes = METADATA_INHERIT;
  72. MDRec.dwMDUserType = IIS_MD_UT_FILE;
  73. MDRec.dwMDDataType = ALL_METADATA;
  74. MDRec.dwMDDataLen = iBufLen;
  75. MDRec.pbMDData = pbBuf;
  76. hr = pIAdminBase->GetData(hMD,szSiteKeyName,&MDRec,&iReqBufLen);
  77. if(hr == RETURNCODETOHRESULT(ERROR_INSUFFICIENT_BUFFER))
  78. {
  79. delete [] pbBuf;
  80. pbBuf = new BYTE[iReqBufLen];
  81. if(!pbBuf)
  82. {
  83. pIAdminBase->CloseKey(hMD);
  84. return E_OUTOFMEMORY;
  85. }
  86. iBufLen = iReqBufLen;
  87. MDRec.dwMDDataLen = iBufLen;
  88. MDRec.pbMDData = pbBuf;
  89. hr = pIAdminBase->GetData(hMD,szSiteKeyName,&MDRec,&iReqBufLen);
  90. }
  91. // @todo: verify that this dir should be watched
  92. // i.e. check if do-not-version flag is set
  93. if(SUCCEEDED(hr))
  94. {
  95. // add
  96. wstring szPrj(L"/Files/"); //@todo: decide on prj
  97. szPrj.append(szSiteKeyName);
  98. hr = Add((LPCTSTR)MDRec.pbMDData,szPrj.c_str());
  99. IF_FAIL_RPT1(hr,"CWatchFileSys::Add");
  100. }
  101. else
  102. {
  103. CError::Trace("Can't get dir for ");
  104. CError::Trace(szVDirKeyName);
  105. CError::Trace("\n");
  106. }
  107. iVDirEnumIndex++;
  108. }
  109. iSiteEnumIndex++;
  110. }
  111. pIAdminBase->CloseKey(hMD);
  112. delete [] pbBuf;
  113. return S_OK;
  114. }
  115. void CWatchFileSys::ShutDownHelper(CWatchInfo &rWatchInfo)
  116. {
  117. if(rWatchInfo.m_hThread)
  118. {
  119. // end notification thread
  120. PostQueuedCompletionStatus(rWatchInfo.m_hCompPort,0,0,NULL);
  121. // wait for thread to finish
  122. WaitForSingleObject(rWatchInfo.m_hThread,INFINITE);
  123. CloseHandle(rWatchInfo.m_hThread);
  124. rWatchInfo.m_hThread = NULL;
  125. rWatchInfo.m_iThreadID = 0;
  126. }
  127. if(rWatchInfo.m_hCompPort)
  128. {
  129. // clean up
  130. CloseHandle(rWatchInfo.m_hCompPort);
  131. rWatchInfo.m_hCompPort = NULL;
  132. }
  133. }
  134. void CWatchFileSys::ShutDown()
  135. {
  136. ShutDownHelper(m_WatchInfo);
  137. m_pOpQ = NULL;
  138. }
  139. DWORD WINAPI CWatchFileSys::NotificationThreadProc(LPVOID lpParam)
  140. {
  141. _ASSERTE(lpParam);
  142. CWatchInfo *pWI = (CWatchInfo*) lpParam;
  143. CWatchFileSys *pWatchFileSys = pWI->m_pWatchFileSys;
  144. // vars for accessing the notification
  145. DWORD iBytes = 0;
  146. CDirInfo *pDirInfo = NULL;
  147. LPOVERLAPPED pOverlapped = NULL;
  148. PFILE_NOTIFY_INFORMATION pfni = NULL;
  149. DWORD cbOffset = 0;
  150. // vars for creating a file op
  151. HRESULT hr;
  152. COpFileSys *pOp = NULL;
  153. LPCTSTR szPrj = NULL;
  154. LPCTSTR szDir = NULL;
  155. wstring szFileName;
  156. wstring szOldFileName;
  157. do
  158. {
  159. _ASSERTE(pWI->m_hCompPort);
  160. GetQueuedCompletionStatus(pWI->m_hCompPort,
  161. &iBytes,
  162. (LPDWORD) &pDirInfo,
  163. &pOverlapped,
  164. INFINITE);
  165. if(pDirInfo)
  166. {
  167. // get ptr to first file_notify_info in buffer
  168. pfni = (PFILE_NOTIFY_INFORMATION) pDirInfo->m_cBuffer;
  169. // clean
  170. szFileName.erase(); // empty to avoid compare wrong compares
  171. szOldFileName.erase(); // empty
  172. // remember dir and prj they are the same for all entries
  173. szPrj = pDirInfo->m_szPrj.c_str();
  174. szDir = pDirInfo->m_szDir.c_str();
  175. // process all file_notify_infos in buffer
  176. _ASSERTE(pWatchFileSys->m_pOpQ);
  177. do
  178. {
  179. cbOffset = pfni->NextEntryOffset;
  180. // sometime an errorous action #0 is send, let's ignore it
  181. switch(pfni->Action) {
  182. case FILE_ACTION_ADDED:
  183. case FILE_ACTION_REMOVED:
  184. case FILE_ACTION_MODIFIED:
  185. case FILE_ACTION_RENAMED_OLD_NAME:
  186. case FILE_ACTION_RENAMED_NEW_NAME:
  187. break;
  188. default:
  189. // unknown action, let's ignore it
  190. pfni = (PFILE_NOTIFY_INFORMATION) ((LPBYTE)pfni + cbOffset);// get next offset
  191. continue;
  192. }
  193. // on rename remember old filename
  194. szOldFileName.erase();
  195. if(pfni->Action == FILE_ACTION_RENAMED_OLD_NAME)
  196. {
  197. // make sure next entry exists and is new-name entry
  198. _ASSERTE(cbOffset); // there is another entry
  199. PFILE_NOTIFY_INFORMATION pNextfni = (PFILE_NOTIFY_INFORMATION) ((LPBYTE)pfni + cbOffset);
  200. _ASSERTE(pNextfni->Action == FILE_ACTION_RENAMED_NEW_NAME); // the next entry contians the new name
  201. // assign old name
  202. szOldFileName.assign(pfni->FileName,pfni->FileNameLength/2);
  203. // skip to next (new-name) entry
  204. pfni = pNextfni;
  205. cbOffset = pNextfni->NextEntryOffset;
  206. // clear szFileName so it doesn't get skiped in next lines
  207. szFileName.erase();
  208. }
  209. // assign affected filename
  210. szFileName.assign(pfni->FileName,pfni->FileNameLength/2);
  211. // create new operation
  212. pOp = new COpFileSys(pfni->Action,szPrj,szDir,szFileName.c_str(),szOldFileName.c_str());
  213. if(!pOp)
  214. {
  215. // this is bad. no more mem? what to do? need to shutdown entire thread/process
  216. FAIL_RPT1(E_OUTOFMEMORY,"new COpFile()");
  217. // continue
  218. break;
  219. }
  220. // add operation
  221. hr = pWatchFileSys->m_pOpQ->Add(pOp);
  222. if(FAILED(hr))
  223. {
  224. // @todo log err
  225. FAIL_RPT1(E_FAIL,"COpQueue::Add failed");
  226. delete pOp;
  227. }
  228. if(hr == S_FALSE) // op was a dupl
  229. delete pOp; // so delete and ignore
  230. pOp = NULL;
  231. // get next offset
  232. pfni = (PFILE_NOTIFY_INFORMATION) ((LPBYTE)pfni + cbOffset);
  233. } while(cbOffset);
  234. // reissue the watch
  235. if(!pWatchFileSys->IssueWatch(pDirInfo))
  236. {
  237. // @todo: log error
  238. }
  239. }
  240. } while( pDirInfo );
  241. // end of thread
  242. return 0;
  243. }
  244. bool CWatchFileSys::AddHelper(CWatchInfo &rWatchInfo,CDirInfo *pDirInfo)
  245. {
  246. // create completion port, or add to it
  247. rWatchInfo.m_hCompPort = CreateIoCompletionPort(pDirInfo->m_hDir,
  248. rWatchInfo.m_hCompPort,
  249. (DWORD)(CDirInfo*) pDirInfo,
  250. 0);
  251. if(!rWatchInfo.m_hCompPort)
  252. return false;
  253. // watch directory
  254. if(!IssueWatch(pDirInfo))
  255. return false;
  256. // create notification thread (if not already exist)
  257. if(!rWatchInfo.m_hThread)
  258. {
  259. rWatchInfo.m_hThread = _beginthreadex(
  260. NULL, // no security descriptor
  261. 0, // default stack size
  262. NotificationThreadProc, //thread procedure
  263. &rWatchInfo, // thread procedure argument
  264. 0, // run imideately
  265. &rWatchInfo.m_iThreadID); // place to store id
  266. if(!rWatchInfo.m_hThread)
  267. return false;
  268. }
  269. // if everything was successfull, add dirinfo to list
  270. rWatchInfo.AddDirInfo(pDirInfo);
  271. return true;
  272. }
  273. HRESULT CWatchFileSys::Add(LPCTSTR szDir,LPCTSTR szRelPrj)
  274. {
  275. CAutoPtr<CDirInfo> pDirInfo;
  276. _ASSERTE(szDir && szRelPrj);
  277. // @todo: check that dir is not already part of list (check in subtree as well)
  278. // @todo: convert szDir to Abstolute path
  279. // create dirinfo
  280. pDirInfo = new CDirInfo(szDir,szRelPrj);
  281. if(!pDirInfo)
  282. FAIL_RTN1(E_OUTOFMEMORY,"new CDirInfo()");
  283. // get handle to dir
  284. pDirInfo->m_hDir = CreateFile(szDir,
  285. FILE_LIST_DIRECTORY,
  286. FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
  287. NULL,
  288. OPEN_EXISTING,
  289. FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED,
  290. NULL);
  291. if(pDirInfo->m_hDir == INVALID_HANDLE_VALUE)
  292. goto _Error;
  293. if(!AddHelper(m_WatchInfo,pDirInfo))
  294. goto _Error;
  295. // @todo: call only if startup flag set.
  296. // the following call is slow!!!
  297. // the following line should only be called if you want to bring the
  298. // versioning store to the same state as the file system. I.e. all files
  299. // will be checked in, and unnecessary files in the version store will be
  300. // marked deleted.
  301. // pVerEngine->SyncPrj(szPrj.c_str,szDir); // @todo: should only be called when
  302. pDirInfo = NULL;
  303. CError::Trace("Watching: ");
  304. CError::Trace(szDir);
  305. CError::Trace("\n");
  306. return S_OK;
  307. _Error:
  308. CError::ErrorMsgBox(GetLastError());
  309. return E_FAIL;
  310. }
  311. BOOL CWatchFileSys::IssueWatch(CDirInfo * pDirInfo)
  312. {
  313. _ASSERTE(pDirInfo);
  314. BOOL b;
  315. DWORD dwNotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME
  316. | FILE_NOTIFY_CHANGE_DIR_NAME
  317. // | FILE_NOTIFY_CHANGE_SIZE
  318. // | FILE_NOTIFY_CHANGE_CREATION
  319. | FILE_NOTIFY_CHANGE_LAST_WRITE;
  320. b = ReadDirectoryChangesW(pDirInfo->m_hDir,
  321. pDirInfo->m_cBuffer,
  322. MAX_BUFFER,
  323. TRUE,
  324. dwNotifyFilter,
  325. & pDirInfo->m_iBuffer,
  326. & pDirInfo->m_Overlapped,
  327. NULL);
  328. if(!b)
  329. {
  330. CError::ErrorTrace(GetLastError(),"ReadDirectoryChangesW failed",__FILE__,__LINE__);
  331. }
  332. return b;
  333. }